engine: supersede a stale provider binding on restart (spec §3.1)#260
Conversation
…s block The provider-attach conflict check rejected any second instance whose incumbent was seen within the 45s node-liveness TTL, ignoring instance_id. Per fleet spec §3.1 a new instance_id is a reconnect/restart and should supersede the stale binding; only a genuinely live duplicate should be rejected. After an unclean disconnect (kill -9, dropped socket) the old connection lingers, so a `node up` restarted within 45s was wrongly rejected and left half-registered. Make the check instance-aware: the same instance re-registering replaces itself, a different instance supersedes unless the incumbent was framed within PROVIDER_ATTACH_LIVENESS_MS (~2x the broker's 12s node heartbeat cadence, 24s). A killed instance stops framing, so its window lapses and the restart takes over via the existing supersede handoff. Genuine live duplicates still reject. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 38 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request updates the provider-attach conflict resolution to honor spec §3.1. A restarted node instance (with a new instance_id) now supersedes a stale binding instead of being rejected, while a genuinely live duplicate is still rejected. The liveness window for provider attachment has been reduced to 24 seconds (approximately twice the broker heartbeat cadence) from the 45-second node TTL, preventing restarted nodes from being blocked after unclean disconnects. New conformance tests have been added to verify this behavior. There are no review comments, so no additional feedback is provided.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 04fbe5989f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| * than the node-liveness TTL, which governs roster/routing rather than attach | ||
| * arbitration. | ||
| */ | ||
| export const PROVIDER_ATTACH_LIVENESS_MS = 24_000; |
There was a problem hiding this comment.
Keep attach liveness above the SDK heartbeat interval
The new 24s attach-liveness window is shorter than the existing TypeScript /v1/node/ws heartbeat cadence: WsClient.startPing() sends node.heartbeat every 30_000 ms, and Agent.connect() uses that path for direct nodes. For those live SDK connections, a duplicate register that arrives 25–30s after the last frame is treated as stale here, so providerAttachConflict returns null and the new connection supersedes/closes the incumbent instead of rejecting a genuinely live duplicate. Please either raise this window to cover the current client heartbeat cadence (plus slack) or lower/update the client heartbeat interval at the same time.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Good catch. Raised PROVIDER_ATTACH_LIVENESS_MS to 35s so it covers the built-in SDKs’ 30s node heartbeat cadence with 5s of scheduling/network slack while staying below the 45s node TTL. Added conformance coverage for a provider-less direct SDK connection at the exact inclusive boundary, plus stale takeover just beyond it; the focused provider suite and full engine suite both pass. Fixed in 6251602.
What this is
The provider-attach duplicate check is instance-aware and honors fleet spec §3.1:
instance_idre-registering replaces itself (reconnect).instance_idsupersedes the incumbent binding — a restart — unless the incumbent was framed withinPROVIDER_ATTACH_LIVENESS_MS(~2× the broker's 12s node heartbeat cadence = 24s), in which case it is a genuinely live duplicate and is rejected.Why
The check previously rejected any second instance whose incumbent connection was seen within the 45s node-liveness TTL, ignoring
instance_id. After an unclean disconnect (kill -9, dropped socket, half-open TCP) the old connection is never closed, so it lingers in the registry with a recentlastSeen. Anode uprestarted within 45s — which mints a freshinstance_idper connection epoch — was rejected before reaching the supersede handoff (attachProvider) and left half-registered. A clean Ctrl-C restart already worked (close unbinds the provider); this closes the unclean-disconnect gap.Keying liveness to ~2× the heartbeat cadence (not the coarser node TTL, which governs roster/routing) means a killed instance — which stops framing immediately — lapses out of the window after ~2 missed beats, so the restart supersedes it instead of being blocked for up to 45s. A genuinely live duplicate still frames every ~12s and is still rejected deterministically.
Tests (
nodeProviders.test.ts)provider_instance_conflict.Refs #250.
🤖 Generated with Claude Code