[Feat] Record a death certificate when the OpenCode server exits unexpectedly - #145
Conversation
…pectedly When the OpenCode subprocess dies mid-task today, the worker silently reconnects and every fact about the death vanishes with the sandbox. The worker now stands witness: a bounded ring of the subprocess's last output lines is kept while it runs, and an exit while the task is not being cancelled records a durable diagnostic run event with the exit code, signal, uptime, output tail, and a memory snapshot taken at the instant of death, plus a Sentry capture for alerting. Exit code 137 versus a panic stack versus a clean exit is the difference between "killed for memory", "OpenCode bug", and "something else" — the distinction the last two investigations could not make. Collection cost is constant per output line, certification runs only on unexpected exits, and the callback is observer-only: its failures are logged and never touch the harness lifecycle. Output lines pass through the diagnostic recorder's secret scrubbing before persisting. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Reviewed the change. 2 findings to consider (both non-blocking; finding 1 is partly acknowledged in the PR discussion). See task
Review findings
1. Reconnect/restart terminations produce false certificates and Sentry noise. 2. Memory snapshot is taken after death, not at it. |
|
Carrying over the known-noise caveat from #141: deliberate harness restarts (e.g. the MCP-reconnect respawn path) terminate opencode without the task-cancel signal, so they will be certified as SIGTERM exits. The killer signatures that matter (exit 137 / SIGKILL / panic stacks) are unambiguous; a follow-up can thread an expected-restart flag to silence the SIGTERM entries. |
…icate (#147) * [Fix] Address review findings on the diagnostic rail and death certificate Review follow-up for #140 and #145, which merged before their bot reviews were read: - Deliberate harness terminations (reconnect, restart, teardown) are no longer certified as unexpected exits: ReconnectableHarness marks the subprocess before killing it, and certification skips marked exits, which also silences the Sentry alert that fired on every routine reconnect. - The death tail no longer depends on the startup URL wait leaving its line listeners attached: dedicated readers feed the ring for the whole subprocess lifetime. - The run-events reader returns the newest events under its limit (descending query, reversed to chronological), so a busy task cannot push the diagnostics it exists to surface out of the window. - A caller-supplied details.kind can no longer override the recorder's classification. - Hash-shaped values (git SHAs, digests) keep an 8-character prefix when redacted, preserving their evidentiary value without exposing credential-length material. - The memory reading is named and documented as what it is: the sandbox observed just after the exit, by the surviving worker process — the exit code/signal remains the authoritative OOM signal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Mark expected exits only while the subprocess is alive A crash surfaces as a disconnect first, and the disconnect cleanup then kills the already-dead process; marking it there raced the exit certification and could suppress the certificate for exactly the crash the rail exists to record. The mark now applies only to a process that is still alive at kill time, so deliberate teardowns stay quiet and self-inflicted deaths stay certified. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
PR 2 of the observability pair — #140 (the diagnostic rail) is merged; this is the first producer on it. Replaces #141, which GitHub auto-closed when the stacked base branch was deleted.
What
The worker keeps a bounded ring (last 50 lines, 300 chars each) of the OpenCode subprocess's output while it runs. If the process exits while the task is not being cancelled, it records one durable
opencode_unexpected_exitdiagnostic event: exit code, signal, uptime, output tail, and a/proc/meminfo+ worker-RSS snapshot taken at the instant of death — plus a Sentry capture so we get alerted. Reconnect-spawned processes are certified individually.This is the fact that decides the open mystery: exit 137 = killed for memory (the 128 MiB request theory), a panic stack = OpenCode bug, clean exit = something else. The last two investigations couldn't make that distinction because the evidence died with the sandbox.
Guarantees (per the requirements)
Tests
Collector: exit facts + tagged tail, ring/line caps, blank-line and missing-
/procsafety. Full harnesses + run-task suites: 423 tests pass; typecheck / eslint / prettier / knip clean.Not auto-merged — ready for review.
🤖 Generated with Claude Code