`bash -c` does not bind to the token that follows it: short-option parsing
continues and the command string is the first operand, so `bash -c -l <cmd>`
runs `<cmd>` as a login shell. brush's clap CLI instead binds `-c` to the next
token and rejects one shaped like an option, so every command the agent CLI
sent as `-c -l <cmd>` inside naros-agent failed with
error: a value is required for '-c <COMMAND>' but none was supplied
Since narOS diverts /bin/sh, /bin/bash and /bin/dash to nash, that broke every
command in the container. nash's compat fallback did not cover it either: it
read "-l" as the command, which parses fine as bash, so no fallback fired.
Normalize the argv before clap sees it, hoisting short options that sit between
the `-c` group and its command ahead of that group (`-c -l cmd` -> `-l -c cmd`),
including value-taking ones (`-c -o xtrace cmd`, `-c -oxtrace cmd`). Only short
options are hoisted — bash itself rejects long options once short-option parsing
has begun — and anything not positively identified as an option ends the scan,
so a command string is never mistaken for a flag. Teach nash's dash_c_command()
the same rule so the parse-failure fallback checks the real command.
Also repair the observe test harness, which could not run under a normal macOS
TMPDIR: it named its socket directory with `{:?}` of an Instant (~45 chars),
overflowing sun_path, and never cleaned up, so a recycled pid inherited a stale
socket and failed the bind with AlreadyExists. Route every scratch directory
through a helper that keeps names short, falls back to /tmp when the socket path
would not fit, clears stale state, and drops the directory when the sink dies.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The "agent started but produced no output within 60s / stdio transport
stalled" lockup was never in the container stack — it was the approval
server's unix-socket transport. UnixSocketByteConn.close() did
shutdown(2)+close(2), freeing the fd NUMBER while the serve loop could
still dispatch its next blocking read(2) on the stored raw int. The race
arms on every SSE-streamed tools/call (respondStreamingToolCall closes
the connection mid-serve-loop; instant auto-approvals make the window
widest). The straggler read then landed on a RECYCLED descriptor —
another session's MCP socket, a container's stdout vsock channel —
parking a global-queue thread forever on, and stealing bytes from, an
unrelated stream. Zombies accumulated until every session stalled at
once (the 2026-07-14 00:02 wedge: 26 threads parked in receive() with
only 2 live unix connections). Yesterday's container-side fixes were
correct but orthogonal, which is why build 898 still stalled.
Fix: close() now only shuts the socket down (wakes a parked read with
EOF, fails later writes with EPIPE); the descriptor is close(2)d exactly
once, in deinit, and every offloaded read/write block strongly captures
self so the fd number cannot be recycled while any block that could
still pass it to the kernel is in flight. receive() is additionally
cancellation-aware, so stop()'s task cancellation unwedges parked reads
instead of stranding them. Regression test drives ten SSE-close /
fresh-connect cycles over a real AF_UNIX socket.
Also found while validating the earlier patch: ensureInitfs trusted a
bare cached vminit.ext4 forever, so repointing vminitReference at the
custom -nucleic1 image never took effect (containers still boot the
Jun-21 upstream initfs — guest patch #8 never actually deployed). The
cache is now keyed on the reference via a sidecar file, the pull
authenticates against GHCR with the app's GitHub token (the package is
private; anonymous pulls 403), and a failed re-pull falls back to the
existing cache so an offline launch still boots.
Co-Authored-By: Claude Fable 5 <[email protected]>
Overview doc so other agents/humans understand the shared-control-container
isolation work: the failure vectors + fixes (stdio wedge, connection leak,
control-plane HOL, OOM cross-kill, CPU/fork-bomb, per-session memory.max), the
host-vs-guest shipping surfaces, the per-exec cgroup layout + graceful fallback,
and the local vminit-image build/validate workflow + -nucleicN tag invariant.
Cross-linked from the vendored PATCHES.md.
Co-Authored-By: Claude Opus 4.8 <[email protected]>