Skip to content

ops(dellsrv): control experiment for the exec EAGAIN outage - #21

Draft
zenprocess wants to merge 2 commits into
mainfrom
fabro-71/exec-eagain-control
Draft

ops(dellsrv): control experiment for the exec EAGAIN outage#21
zenprocess wants to merge 2 commits into
mainfrom
fabro-71/exec-eagain-control

Conversation

@zenprocess

Copy link
Copy Markdown
Owner

Context

Active outage since 2026-07-25T23:44Z: POST /v1/sandboxes/{id}/exec returns
HTTP 500 "exec: read response: Resource temporarily unavailable" (EAGAIN) on
every gate run for the affected repo. 182 occurrences, 218 INFRA / 10 FAIL /
zero PASS. sandbox-create still succeeds — only the exec response read fails.

What this adds

exec-eagain-control.sh — the control experiment that splits the hypothesis
space before anyone reaches for a fix:

  • EAGAIN on a trivial echo hi → deterministic/global fault, workload
    irrelevant, retry/backoff is the right fix.
  • PASS on echo hi → workload-dependent, sizing/config is the right fix.

Times the exec call: sub-second failure implies a non-blocking fd with no
poll/retry loop; ~120s implies a proxy timeout.

--diagnose adds a strictly read-only diagnostic capture (controller/proxy
state, /v1/snapshots for restart detection, orphan sandboxes, fd/proc limits,
dmesg, disk/memory, per-child netns) so the operator gets everything in one pass.

A specific lead this investigation surfaced

~/fabro-run/forkd-shim.py (the reverse proxy in front of the controller)
gives sandbox-create extra attempts on a known transient-restore signature.
Every other request shape — including exec, the call that's failing — gets
exactly one attempt, by explicit design (max_attempts logic in that file).
This script's result determines whether extending retries to exec is the right
fix.

Verification

bash -n scripts/ops/dellsrv-forkd-supervision/exec-eagain-control.sh   # OK
shellcheck -S warning ...exec-eagain-control.sh                        # clean

Read-only proof: negated grep for mutating verbs (systemctl start/stop/restart/
enable/disable, docker restart/rm/stop, forkd snapshot registration), excluding
comment lines — finds nothing in the real script. Verified the guard actually
bites by injecting docker restart forkd into a scratch copy and confirming the
same check fails there.

EXIT-trap-fires-on-failure verified by simulating the same trap/SANDBOX_ID
pattern under a forced non-zero exit — teardown runs before the process exits,
not just on the success path.

Not in scope

This is a diagnostic, not a fix. No live dellsrv execution — I don't have host
access (sandbox blocks dellsrv.zp.digital). An operator runs this on the gate
host; the result determines the next PR.

🤖 Generated with Claude Code

Referee and others added 2 commits July 28, 2026 20:07
POST /v1/sandboxes/{id}/exec has been returning HTTP 500 "Resource
temporarily unavailable" (EAGAIN) on every uniforme gate run since
2026-07-25T23:44Z (182 occurrences, zero PASS). sandbox-create still
succeeds -- only the exec response read fails.

exec-eagain-control.sh runs one trivial `echo hi` exec against the
gate's actual snapshot_tag to split the hypothesis space: EAGAIN on a
trivial command means the fault is deterministic/global (a code
defect, not a workload issue); a PASS keeps the workload-dependent
theories alive. Also times the call -- sub-second failure implies a
non-blocking fd with no retry loop, ~120s implies a proxy timeout.

Flags a specific lead found while investigating: ~/fabro-run/forkd-shim.py
(the reverse proxy in front of the controller) gives sandbox-create
extra attempts on a known transient-restore signature, but every other
request shape -- including exec, the call that is failing -- gets
exactly one attempt by explicit design (see that file's `max_attempts`
logic). This script's result determines whether extending retries to
exec is the right fix or a wasted one.

--diagnose adds a strictly read-only one-pass capture (controller/proxy
state, restart detection via /v1/snapshots, orphan sandboxes, fd/proc
limits, dmesg OOM signatures, disk/memory, per-child netns) so the
operator does not need a round trip per command on the host.

Reuses gate-health-probe.sh's conventions in this directory: token
never leaves the forkd container (docker exec heredoc), sandbox id
scraped before any die() so the EXIT trap can always tear down, same
scrape_field jq/regex-fallback contract.

Read-only proof: no systemctl start/stop/restart/enable/disable, no
docker restart/rm/stop, no forkd snapshot registration anywhere in the
script (verified by a negated grep excluding comment lines; the guard
was checked against a scratch copy with an injected `docker restart`
line to confirm it actually fails before trusting it against the real
file). EXIT-trap-fires-on-failure verified by simulating the same
trap/SANDBOX_ID pattern under a forced non-zero exit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… before host restart

Drain-and-pause ahead of an operator host restart + AO update. No workers
were running (all five dispatched this session were killed earlier after
the MiniMax/qwen/cclocal routes all failed); this commit preserves the
orchestrator's own artifacts so nothing is stranded in the worktree.

Contents:
  STATE.md                        - full session ledger: the EAGAIN ->
                                    restore_many investigation, four
                                    superseded analyses with their
                                    refutations kept visible, and the
                                    morning-digest asks.
  DELLSRV-COMPLETION-RUNBOOK.md   - operator runbook. PART 00 is the
                                    executable list (read-only forensics
                                    first, mutating steps gated on them);
                                    PART 0 the outage; PART A/B the
                                    original completion mandate.
  .briefs/                        - the four worker task briefs authored
                                    this session (T1-T4), reusable if the
                                    dispatch routes recover.
  .salvage/fabro-74-referee/      - partial referee run-registration work
                                    (register.rs + Cargo.toml + diff)
                                    salvaged before killing fabro-74, so a
                                    future worker does not restart from zero.
  HANDOFF-...-fabro-67.md         - inherited predecessor context.

Deliberately NOT included: four modified .claude/ agent-config files that
were already dirty on entry and are not this session's work.

Nothing here is a code change to fabro itself. The one code deliverable
this session is PR #21 (exec-eagain-control.sh), already pushed and open
as a draft.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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