Skip to content

fix(daemon): port upstream's handoff grace window without regressing the consecutive circuit breaker - #46

Open
asachs01 wants to merge 1 commit into
mainfrom
fix/daemon-ctxcircuit-grace-window
Open

fix(daemon): port upstream's handoff grace window without regressing the consecutive circuit breaker#46
asachs01 wants to merge 1 commit into
mainfrom
fix/daemon-ctxcircuit-grace-window

Conversation

@asachs01

Copy link
Copy Markdown
Member

Summary

Implements the reconciliation task from analyst's diagnosis (deliverables/2026-07-28-daemon-circuit-breaker-upstream-reconciliation.md, task_1785200519438), deferred from the 2026-07-21 upstream-divergence brief (task_1783984438986).

Ports upstream's (grandamenium/cortextos) fresh-session handoff grace window additively on top of wyre's Tier-3 context circuit breaker, per the diagnosis's hard constraint: do not adopt upstream's window-based ctxCircuitRestarts: number[] structure for the context breaker — that is literally the pre-PR#27 code, the exact bug class PR#27 fixed (a slow, spaced-out crash loop that never accumulated in a 15min window and never tripped, causing a 7-hour incident).

What's ported

  • handoffGraceMs(runtime) — exported pure function, 10min grace for codex-app-server/opencode runtimes (they can emit a spurious prompt-cache-token spike several minutes after boot), 2min for everything else.
  • ctxSessionStartedAt field — anchored the moment a new session_id is observed.
  • withinHandoffGrace gate applied to Tier 1 (warning) and Tier 2 (handoff) only — the soft, cooperative actions that a transient fresh-boot spike could otherwise mis-trigger into a handoff → restart → fresh-session loop.

What's explicitly NOT touched

  • Tier 3 (deadline-exceeded force-restart) and the hard API-overflow banner check — both safety backstops, must act regardless of session age. Neither is gated by grace.
  • The consecutive Tier-3 circuit breaker itself (consecutiveCtxRestartsWithoutRecovery) — grace only gates whether Tier 1/2 fire in the first place; it does not touch the breaker's counters or reset logic.
  • The hang-detector subsystem — wyre-only, no upstream equivalent, untouched.
  • agent-manager.ts / CrashLoopPauser — confirmed textually identical to upstream in the diagnosis, no changes needed.

Defensive addition

loadCtxCircuit() now logs (does not silently default) if .ctx-circuit.json is ever found in upstream's { restarts: number[] } shape instead of wyre's { consecutiveWithoutRecovery, handoffFires, brokenAt }. Belt-and-suspenders per the diagnosis — no evidence this has happened, cheap to guard against a silent-default failure mode.

Test plan

12 new tests in tests/unit/daemon/context-handoff-grace-window.test.ts:

  • handoffGraceMs per-runtime values (codex-app-server, opencode, claude-code, hermes, undefined)
  • Grace suppresses Tier 1/2 on a fresh session; a later tick after grace expires fires normally
  • Runtime-aware: codex-app-server stays suppressed at 3min where claude-code's 2min grace would already have fired
  • Tier 3 and the hard overflow-banner check both still fire even while within grace
  • Grace-anchoring doesn't touch the Tier-3 breaker's own counter
  • loadCtxCircuit shape-detection log fires on upstream-shaped state, does not false-positive on wyre-shaped state

Ran npx tsc --noEmit clean, and the full tests/unit/daemon/ + related integration suite (458 tests, all passing). Also ran the full npm test suite — 11 pre-existing failing files (dashboard tests, bus-crons.test.ts, hooks.test.ts, the known-flaky fast-checker.test.ts heartbeat-watchdog tests already tracked in task_1785176939193) confirmed identical with this diff stashed out, so unrelated to this change.

🤖 Generated with Claude Code

…egressing the consecutive circuit breaker

Reconciliation task from analyst's diagnosis (deliverables/2026-07-28-daemon-
circuit-breaker-upstream-reconciliation.md, task_1785200519438), deferred from
the 2026-07-21 upstream-divergence brief.

Ports upstream's (grandamenium/cortextos) fresh-session grace window
additively on top of wyre's Tier-3 circuit breaker, per the diagnosis's
explicit hard constraint: do NOT adopt upstream's window-based
`ctxCircuitRestarts: number[]` structure for the context circuit breaker —
that is literally the pre-PR#27 code and the bug class PR#27 fixed (a slow,
spaced-out crash loop that never accumulated in a 15min window and never
tripped).

What's ported: `handoffGraceMs(runtime)` + `ctxSessionStartedAt` anchor,
suppressing Tier 1 (warning) and Tier 2 (handoff) — the soft, cooperative
actions — for a runtime-aware grace period after a fresh session starts.
Explicitly NOT applied to Tier 3 (deadline-exceeded force-restart) or the
hard API-overflow banner check, both safety backstops that must act
regardless of session age. Also independent of the separate hang-detector
subsystem (wyre-only, untouched) and the consecutive circuit breaker itself
(grace only gates whether Tier 1/2 fire, never the breaker's counters).

Also adds a defensive log (not a silent default) in loadCtxCircuit() if
`.ctx-circuit.json` is ever found in upstream's `{ restarts: number[] }`
shape instead of wyre's `{ consecutiveWithoutRecovery, handoffFires,
brokenAt }` — belt-and-suspenders per the diagnosis, no evidence this has
happened.

12 new tests cover: handoffGraceMs per-runtime values, grace suppressing
Tier 1/2, grace expiring and re-enabling them, runtime-aware window
differences, Tier 3 and the overflow banner staying ungated by grace,
non-interaction with the Tier-3 breaker's own counter, and the
loadCtxCircuit shape-detection log (both trip and non-trip cases).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant