Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions PATCHES.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ half is upstreamable, the Argus behavior lives in project config).
| [#167](#patch-167) | The command palette lists the stack's projects and groups that browser extensions contribute (`commandProviders`), and an Apps entry in the sidebar opens it and shows its shortcut | generic-upstreamable (hook) + argus (sidebar entry) | this PR |
| [#168](#patch-168) | Chat header: no token or context pill, Export and Artifacts are icon-only, and the Browser trigger appears only once the agent has browsed in the thread | argus-edit | this PR |
| [#169](#patch-169) | Filter chips on the Chats page (All, Mine, Scheduled, Telegram, In a project, Pinned, Builders, Artifacts); origin chips filter in the infinite-list page loop, flag chips through the search API's exact-match metadata filter | generic-upstreamable (origin and pin chips) + argus (Aeacus, legacy `:sched:` chat ids, Builders/Artifacts flags) | this PR |
| [#170](#patch-170) | Quarantine retirement of an open-mode sandbox (`complete_absent_teardown`) runs the strict restricted-mode teardown only when the sandbox's `deer-flow-netproxy-*` sidecar is actually listed, so the socket proxy's refusal of that fixed name no longer keeps the record and fails every new sandbox in the thread (extends #142 to the absent-sandbox path) | argus-edit | this PR |

Dropped / deferred / not-carried records are at the bottom, followed by the
carry budget ledger.
Expand Down Expand Up @@ -2903,6 +2904,21 @@ carry budget ledger.

---

## Patch #170

**Patch #170 - A quarantined open-mode sandbox is retired without touching a netproxy it never had** (2026-10-28)

- Class: argus-edit (harness; `LocalContainerBackend.complete_absent_teardown`). Extends #142.
- Intent: after an uncertain shell outcome the provider recycles the sandbox and keeps its quarantine record (`sandbox-quarantine/`) until a later create proves the container absent: `_create_sandbox[_async]` -> `_retire_absent_quarantine` -> `_complete_quarantined_teardown` -> `complete_absent_teardown`. That last step retried the restricted-mode teardown strictly in every mode (`docker stop` and `docker rm -f` of `deer-flow-netproxy-<digest>`, then two `docker network rm`). The per-stack socket proxy refuses those names (`sockproxy: containers.stop: deer-flow-netproxy-... is not one of this stack's sandboxes`), so the strict cleanup raised, the record stayed and every new sandbox in that thread failed until the user moved to another thread. #142 fixed the recycle's own destroy by reading the sandbox's labels, but here the container is gone, and #142's predicate keeps the cleanup for an absent sandbox. Seen after #142 on atlas-nicholas 2026-10-26 11:02-11:04 CET (18 refusals) and atlas-daeun 2026-10-27 09:36-10:04 CET (19 refusals, thread 9a38e323, sandbox 2d29ba9b54e63aeb); the recycle itself stopped the container cleanly both times. `_absent_sandbox_may_hold_restricted_resources` now decides: a restricted-mode process keeps the strict cleanup unchanged. An open-mode process 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>`; the socket proxy allows the list and filters it to the stack's prefix) and runs the strict cleanup only when that exact name is listed, i.e. a sidecar left by an earlier restricted-mode process. A failed listing (non-zero exit, timeout, missing CLI) keeps the cleanup. Each skip logs one info line, "Retiring open-mode sandbox <id> without restricted cleanup".
- Records written before this patch: the record carries no mode and the decision reads only the process mode and the listing, so the next create in that thread retires an existing record by itself. No manual clearing is needed after rollout.
- Limit: in an open-mode process, networks left by an earlier restricted-mode process whose sidecar is already gone are no longer retried here (a network listing is not on the socket proxy's allowlist, so there is nothing to ask). Only a restricted-to-open configuration switch with a partly failed teardown leaves them, and they do not affect open-mode creation.
- Files: `backend/packages/harness/deerflow/community/aio_sandbox/local_backend.py`.
- Tests: `backend/tests/test_aio_sandbox_quarantine_open_mode_retire.py` (the daeun sidecar name; open-mode absent teardown issues only the listing, no refused call, and releases its port; a longer or prefixed lookalike name does not count; restricted mode stays strict and lists nothing; an exactly listed sidecar stays strict behind the proxy and is removed on a raw engine; a failed listing keeps the cleanup; through the provider, the next create retires an open-mode record behind the proxy, a still-running open-labelled container is stopped and its record retired in the same call, and a restricted-mode record is kept when its sidecar stop is refused). The six open-mode cases fail on the base. `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` unchanged and green.
- Delete-when: as #142 (upstream records the provisioned mode where an absent sandbox can still be judged, or treats a refused sidecar teardown as absent for open-provisioned sandboxes).
- Upstream status: generic-upstreamable together with #142.

---

## Dropped / deferred / re-expressed (v2.0.0 rebase record - do not re-add blindly)

**Dropped at the 2026-10-25 upstream sync (base `65a18618` -> `66f00e7b8`):**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1418,6 +1418,42 @@ def _may_hold_restricted_resources(self, info: SandboxInfo) -> bool:
return True
return inspection is None or self._persisted_sandbox_mode(inspection, info.sandbox_id) != "open"

def _absent_sandbox_may_hold_restricted_resources(self, sandbox_id: str) -> bool:
"""[argus] patch #170: #142's question for a sandbox that is already gone.

``complete_absent_teardown`` runs only after the sandbox container is
confirmed absent, so the labels #142 reads no longer exist. A
restricted-mode process keeps the strict upstream cleanup. An open-mode
process never creates a sidecar or networks and its create path never
consults them, so only a sidecar left by an earlier restricted-mode
process needs the strict teardown, and a read-only listing of its fixed
name finds one. Without this, a per-stack engine proxy that admits only
the stack's own sandbox names refused the ``deer-flow-netproxy-*``
stop, quarantine retirement aborted, and every new sandbox for that
thread failed. A listing that fails keeps the cleanup.
"""
if self._network_mode != "open":
return True
proxy_name, _ = self._resource_names(sandbox_id)
try:
result = subprocess.run(
["docker", "ps", "-a", "--filter", f"name={proxy_name}", "--format", "{{.Names}}"],
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=15,
)
except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
return True
if result.returncode != 0:
return True
# ``--filter name=`` is a substring match; only the exact name counts.
if any(name.strip().lstrip("/") == proxy_name for name in (result.stdout or "").splitlines()):
return True
logger.info("Retiring open-mode sandbox %s without restricted cleanup: no network proxy %s is listed", sandbox_id, proxy_name)
return False

def _forget_unknown_cleanup_port(self, sandbox_id: str, port: int) -> None:
"""Remove a released reservation; caller holds the cleanup ledger lock."""
pending = self._unknown_cleanup_ports.get(sandbox_id)
Expand All @@ -1433,12 +1469,13 @@ def complete_absent_teardown(self, sandbox_id: str) -> None:
only after confirming absence under both teardown fences, before it
retires quarantine records. Metadata inspection stays read-only.
"""
if self._runtime == "docker":
if self._runtime == "docker" and self._absent_sandbox_may_hold_restricted_resources(sandbox_id):
# A timeout can remove the main container before its deterministic
# sidecar/networks are cleaned. Retry even after a process restart,
# where the local reservation ledger is empty. Unknown cleanup
# failures must retain quarantine instead of stranding new creation
# behind the existing-resource mismatch guard.
# behind the existing-resource mismatch guard. [argus] patch #170:
# an open-mode process skips it when no sidecar is listed.
self._cleanup_restricted_resources(sandbox_id, stop_sandbox=False, strict=True)
with self._pending_cleanup_ports_lock:
ports = {port for owner_id, port in self._pending_cleanup_ports.values() if owner_id == sandbox_id}
Expand Down
Loading
Loading