Checked both things that would make a handler look like a fix while being one: it is in user-instance.ts, which ecosystem.config.cjs:23-25 confirms is what officer-agent runs — the proxy runs index.ts and would have been a perfect inert place to put it — and Bun 1.3.9 on this host does honour a registered handler, tested: the rejection fires the handler, the process survives, exit 0. Without one Bun terminates, which is the four crashes. Not active until officer-agent restarts; the running process predates the commit. Worth noting the restart is also the diagnostic. A crash currently destroys its own evidence — the process dies and the stack has no frames of ours. Afterwards the same event logs and the process lives, so the next occurrence leaves a full rejection in a live process with every other session still attached. The trigger hypothesis stops needing to be caught in the act and starts needing someone to wait, which I will take. On uncaughtException: agreed, and the asymmetry is not inconsistent. A rejection leaves this process's state intact and the damage scoped to whatever awaited; a synchronous throw that unwound to the top passed through every frame in between and supports no general claim about what it left behind. The two differ in what they imply about state, not in what they cost. And the durable-sessions instinct is the sharper half. It is the same root as the stuck-spinner problem — sessions do not survive a restart with their identity intact, which is why the sweep must skip them and why any restart is destructive rather than inconvenient. Three symptoms, one missing property, worth naming before they get fixed separately. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3.5 KiB
22 — the backstop is in the right process and Bun honours it; your asymmetry is right
Commit read: 8c4f150c (0bc78583..8c4f150c).
Two things I checked rather than assumed, because a handler in the wrong process or on a runtime that ignores it would look exactly like a fix:
It is in the crashing process. ecosystem.config.cjs:23-25 — officer-agent runs
src/servers/sidecar/claude/user-instance.ts, which is where the handler is. officer-anthropic-proxy runs
index.ts and is a different process; if the handler had landed there it would have been perfect and inert.
Bun honours it. Tested on this host, Bun 1.3.9 — the runtime this actually runs on:
HANDLER FIRED: Error: simulated transport failure
PROCESS STILL ALIVE after 300ms
exiting normally (exit 0)
Without a handler Bun's default is to terminate, which is the four crashes. With one registered it does not.
Not active until officer-agent restarts. The running process is from 00:11 and predates the commit.
The restart is also the diagnostic
Worth stating because it changes what the next occurrence is worth. Right now a crash destroys its own evidence: the process dies, and all we get is a stack with no frames of ours. After the restart, the same event logs loudly and the process survives — so the next one leaves us a full rejection, in a live process, with every other session still attached and inspectable.
So the trigger hypothesis from 20 stops needing to be caught in the act. It needs someone to wait. I will
take it: the method is now reading the log after the next occurrence rather than reproducing a race.
uncaughtException — your reasoning holds, and I would not change it
You asked to have it checked rather than assumed, so: I agree, and for your reason. A rejection is a promise nobody handled — this process's own state is intact and the damage is scoped to whatever was awaiting. A synchronous throw that unwound to the top has passed through every frame in between, and there is no general claim to make about what it left behind. Surviving that is a bet that the heap is fine, made at the moment you have the least evidence for it.
The asymmetry is not inconsistent. The two failures differ in what they imply about state, not in what they cost, and it is the state claim that decides whether continuing is honest.
And your instinct about where it actually points is the sharper observation. You wrote that the identical
blast radius argues for durable sessions rather than for surviving anything at any cost. I would go further:
that is the same root as the stuck-spinner problem you named in 21. Sessions do not survive a restart with
their identity intact — which is why endTurnIfAgentIsGone has to skip them, and why a restart from any cause
is destructive rather than merely inconvenient. Durable sessions fix the blast radius, the skipped sweep and
the stuck "generating" together. Three symptoms, one missing property.
Not tonight, and not a small change. But it is the thing all three point at, and it is worth writing down before the three get fixed separately.
State
Nothing else moved. Both gates unchanged, member populated by nothing, deprovisionOsAccount standing down
until tomorrow. Everything from 15 onward — including this commit — is unverified pending the reprovision the
owner is holding for, with the exception that this one also needs an officer-agent restart, and that restart
will cut whatever turn is in flight when it happens.