Observation
The settle loop's "two identical captures span the quiet window" decision rides a 0ms margin by construction for every --settle-quiet value in [25, 300].
runStableCaptureLoop (src/commands/interaction/runtime/stable-capture.ts) derives its cadence from the quiet window:
const pollMs = Math.min(STABLE_POLL_INTERVAL_MS, Math.max(25, quietMs)); // 300 / 25 floor
so pollMs === quietMs for any quietMs in [25, 300]. Settling requires:
} else if (captures >= 2 && nowMs - quietSinceMs >= quietMs) {
quietSinceMs is stamped right after capture #1, so the gap measured at capture #2 is exactly one sleep(pollMs) plus the capture's own duration. With pollMs === quietMs that comparison is "roughly-quietMs >= quietMs" — decided by sub-millisecond noise.
Why it flips
sleep() falls back to setTimeout and now() to Date.now() (src/commands/runtime-common.ts:16-23). These read different clocks: libuv's timer fires off cached monotonic loop time, Date.now() is the wall clock. Measured on darwin/Node 26, 4000 samples of await setTimeout(25):
| condition |
Date.now() delta of 24ms |
| idle |
0.13% of calls |
| under CPU load |
0.63% of calls (5x) |
Instrumenting the loop with settleQuietMs: 25, sinceQuiet lands at 25, 26, 27, 28, 29 against a threshold of 25 — repeatedly exactly on the boundary.
Impact
Not a correctness bug. When the comparison loses, quietSinceMs is not reset (the tree is unchanged), so capture #3 sees ~2x the gap and settles. The cost is one wasted capture + poll on ~0.13–0.63% of settles in that quiet-window range. The default (quietMs: 500 → pollMs: 300) settles at capture 3 with a 100ms margin and is unaffected.
Proposed fix
Make the sleep deadline-aware rather than a fixed pollMs: sleep the remaining time to the quiet deadline (plus a ~1-2ms epsilon) instead of exactly pollMs. Capture #2 then lands deterministically past the window, settle costs 2 captures instead of an occasional 3, and the skew sensitivity disappears at the source.
Provenance
Surfaced while diagnosing the settle-observation.test.ts CI flake (settled: false on loaded runners, passing on rerun). That test failed because it scripted an exact capture count, turning the benign extra poll into a swallowed transcript exhaustion; the test fix is separate and touches no production code. Confirmed by forcing the undershoot (sleep(pollMs - 6)): the old fixture failed 58/60, matching CI's signature exactly.
This issue is the production half, filed separately because it changes production timing.
Observation
The settle loop's "two identical captures span the quiet window" decision rides a 0ms margin by construction for every
--settle-quietvalue in [25, 300].runStableCaptureLoop(src/commands/interaction/runtime/stable-capture.ts) derives its cadence from the quiet window:so
pollMs === quietMsfor anyquietMsin [25, 300]. Settling requires:quietSinceMsis stamped right after capture #1, so the gap measured at capture #2 is exactly onesleep(pollMs)plus the capture's own duration. WithpollMs === quietMsthat comparison is "roughly-quietMs >= quietMs" — decided by sub-millisecond noise.Why it flips
sleep()falls back tosetTimeoutandnow()toDate.now()(src/commands/runtime-common.ts:16-23). These read different clocks: libuv's timer fires off cached monotonic loop time,Date.now()is the wall clock. Measured on darwin/Node 26, 4000 samples ofawait setTimeout(25):Date.now()delta of 24msInstrumenting the loop with
settleQuietMs: 25,sinceQuietlands at 25, 26, 27, 28, 29 against a threshold of 25 — repeatedly exactly on the boundary.Impact
Not a correctness bug. When the comparison loses,
quietSinceMsis not reset (the tree is unchanged), so capture #3 sees ~2x the gap and settles. The cost is one wasted capture + poll on ~0.13–0.63% of settles in that quiet-window range. The default (quietMs: 500→pollMs: 300) settles at capture 3 with a 100ms margin and is unaffected.Proposed fix
Make the sleep deadline-aware rather than a fixed
pollMs: sleep the remaining time to the quiet deadline (plus a ~1-2ms epsilon) instead of exactlypollMs. Capture #2 then lands deterministically past the window, settle costs 2 captures instead of an occasional 3, and the skew sensitivity disappears at the source.Provenance
Surfaced while diagnosing the
settle-observation.test.tsCI flake (settled: falseon loaded runners, passing on rerun). That test failed because it scripted an exact capture count, turning the benign extra poll into a swallowed transcript exhaustion; the test fix is separate and touches no production code. Confirmed by forcing the undershoot (sleep(pollMs - 6)): the old fixture failed 58/60, matching CI's signature exactly.This issue is the production half, filed separately because it changes production timing.