T1 — zcode-protocol: release the AbortSignal listener on request timeout
Parent: spec #26 (.scratch/pr139-review/spec.md)
Status: ready-for-agent
Blocked by: None — can start immediately.
What to build
The per-request timeout path in the ZCode app-server protocol client must
release its AbortSignal listener when it fires, so a long-lived AbortController
does not accumulate listeners across timed-out RPCs and a late abort after a
timeout is a silent no-op. Today the timeout callback does pending.delete +
reject but skips the cleanup() that the success/error/abort paths all call —
so the listener stays attached until the signal is GC'd. The fix makes the
timeout path match the others and match the callback's own doc-comment (which
already says cleanup "runs on response/timeout/close").
Acceptance criteria
Blocked by
- None — can start immediately.
T1 — zcode-protocol: release the AbortSignal listener on request timeout
Parent: spec #26 (
.scratch/pr139-review/spec.md)Status: ready-for-agent
Blocked by: None — can start immediately.
What to build
The per-request timeout path in the ZCode app-server protocol client must
release its AbortSignal listener when it fires, so a long-lived
AbortControllerdoes not accumulate listeners across timed-out RPCs and a late abort after a
timeout is a silent no-op. Today the timeout callback does
pending.delete+rejectbut skips thecleanup()that the success/error/abort paths all call —so the listener stays attached until the signal is GC'd. The fix makes the
timeout path match the others and match the callback's own doc-comment (which
already says cleanup "runs on response/timeout/close").
Acceptance criteria
cleanup()(the existing helper thatcalls
signal.removeEventListener("abort", onAbort)) beforepending.delete+reject.request()withan
AbortSignaland fake timers; after the timeout fires,signal.removeEventListenerwas called with"abort", and a latecontroller.abort()does not throw or double-reject.rejection — attach a
.catchup front, mirroring the sibling "rejectsafter timeoutMs" test, so the timeout rejection is never briefly unhandled).
Blocked by