Skip to content

[argus patch #170] Retire a quarantined open-mode sandbox without touching a netproxy it never had - #129

Merged
Nichol4s merged 1 commit into
argusfrom
argus-quarantine-retire-open-mode
Oct 28, 2026
Merged

Nichol4s merged 1 commit into
argusfrom
argus-quarantine-retire-open-mode

Conversation

@Nichol4s

Copy link
Copy Markdown

Patch bytedance#170, argus-edit, no switch. Extends bytedance#142 to the quarantine-retirement path, which bytedance#142 missed.

What broke

After an uncertain shell outcome the provider recycles the sandbox and keeps its quarantine record until a later create proves the container absent: _create_sandbox[_async] -> _retire_absent_quarantine -> _complete_quarantined_teardown -> LocalContainerBackend.complete_absent_teardown. That last step retried the restricted-mode teardown strictly in every mode, starting with docker stop deer-flow-netproxy-<digest>. The per-stack socket proxy refuses names outside the stack's prefix:

Failed to stop container deer-flow-netproxy-c1a44b2434411cc2: Error response from daemon: sockproxy: containers.stop: deer-flow-netproxy-c1a44b2434411cc2 is not one of this stack's sandboxes

The strict cleanup raised, the record stayed, and every new sandbox in that thread failed until the user moved to another thread. Gateway journal: atlas-nicholas 2026-10-26 11:02-11:04 CET (18 refusals), atlas-daeun 2026-10-27 09:36-10:04 CET (19 refusals, thread 9a38e323, sandbox 2d29ba9b54e63aeb). In both cases the recycle itself stopped the container cleanly, so bytedance#142 was doing its job on the destroy path.

bytedance#142's predicate (_may_hold_restricted_resources) cannot be reused as is: it reads the sandbox container's labels, and on this path the container is already gone, so it answers "keep the cleanup".

What changes

complete_absent_teardown asks _absent_sandbox_may_hold_restricted_resources(sandbox_id) first:

  • restricted-mode process: unchanged strict cleanup;
  • open-mode process: it never creates a sidecar or networks and its create path never consults them, so it lists the sidecar's fixed name (docker ps -a --filter name=<proxy> --format {{.Names}}). The socket proxy allows this list and filters the response to the stack's prefix. The strict cleanup runs only when that exact name is listed (a sidecar left by an earlier restricted-mode process); a lookalike longer name does not count;
  • a failed listing (non-zero exit, timeout, missing CLI, OS error) keeps the strict cleanup.

Each skip logs one info line: Retiring open-mode sandbox <id> without restricted cleanup: no network proxy <name> is listed.

Verified on the host through atlas-daeun's own socket proxy: docker ps -a --filter name=deer-flow-netproxy-c1a44b2434411cc2 --format {{.Names}} exits 0 with empty output, so it is not refused.

Known limit, written into the ledger: an open-mode process no longer retries networks left by an earlier restricted-mode process whose sidecar is already gone. A network listing is not on the proxy's allowlist, so there is nothing to ask. Only a restricted-to-open config switch with a partly failed teardown leaves such networks, and they do not affect open-mode creation.

Records left before this fix

They are retired automatically. The record is an empty file per generation and carries no mode. The new decision reads only the process mode and the listing, so the next create in that thread retires the record. On 2026-10-28 atlas-daeun and atlas-nicholas each hold one.

Tests

New backend/tests/test_aio_sandbox_quarantine_open_mode_retire.py (17), with an engine fake that answers like the socket proxy (inspect gives the bytedance#154 GET /info denial plus "no such object", list is allowed and filtered, every deer-flow-* stop/rm/network rm is refused):

Red on the base: the 6 open-mode cases fail without the code change; the strict-mode cases pass both ways, as intended.

Locally: the new file, test_aio_sandbox_local_backend.py, test_aio_sandbox_sockproxy_info_probe.py, test_aio_sandbox_provider.py and test_quarantine_retire_race_regression.py (552 passed, 3 live deselected). ruff check and ruff format --check are clean, and check_patches_ledger.py passes.

Ledger

PATCHES.md row and a ## Patch #170 section. No config keys, no migrations. PATCHES.md will conflict textually with other open fork PRs that append rows.

Not deployed, not merged, no pin bump.

🤖 Generated with Claude Code

…thout touching a netproxy it never had

complete_absent_teardown retried the restricted-mode teardown strictly in
every mode, so the socket proxy's refusal of the fixed
deer-flow-netproxy-<digest> name kept the quarantine record and every new
sandbox in that thread failed (atlas-nicholas 2026-10-26, atlas-daeun
2026-10-27). Patch bytedance#142 reads the sandbox's labels, but here the container
is already gone. A restricted-mode process keeps the strict cleanup; an
open-mode process runs it only when the sidecar's exact name is listed,
and a failed listing keeps it. Records left before the patch retire on the
next create in their thread.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Nichol4s
Nichol4s merged commit 62dcf39 into argus Oct 28, 2026
19 checks passed
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