Public: anyone can read this; the crew can change it.
crew-sandbox-real skips by a named cause: a syscall, a mount or a missing binary, not "nested or refused" #10755
Check · Review · Land · Live done — Landed and live in production.
When `test/crew-sandbox-real.test.ts` cannot make a sandbox, its skip line now gives one named cause in place of "nested in a crew sandbox or user namespaces refused". The cause is user namespaces refused, a mount denied, or a missing binary (bwrap, socat, srt, rg). A failure that fits none of these keeps the probe's own text.
shipped production is serving it deploy 7bd4f86
When test/crew-sandbox-real.test.ts cannot make a sandbox, its skip line now gives one named cause in place of "nested in a crew sandbox or user namespaces refused". The cause is user namespaces refused, a mount denied, or a missing binary (bwrap, socat, srt, rg). A failure that fits none of these keeps the probe's own text.
src/crew/sandbox_cause.ts(new):sandboxCause(reason)is a pure function that reads the failure text fromsandboxAvailable()and returns the cause. The probe labels every bwrap failure "user namespaces are refused here (…)". The classifier reads bwrap's own line inside it, so a mount bwrap refused (Can't mount proc…,Failed to make / slave…) is reported as a mount, not as namespaces.skipReasonbuilds the skip line andnamesCausechecks that a reason uses those words.test/crew-sandbox-real.test.ts: the skip line isno sandbox can be made here: <cause>. On a host that can sandbox, a missingsrtorrgstill fails the test, and the error now names the binary and its path. The probe itself is unchanged, so a host that can sandbox still runs the suite.scripts/gate.ts: since #10743 the gate no longer excludes this file, so there was no exclusionreasonto reword. I added no exclusion. Instead the gate's header says any future exclusion must use the skip line's words. A test intest/crew-sandbox-cause.test.tsfails if such an exclusion uses any other wording.test/crew-sandbox-cause.test.ts(new): one test per cause, one for the unknown case, plus the skip-line and gate-wording tests above. It fails without the module.
Checks: bun scripts/gate.ts and bun x tsc --noEmit pass. scripts/fast.ts said the diff touches scripts/gate.ts and is too broad to name tests for. I ran the new test, the real-sandbox test, and check-list, shards, nightly and crew-sandbox; all pass. The real-sandbox suite skipped here because this sandbox refuses user namespaces, so it has not actually run in this pull; the forge's check runs it on the push. The skip line read:
skipped: test/crew-sandbox-real.test.ts: no sandbox can be made here: user namespaces refused (bwrap: No permissions to create a new namespace, …)
To try it: bun test test/crew-sandbox-cause.test.ts test/crew-sandbox-real.test.ts
🤖 Generated with Claude Code
When test/crew-sandbox-real.test.ts cannot make a sandbox, its skip line now gives one named cause in place of "nested in a crew sandbox or user namespaces refused". The cause is user namespaces refused, a mount denied, or a missing binary (bwrap, socat, srt, rg). A failure that fits none of these keeps the probe's own text.
src/crew/sandbox_cause.ts(new):sandboxCause(reason)is a pure function that reads the failure text fromsandboxAvailable()and returns the cause. The probe labels every bwrap failure "user namespaces are refused here (…)". The classifier reads bwrap's own line inside it, so a mount bwrap refused (Can't mount proc…,Failed to make / slave…) is reported as a mount, not as namespaces.skipReasonbuilds the skip line andnamesCausechecks that a reason uses those words.src/crew/dispatch.ts: the startup warning "agent sandbox not available here; runs are unrestricted" now carries acausefield besidereason. This is the only behaviour change, a log field. It also means the new module is used fromsrc/, not only from tests; the first push failedtest/unreached.test.ts("every module under src/ is imported by another, or allowed") because it wasn't.test/crew-sandbox-real.test.ts: the skip line isno sandbox can be made here: <cause>. On a host that can sandbox, a missingsrtorrgstill fails the test, and the error now names the binary and its path. The probe itself is unchanged, so a host that can sandbox still runs the suite.scripts/gate.ts: since #10743 the gate no longer excludes this file, so there was no exclusionreasonto reword. I added no exclusion. Instead the gate's header says any future exclusion must use the skip line's words. A test intest/crew-sandbox-cause.test.tsfails if such an exclusion uses any other wording.test/crew-sandbox-cause.test.ts(new): one test per cause, one for the unknown case, plus the skip-line and gate-wording tests above. It fails without the module.
Checks: bun scripts/gate.ts and bun x tsc --noEmit pass. These test files pass: the new one, crew-sandbox-real, unreached, vitals-held-agent, orphan-dispatch and team-dispatch, plus check-list, shards, nightly and crew-sandbox on the first push. The real-sandbox suite skipped here because this sandbox refuses user namespaces, so it has not actually run in this pull; the forge's check runs it on the push. The skip line read:
skipped: test/crew-sandbox-real.test.ts: no sandbox can be made here: user namespaces refused (bwrap: No permissions to create a new namespace, …)
To try it: bun test test/crew-sandbox-cause.test.ts test/crew-sandbox-real.test.ts
🤖 Generated with Claude Code
test/crew-sandbox-real.test.ts no longer skips with a vague reason. On a host that can't build the crew sandbox, it now gives one of three named causes: user namespaces were refused, a mount was denied, or a binary is missing.
src/crew/sandbox_cause.tsworks out the cause from what the sandbox reported when it failed. It has its own tests intest/crew-sandbox-cause.test.ts.test/crew-sandbox-real.test.tsnow skips with that cause.src/crew/dispatch.tsuses the same cause in the crew's warning when it runs without a sandbox, so the module is used by the app itself and not only by tests.scripts/gate.tsgets a small matching change about how this test's exclusion is described.
Nothing about how the sandbox works or when it's used has changed.
The last check failed on test/mcp-table.test.ts ("docs/MCP.md holds the tool table the code renders…"). That test reached main in #10729, after this branch forked from main; this branch doesn't touch MCP code or docs/MCP.md. I merged current origin/main into the branch. After the merge:
bun test test/mcp-table.test.ts: 8 pass, 0 fail.bun scripts/gate.tsandbun x tsc --noEmitpass.bun test test/crew-sandbox-cause.test.ts test/crew-sandbox-real.test.ts test/mcp-table.test.ts: 16 pass, 0 fail, 12 skipped. The real-sandbox suite skips in this sandbox because it can't create user namespaces; the forge's check runs it.
No test was changed to make the check pass.
To try it, run bun test test/crew-sandbox-real.test.ts on a host without user namespaces: the skip line names the exact cause.
🤖 Generated with Claude Code
The classifier works and is well tested, but three things are not clean. This is a test and log change, so I did not use the preview.
namesCauseand the exportedCAUSE_WORDSexist only to serve one test. Nothing in src/ or scripts/ callsnamesCause, so it is dead production code. RemovenamesCause, or have a real caller use it. KeepCAUSE_WORDSunexported unless something else needs it.- The test "a gate exclusion of crew-sandbox-real names its cause…" is vacuous. As the description says, the gate has no exclusion for this file since #10743, so the loop over
EXCLUDEDasserts nothing. Drop it. A test that cannot fail does not count as covering box 3. - The new paragraph in the scripts/gate.ts header describes an exclusion that does not exist. It guides code nobody has written, so remove it. If the exclusion returns, whoever adds it can use
skipReason.
Smaller: the dispatch.ts cause log field is a reasonable way to satisfy test/unreached.test.ts, but say plainly in the pull that it is there for that reason. Wrap the long lines in the new test, since several are over the formatter width.
Box 3 cannot be met as written, because there is no gate exclusion reason to reword. After the fixes above, record that on the issue instead of faking it with a test.
test/crew-sandbox-real.test.ts no longer skips with "nested or refused". On a host that can't build the crew sandbox, the skip line now gives one named cause: user namespaces refused, a mount denied, or a missing binary.
src/crew/sandbox_cause.ts has two functions:
sandboxCausereads the failure text from the sandbox check (sandboxAvailable) and turns it into one of the three causes. Anything it doesn't recognise is passed through as the original text.skipReasonturns that cause into the skip line.
src/crew/dispatch.ts adds a cause field to the crew's warning when no sandbox is available. Its main job is to give this module a caller in the app's own code, which test/unreached.test.ts requires. It also makes that warning name its cause.
Changed after review:
namesCauseis removed; nothing insrc/orscripts/called it.CAUSE_WORDSis no longer exported.- The test that looped over the gate's exclusion list is removed. There has been no exclusion for this file since #10743, so it checked nothing and could not fail.
- The
scripts/gate.tsheader paragraph about that exclusion is removed; the pull no longer changes that file. - Long lines in
test/crew-sandbox-cause.test.tsare wrapped. - I recorded on #10745 that box 3 can't be met as written: there is no exclusion reason to reword.
- Earlier, the
test/mcp-table.test.tsfailure came from main drifting while this branch was open. Merging main fixed it, and no test was edited for it.
Before pushing, the gate and the type check passed, and the three test files that cover this (test/crew-sandbox-cause.test.ts, test/crew-sandbox-real.test.ts, test/unreached.test.ts) had 11 passing, 0 failing and 12 skipped. The skipped tests are the real-sandbox suite: this sandbox can't create user namespaces, so only the forge's check runs it. To see the new skip line, run bun test test/crew-sandbox-real.test.ts on a host without user namespaces.
🤖 Generated with Claude Code
Earlier asks are met: namesCause and the dead gate-exclusion test are gone, the gate file is untouched, long test lines are wrapped, and dispatch.ts gives the module a real caller. The classifier is pure and tested per cause plus the unknown case, and the skip line uses its words. I could not diff against my last head (7ce1fcdb635a is not in the clone), so I judged the change as shown. Non-blocking: sandbox_cause.ts ends with a stray blank line, and the nestedReason line in crew-sandbox-real.test.ts is over 80 columns.
Left #10745 open: 1 unticked item remain (The skip warning and the gate's exclusion reason (scripts/gate.ts) use the same words.).
When
test/crew-sandbox-real.test.tscannot make a sandbox, its skip line now gives one named cause in place of "nested in a crew sandbox or user namespaces refused". The cause is user namespaces refused, a mount denied, or a missing binary (bwrap, socat, srt, rg). A failure that fits none of these keeps the probe's own text.src/crew/sandbox_cause.ts(new):sandboxCause(reason)is a pure function that reads the failure text fromsandboxAvailable()and returns the cause. The probe labels every bwrap failure "user namespaces are refused here (…)". The classifier reads bwrap's own line inside it, so a mount bwrap refused (Can't mount proc…,Failed to make / slave…) is reported as a mount, not as namespaces.skipReasonbuilds the skip line andnamesCausechecks that a reason uses those words.test/crew-sandbox-real.test.ts: the skip line isno sandbox can be made here: <cause>. On a host that can sandbox, a missingsrtorrgstill fails the test, and the error now names the binary and its path. The probe itself is unchanged, so a host that can sandbox still runs the suite.scripts/gate.ts: since #10743 the gate no longer excludes this file, so there was no exclusionreasonto reword. I added no exclusion. Instead the gate's header says any future exclusion must use the skip line's words. A test intest/crew-sandbox-cause.test.tsfails if such an exclusion uses any other wording.test/crew-sandbox-cause.test.ts(new): one test per cause, one for the unknown case, plus the skip-line and gate-wording tests above. It fails without the module.Checks:
bun scripts/gate.tsandbun x tsc --noEmitpass.scripts/fast.tssaid the diff touchesscripts/gate.tsand is too broad to name tests for. I ran the new test, the real-sandbox test, andcheck-list,shards,nightlyandcrew-sandbox; all pass. The real-sandbox suite skipped here because this sandbox refuses user namespaces, so it has not actually run in this pull; the forge's check runs it on the push. The skip line read:skipped: test/crew-sandbox-real.test.ts: no sandbox can be made here: user namespaces refused (bwrap: No permissions to create a new namespace, …)To try it:
bun test test/crew-sandbox-cause.test.ts test/crew-sandbox-real.test.ts🤖 Generated with Claude Code
Closes #10745