- Shell 100%
|
Hugo O'Connor
3c1ce951a1
Composes withdone with 'codex exec' (Codex's non-interactive subcommand) using --dangerously-bypass-approvals-and-sandbox so the agent can invoke its bash tool to write the sentinel. Authenticated via 'codex login' (ChatGPT/Codex subscription billing). Verified live: rc=7 propagates cleanly, transcript clean (no /dev/tty bypass issues like opencode), no terminal-mode corruption. Added recipes/codex-exec-sentinel.sh as a turnkey script and a new section 2 in recipes/README.md showing the inline pattern. Renumbered sections 3-7 → 4-8. |
||
|---|---|---|
| recipes | feat(recipes): codex-exec-sentinel for OpenAI Codex CLI | |
| tests | BREAKING: v3.0.0 — drop -m marker-line mode, sentinel-file only | |
| .gitignore | revert: drop Rust v3 port, restore shell withdone v2.1.1 | |
| LICENSE | Initial commit: withdone v1.0.0 | |
| README.md | BREAKING: v3.0.0 — drop -m marker-line mode, sentinel-file only | |
| SPEC-001-withdone.md | BREAKING: v3.0.0 — drop -m marker-line mode, sentinel-file only | |
| withdone | BREAKING: v3.0.0 — drop -m marker-line mode, sentinel-file only | |
withdone
A tiny POSIX sh process wrapper that lets a coding-agent CLI declare
its own logical completion and exit code by writing it to a sentinel
file. withdone watches the path, kills the child on detection, and
exits with the agent-supplied code.
withdone -s PATH -- COMMAND [ARGS...]
Caller mints PATH, embeds it in the agent's prompt as the completion
instruction ("when done: echo N > PATH"), passes the same path to
withdone via -s. Agent's bash tool writes the code; withdone reads it
and propagates as its own exit code.
See SPEC-001-withdone.md for the contract,
recipes/ for compositions with expect(1), script(1),
timeout(1), etc.
Why
Coding agents have no built-in way to tell an orchestrator "I finished
task X with exit code N" — the host process keeps running. Existing
workarounds (parse output for a marker, detect idle, watch for prompt
re-appearance) all guess at completion and can't communicate semantic
exit codes. withdone gives the agent a direct channel: write the
code to a path you control via your bash tool. The wrapper exits with
that code, no guessing.
withdone deliberately does not allocate PTYs, inject prompts,
detect readiness, parse output, or do log rotation. Those are existing
Unix tools (script, expect, unbuffer, timeout, tee, sed,
ts); compose them. See
ADR-002
for why.
Install
curl -fsSL https://codeberg.org/anuna/withdone/raw/branch/main/withdone \
-o ~/.local/bin/withdone && chmod +x ~/.local/bin/withdone
Or, if you've cloned the repo and want to track edits:
ln -s "$(pwd)/withdone" ~/.local/bin/withdone
Verify with withdone --version → withdone 3.0.0. No dependencies
beyond mkfifo, mktemp, sleep, cat, tr — all POSIX-baseline.
Quick example
s=$(mktemp -u "${TMPDIR:-/tmp}/withdone-XXXXXX")
prompt="Respond with the single word 'hello'.
When done, run this command via your bash tool — no other output:
echo 0 > $s(use a non-zero number if you couldn't complete the task; do not run
this command until the task is genuinely done.)"
withdone -s "$s" -- opencode run --dangerously-skip-permissions "$prompt" </dev/null
echo $? # 0
The completion instruction (canonical)
Every prompt you send through withdone MUST teach the agent how to signal completion. The canonical wording is:
When you have completed the task, signal completion via your bash tool by running exactly:
echo <CODE> > <SENTINEL_PATH>Where
<CODE>is a single non-negative integer:
0on success1if you could not complete the task2+for other non-zero codes describing specific failuresDo not run this command until the task is genuinely done.
Paste it into your prompt verbatim with the sentinel path substituted. If you find a phrasing that gives more reliable agent compliance, open a PR against this README.
Usage
withdone -s PATH [OPTIONS] -- COMMAND [ARGS...]
Options:
-s, --sentinel PATH path the agent writes its exit code to (required)
MUST NOT exist at invocation time
--grace SECS SIGTERM-to-SIGKILL grace period (default: 5)
-h, --help
-V, --version
Exit codes:
| Code | Meaning |
|---|---|
| 0 | Agent wrote 0, or child exited 0 |
| 1–63, 65–123 | Forwarded child or agent-supplied code |
| 64 | Usage error or invalid sentinel content |
| 127 | Exec failure |
For wall-clock bounds, wrap with timeout(1): timeout 600 withdone ....
Pipeline composition
The whole point: exit codes propagate cleanly through shell composition.
# Watch loop — the canonical use case
while task=$(get_next_task); do
s=$(mktemp -u "${TMPDIR:-/tmp}/withdone-XXXXXX")
prompt="$taskWhen done: echo 0 > $s (success) or echo N > $s (failure code N)."
timeout 600 withdone -s "$s" -- opencode run \
--dangerously-skip-permissions "$prompt" \
</dev/null > "logs/${task}.log" 2>&1
case $? in
0) record_success "$task" ;;
124) record_wallclock_exceeded "$task" ;;
*) record_failure "$task" "$?" ;;
esac
done
See recipes/README.md for more patterns —
including Claude TUI subscription mode via expect.
Testing
tests/live-opencode.sh # live LLM integration; costs a small LLM round-trip
Status
v3.0.0 — approved and implemented.
v3 removes the v2 marker-line mode (-m MARKER watching stdout). The
sentinel-file mode covers every case marker-line did, with strictly
better reliability across TUI agents, /dev/tty-bypass agents, and
multilingual model paraphrasing. Single mode, one obvious way to use
the tool.
The script and its spec are intended to remain stable; cat -v Considered Harmful is the explicit standing rebuke against feature
additions.
License
Apache 2.0 — see LICENSE.