fix: bypass DooD socat proxy so /afk agent turns stream#71
Merged
Conversation
The docker-outside-of-docker feature fronts the host socket with a socat proxy (for permissions). socat tears down `docker exec`'s hijacked bidirectional stream after the first data burst, and sandcastle streams the agent's stream-json over `docker exec` — so the agent's init line arrives, the rest of the turn is dropped, and every iteration ends as an empty "started → stopped" turn with zero commits (the exec close handler's `code ?? 0` even masks it as a clean exit). Point the orchestrator's docker CLI at the real host socket (docker-host.sock, which the feature also exposes and grants docker-group access to) via a guarded resolveDockerHost() called first thing in main(). It is a no-op when socat isn't in play — a bare `docker compose up` has no docker-host.sock and its raw socket streams natively — or when an explicit DOCKER_HOST is already set. Pure decider with unit tests.
9 tasks
lsfera
added a commit
that referenced
this pull request
Jun 28, 2026
) Switch SandboxRunner from run() to createSandbox + await using + hooks.onSandboxReady + promptFile/promptArgs so the library owns worktree creation and deterministic teardown instead of the adapter. - SandboxRunner.runIssue uses `await using sandbox = await createSandbox()` then `sandbox.run()` with promptFile/promptArgs; scope-bound disposal replaces bespoke try/finally teardown - buildAgentInput extended to accept IssueInput; returns the full config for both createSandbox() and sandbox.run() (agent, imageName, network, copyToWorktree, onSandboxReady, promptFile, promptArgs) - In-sandbox setup (opencode.json relocation) moves to hooks.onSandboxReady - Agent prompts move to prompt-claude.md / prompt-local.md templates with {{ISSUE_NUMBER}}/{{ISSUE_TITLE}}/{{ISSUE_BODY}} substitution via promptArgs - buildPrompt/buildLocalPrompt string builders removed - 6 new unit tests covering promptFile, promptArgs, and network forwarding - ADR-0019 records the two asymmetries that stay imperative (MTU creation, socat host override) and their relation to ADR-0006/0013 and #71
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.
Problem
Every
/afkclaude-tier iteration came back as an empty "Agent started → Agent stopped" turn with zero commits, regardless of issue, image, or claude-code version (2.1.181/193/195 all failed identically).Root cause
The docker-outside-of-docker devcontainer feature fronts the host socket with a socat proxy (to fix permissions):
socat tears down
docker exec's hijacked bidirectional stream after the first data burst. sandcastle streams the agent's stream-json overdocker exec, so the agent'ssystem/initline arrives, then everything after — the actual turn — is dropped. The exec close handler'sexitCode: code ?? 0masks the severed stream as a clean exit, so it surfaces as a silent empty turn.Evidence
docker execfrom the hostecho a; sleep 1; echo b; sleep 1; echo cvia socat socketadocker-host.sock(bypass socat)a b cFix
A guarded
resolveDockerHost()called first thing inmain()points the orchestrator's docker CLI at the real socket (docker-host.sock, which the feature also exposes and grants docker-group access to):docker compose up(the cockpit distribution path) has nodocker-host.sock; its raw/var/run/docker.sockstreams natively and is left untouched. (A staticdocker-compose.ymlenv can't express this condition — the image has no entrypoint, sodocker-host.sockonly exists under the devcontainer lifecycle — which is why the fix lives in TypeScript, not compose.)DOCKER_HOSTis already set — explicit choice wins.process.env.Verification
With the fix live, two full autonomous
/afkcycles implemented and merged real PRs (#69 cockpit Slice A, #70 Slice B) —commits=1 completed=trueeach. Suite: 131 pass, typecheck clean.🤖 Generated with Claude Code