Skip to content

fix(probe): classify EAGAIN-500 on the gate-health probe's exec round-trip (fabro-71) - #34

Open
zenprocess wants to merge 3 commits into
fabro-71/exec-eagain-controlfrom
fabro-71/exec-eagain-control+probe-exec-roundtrip
Open

fix(probe): classify EAGAIN-500 on the gate-health probe's exec round-trip (fabro-71)#34
zenprocess wants to merge 3 commits into
fabro-71/exec-eagain-controlfrom
fabro-71/exec-eagain-control+probe-exec-roundtrip

Conversation

@zenprocess

Copy link
Copy Markdown
Owner

Closes fabro-sh#122

Closes zenprocess/ao-company#122 ACCEPTANCE item 4 (extend the existing gate-health probe with an exec round-trip so this failure class is caught by monitoring, not by users). Pairs with the ao-company PR stack on ao/fabro-123/snapshot-preflight (PR fabro-sh#145).

Evidence

  • ~/.ao/state/fabro-gate-poll.log: ~91:1 ratio of os error 11 (1092) vs the next-largest class (12, snapshot-restore 400).
  • The leading HYPOTHESIS (controller source not local) is captured in §2 of the new docs/internal/fabro-122-eagain-hypothesis.md.
  • The probe already exercises exec since fec175a65 ops(dellsrv): gate-health probe for per_child_netns=true path (#17); this PR adds the failure-class split monitoring needs.

What this PR does

Adds an EAGAIN-500 classifier to the existing exec round-trip in scripts/ops/dellsrv-forkd-supervision/gate-health-probe.sh:

  1. After the exec step scrapes exit_code, the raw response body is checked for the OS-level EAGAIN signature (os error 11 or Resource temporarily unavailable).
  2. On match, a structured FORKD-GATE-ALERT reason=exec_eagain_500 is emitted BEFORE the existing exec_nonzero_exit marker.
  3. Monitoring can split the failure class from generic exec failures via journalctl -t gate-probe | grep exec_eagain_500.

The change is local + hermetic — no token or argv changes; it reuses the existing in_container_curl, alert, and teardown_sandbox infra. The EXIT trap teardown discipline is preserved (sandbox id scraped before any die()).

Dry-run evidence

$ DRY_RUN=1 bash scripts/ops/dellsrv-forkd-supervision/gate-health-probe.sh
[gate-probe] create: POST /v1/sandboxes tag=zen-gate-base per_child_netns=true
[gate-probe] DRY-RUN: would POST http://127.0.0.1:8891/v1/sandboxes body={"snapshot_tag":"zen-gate-base","per_child_netns":true}
[gate-probe] DRY-RUN: would scrape sandbox id from .[0].id (array-aware), exec /bin/true, then DELETE
[gate-probe] DRY-RUN: skipping all API calls
$ bash -n scripts/ops/dellsrv-forkd-supervision/gate-health-probe.sh && echo OK
OK

In-repo HYPOTHESIS doc (acceptance item 3)

New file docs/internal/fabro-122-eagain-hypothesis.md captures:

  • §1: the verbatim observed signature + reproduction-rate table.
  • §2: leading HYPOTHESIS — non-blocking fd read returns EAGAIN → fatal; fix = retry-with-deadline bounded by FABRO_EXEC_TIMEOUT. MARKED AS HYPOTHESIS with reasons (controller source not local; alternatives not excluded; no live trace).
  • §3: client-side defense in depth (Rust retry already in place; gate-side deferral added by fabro-122; probe-side classification added here).
  • §4: explicit non-goals (no controller edits, no live canary, no new production state).
  • §5: local-only acceptance evidence; live evidence requires operator on dellsrv.

What I could NOT verify live

  • The 5-min launchd-driven poll cycle will not see this branch until it lands in a fabro release that ops installs on dellsrv. The branch is staged for that (the .service/.timer for the probe are part of the parent fabro-71/exec-eagain-control workflow).
  • No live trace of the controller read loop.

Branching / merge order

🤖 Generated with Claude Code

Referee and others added 3 commits August 1, 2026 13:51
Extends the existing gate-health probe to emit a structured
FORKD-GATE-ALERT reason=exec_eagain_500 marker whenever the exec
round-trip response carries the OS-level EAGAIN signature
('os error 11' or 'Resource temporarily unavailable'). The
exec round-trip itself was already in place since fec175a (the
2026-07-22 netns fix); this branch adds the failure-class split
monitoring needs.

WHY: ~91:1 of all observed gate failures (1092 of 1104 in
~/.ao/state/fabro-gate-poll.log) carry this single signature. A
generic exec_nonzero_exit alert cannot distinguish it from a
guest-side failure, so the controller-side fix (retry-with-
deadline on EAGAIN in the read loop, bounded by FABRO_EXEC_TIMEOUT)
has remained unprioritized. The leading HYPOTHESIS — controller
source not local on this box — is captured in
docs/internal/fabro-122-eagain-hypothesis.md §2.

PAIRS WITH: ao-company PR stacking on ao/fabro-123/snapshot-preflight,
which adds the matching gate-side deferral
(FABRO_EAGAIN_MAX_RETRIES budget, EAGAIN-500 retryable
classification in fabro-github-gate.sh cmd_gate). Branched FROM
fabro-71/exec-eagain-control because the probe scripts already
live on that WIP branch.

Co-Authored-By: Claude <noreply@anthropic.com>
Rewrite docs/internal/fabro-122-eagain-hypothesis.md to match the
structure and honesty conventions of the merged design note
(docs/internal/forkd-snapshot-registry.md, revision 2):

- Status header at top (author, related PRs, controller-half scope).
- Section 1 Headline — exec EAGAIN dominates by ~91:1 on the exec
  stage; restore succeeded.
- Section 3 Downstream harm — records the operator quote on uniforme
  PR #1009 'merged 20 minutes after head showed qa-pipeline ERROR',
  marked UNVERIFIED by this worker (quote is from the issue text,
  not independently confirmable from this sandbox).
- Section 4 Leading HYPOTHESIS — explicitly marked HYPOTHESIS, not
  finding; cites the verbatim Go os.Errno(11) wording; lists what
  would promote to confirmed.
- Section 5 Client-side defense in depth — three layers (Rust retry
  already in place on lib/components/fabro-sandbox/src/forkd/mod.rs,
  gate-side deferral via ao-company PR fabro-sh#145, probe-side classifier).
- Section 6 VERIFIED vs UNVERIFIED — explicit split with reasons
  (egress boundary, no GH_TOKEN, controller source not local).
- Section 7 Acceptance evidence — local-only self-test PASS plus
  bash -n probe plus dry-run. Honest 'could not verify' on the
  workspace build step: dependency resolution needs a registry
  fetch that is unreachable from this sandbox (not blocked by my
  edits).
- Section 8 Operator runbook handoff with T3 maint-window discipline,
  pointing at the operator runbook companion on
  ao/fabro-84/forkd-snapshot-registry.

Docs-only commit. Routes through PR #34.

Co-Authored-By: Claude <noreply@anthropic.com>
Adds an explicit STOP callout at the top of doc section 5a (Rust
client retry) saying the existing retry is defense in depth, NOT
the fix. The remaining 1092-occurrence rate is the tail beyond the
client-side envelope; the fix is still the controller-side
retry-with-deadline bounded by FABRO_EXEC_TIMEOUT (sections 4 and
8).

Also rewrites the cargo section with the actual command output:
cargo build -p fabro-sandbox --features forkd fails on daytona-sdk
fetch (workspace dep resolved before -p is honored; not a blocker
on the edits).

Docs-only commit. PR #34 routes this through.

Co-Authored-By: Claude <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