A sandboxed child that dies without sending names the tool it was running - #112
Merged
Conversation
…ning
`poll()` returns true when the pipe is readable, and a closed pipe is
readable. So a child that died without sending — `os._exit`, a segfault, an
OOM-kill, anything that kills the process rather than raising inside
`spec.fn` — fell through the timeout guard into `recv()` and raised a bare
`EOFError('')`.
`run` has two failure shapes and that was neither: `SandboxViolation` for a
confinement breach, `RuntimeError(f"tool {name!r} failed: ...")` for the tool's
own exception. An EOFError with an empty message is attributable to nothing.
Downstream is where it bit. `AgentNode`'s loop catches it under its blanket
`except Exception` and renders `f"TOOL_ERROR: {exc}"` — and `str(EOFError(''))`
is `''`, so the model was handed `TOOL_ERROR:` with nothing after the colon. It
was told its call failed and given no way to tell why, which tool, or whether a
retry could help; the same text went into the trace, so the audit trail could
not explain the failure either. The `_CALL_SHAPE_ERROR` hint below that clause
cannot fire on it, since it matches on message text and there is none.
This is the shape of a defect already closed here once: a curtailed phase
writing `[budget_exhausted] ` with nothing after it. Same empty message, one
layer down.
Now a `RuntimeError` naming the tool and the exit code, with a negative one
rendered as the signal that killed it — which is what tells an OOM-kill apart
from a deliberate `_exit`. Deliberately not a `SandboxViolation`: a child dying
is not evidence it tried to escape confinement, and a violation is a specific
accusation that lands in the trace as one.
The three new tests go red without the fix, with the EOFError raising out of
`multiprocessing/connection.py` exactly as reported. The normal return, the
tool-raises and the timeout paths are untouched.
Closes #111
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Shashankss1205
force-pushed
the
fix/sandbox-child-death-eof
branch
from
August 13, 2026 19:14
110b813 to
d68596f
Compare
Merged
Shashankss1205
added a commit
that referenced
this pull request
Aug 13, 2026
Two defects closed since 0.1.6, both found by re-verifying a stale bug backlog against main rather than by a report: - admission accepted `END` as an edge source and `START` as a target, so a graph that cannot be built was admitted and failed in materialisation — charged to the execution-failure allowance rather than the rejection one, and reaching the planner as prose instead of a code and a remedy (#108/#109). - a sandboxed child that died without sending escaped as a bare `EOFError('')`, which the agent loop rendered to the model as `TOOL_ERROR:` and nothing else (#111/#112). The version moves in the two places CI compares and the six where prose states it. `tests/test_deep_dive.py` and `tests/test_cookbook_*.py` assert all but the README line, which is how they stay right. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #111.
The gap
poll()returns true when the pipe is readable, and a closed pipe is readable. So a child that died without sending —os._exit, a segfault, an OOM-kill — fell through the timeout guard intorecv():runhas two failure shapes and this was neither:SandboxViolationfor a confinement breach,RuntimeError(f"tool {name!r} failed: …")for the tool's own exception.Why it mattered
AgentNode's loop catches it under the blanket clause and rendersf"TOOL_ERROR: {exc}".str(EOFError(''))is'', so the model was handed:Nothing after the colon. Told its call failed, given no way to tell why, which tool, or whether a retry could help — and the same text went into the trace, so the audit trail couldn't explain it either. The
_CALL_SHAPE_ERRORhint just below can't fire on it, since it matches on message text and there is none.This is a defect shape already closed here once: "a phase the budget curtailed reported nothing at all … wrote
[budget_exhausted]with nothing after it." Same empty message, one layer down.After
A negative exit code renders as the signal that killed it — which is what tells an OOM-kill apart from a deliberate
_exit.Deliberately still a
RuntimeError, not aSandboxViolation. A child dying is not evidence it tried to escape confinement, and a violation is a specific accusation that lands in the trace as one. One of the tests asserts that directly.Verification
tests/test_harness_gate.py(parametrised over clean exit and SIGKILL, plus one pinning the signal detail). All go red without the fix, with theEOFErrorraising out ofmultiprocessing/connection.pyexactly as reported.uv run pytestgreen ·uv run ruff check .clean.The forked child runs module-level functions rather than closures, since a
forkchild needs picklable top-level bodies for this to behave the same way under other start methods.🤖 Generated with Claude Code