Skip to content

feat: headless exit-status contract for claude-box - #9

Merged
alechill merged 2 commits into
mainfrom
feat/headless-exit-status
Aug 8, 2026
Merged

alechill merged 2 commits into
mainfrom
feat/headless-exit-status

Conversation

@alechill

@alechill alechill commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

What

Makes claude-box -p … compose cleanly in scripts, CI, and one-shots by giving the launcher a real exit-status contract and clean redirected streams.

  • Exit codes: harness's own status passes through verbatim (0, 1–124, 128+ signal deaths); docker's container-lifecycle band maps to distinguishable launcher faults — 125 image-missing, 126 engine-start-failed, 127 harness-not-executable. Every fault also prints an unconditional claude-box: fault=<reason> detail="…" line to stderr so a caller can disambiguate.
  • Image-missing caught pre-run so a runtime 125 unambiguously means engine/runtime rejection.
  • Clean streams: -t (PTY) only when both stdout and stderr are terminals, so claude-box -p … > out.txt captures no CRLF and no [entrypoint] noise; -i only when stdin is actually connected.
  • No half-flushed snapshots: sync_back stops the container before copying out, so a supervisor's signal on a headless run can't snapshot mid-write.
  • Test hooks: CLAUDE_BOX_DEBUG=1 gates the entrypoint state dump (off by default); CLAUDE_BOX_EXEC=1 execs raw args instead of claude for acceptance testing.
  • Runbook: docs/runbook-headless-exit.md maps 1:1 to the acceptance criteria.

Second commit tracks the repo's own .faffrc.yaml and ignores the local-only faff files.

Verification

bash -n passes on both scripts. Full manual acceptance steps are in the runbook.

🤖 Generated with Claude Code

alechill and others added 2 commits August 8, 2026 23:06
Make `claude-box -p …` compose in scripts, CI, and one-shots:

- Propagate the harness's own exit code verbatim (0, 1–124, 128+ signal
  deaths) and map docker's container-lifecycle band to distinguishable
  launcher faults: 125 image-missing, 126 engine-start-failed, 127
  harness-not-executable. Each fault also prints a machine-readable
  `claude-box: fault=<reason> detail="…"` line to stderr unconditionally.
- Catch image-missing pre-run (build failure or image absent) so a runtime
  125 unambiguously means engine/runtime rejection.
- Compose docker -i/-t independently from the fd shapes: allocate a PTY only
  when both stdout and stderr are terminals (so a redirected stdout capture
  stays clean — no CRLF, no [entrypoint] lines), and attach stdin only when
  something is connected.
- Stop the session container before sync_back copies out, so a supervisor's
  signal on a headless run can't snapshot half-flushed state.
- Gate the entrypoint state dump behind CLAUDE_BOX_DEBUG (off by default) and
  add the CLAUDE_BOX_EXEC acceptance hook that execs raw args instead of
  claude, dropping the launcher's leading --dangerously-skip-permissions.
- Document the whole contract in docs/runbook-headless-exit.md (and un-ignore
  docs/ so the runbook is tracked).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Track .faffrc.yaml (Linear tracker, agile lens, nlspec spec) and ignore the
local-only .faffrc.local.yaml / .faff / .env.* alongside it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@alechill
alechill merged commit 35512f6 into main Aug 8, 2026
2 checks passed
@alechill
alechill deleted the feat/headless-exit-status branch August 8, 2026 23:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant