You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(daemon): E-lite root-cause telemetry for inactivity_timeout attribution (#5775)
* feat(daemon): E-lite root-cause telemetry for inactivity_timeout attribution
Add four run_finished fields that separate WHY a stalled run died, which
the coarse `last_observed_phase` (WHICH phase) cannot:
- approval_requested: an ACP approval/permission gate fired. Only the ACP
path is daemon-observable (stream/CLI runtimes pass a skip-permissions
flag so no gate fires); surfaced via an acp_approval_request diagnostic
and folded in by summarizeRunDiagnosticsForAnalytics.
- stdin_backpressure: the prompt write to the child's stdin was queued
because the OS pipe buffer was full (the child was not draining stdin) —
the corroborating signal for a stdin_write-phase stall.
- tool_result_sent: a tool_result came back for the run's LAST tool_use.
A stall with tool_call_seen && !tool_result_sent is the
tool-result-not-delivered root cause.
- last_progress_age_ms: age of the last agent activity at finish (the
inactivity-watchdog clock). Near the ceiling on a stall, ~0 on a clean
finish.
Contract fields on RunFinishedProps; daemon populates two off the run
object (stdin_backpressure, last_progress_age_ms) and derives two from
run.events in the diagnostics summarizer (tool_result_sent,
approval_requested).
* fix(daemon): pair tool_result_sent by id so parallel tools attribute correctly
Review catch (nettee/Looper): the first cut flipped `tool_result_sent` to
true on any tool_result after a tool_use, so a multi-tool turn like
tool_use(A), tool_use(B), tool_result(A) reported delivered even though B
was still outstanding — bucketing multi-tool stalls as "provider stalled
after delivery" instead of "tool result not delivered".
Now paired by id (`tool_use.id` <-> `tool_result.toolUseId`, the same
pairing summarizeRunTimingAnalytics uses): `tool_result_sent` is true only
when EVERY committed tool_use received a matching tool_result (outstanding
set empty). Tests cover the reviewer's regression case plus parallel
all-resolved and sequential-last-outstanding.
* fix(daemon): count-pair id-less tool events so degraded stalls aren't "delivered"
Second review catch (nettee/Looper): id-based pairing alone still fell
through to `tool_result_sent: true` when a runtime emits a tool_use with a
null id, because those were counted in `sawAnyToolUse` but never tracked as
outstanding. That is not hypothetical — `agent-protocol/pi-rpc/events.ts`
and `copilot-stream.ts` both emit `toolCallId ?? null` for tool_use.id AND
tool_result.toolUseId on degraded provider events, so an unpaired id-less
tool call would mask exactly the stall this metric attributes.
Id-less tool events are now paired by count alongside the id-keyed set:
`tool_result_sent` requires both an empty outstanding-id set and at least as
many id-less results as id-less uses. Tests cover an unpaired id-less stall
and an id-less pair that did resolve.
* fix(daemon): capture stdin backpressure on the default text-input path
Third review catch (nettee/Looper): `stdin_backpressure` never recorded
anything for non-`stream-json` runtimes. `child.stdin.end(composed, ...)`
returns the stream rather than a boolean, and `writableNeedDrain` is already
back to false by the time it returns — even for a chunk that `write(chunk)`
would have rejected. Claude is the only runtime on the stream-json path, so
the field was permanently false on exactly the runs whose `stdin_write`
stalls it exists to attribute.
The write and the close are now issued separately through
`writePromptAndEndStdin`, which returns the boolean the OS pipe actually
gave us. Extracted into chat-run-lifecycle.ts so it is unit-testable:
tests cover the backpressured write, the accepted write, and the flush
callback still firing (the `stdin_write_end` lifecycle mark). Verified red
against the previous behavior.
0 commit comments