Skip to content

engine: supersede a stale provider binding on restart (spec §3.1)#260

Merged
willwashburn merged 2 commits into
mainfrom
node-attach-liveness-window
Jul 11, 2026
Merged

engine: supersede a stale provider binding on restart (spec §3.1)#260
willwashburn merged 2 commits into
mainfrom
node-attach-liveness-window

Conversation

@willwashburn

@willwashburn willwashburn commented Jul 10, 2026

Copy link
Copy Markdown
Member

What this is

The provider-attach duplicate check is instance-aware and honors fleet spec §3.1:

  • The same instance_id re-registering replaces itself (reconnect).
  • A different instance_id supersedes the incumbent binding — a restart — unless the incumbent was framed within PROVIDER_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 recent lastSeen. A node up restarted within 45s — which mints a fresh instance_id per 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)

  • Unclean disconnect + fresh-instance restart within the node TTL now supersedes (fails against the old 45s window).
  • A different instance while the incumbent is actively framing still rejects with provider_instance_conflict.
  • Clean-reconnect replacement is unchanged (existing coverage).

Refs #250.

🤖 Generated with Claude Code

Review in cubic

…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>
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@willwashburn, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 38 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 816b5ea1-b39d-45cb-9d4f-a529293cbdda

📥 Commits

Reviewing files that changed from the base of the PR and between cabea01 and 6251602.

📒 Files selected for processing (4)
  • packages/engine/CHANGELOG.md
  • packages/engine/src/__tests__/conformance/nodeProviders.test.ts
  • packages/engine/src/adapters/node/realtime.ts
  • packages/engine/src/engine/placement.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch node-attach-liveness-window

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/engine/src/engine/placement.ts Outdated
* than the node-liveness TTL, which governs roster/routing rather than attach
* arbitration.
*/
export const PROVIDER_ATTACH_LIVENESS_MS = 24_000;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 4 files

Re-trigger cubic

@willwashburn willwashburn merged commit 1447018 into main Jul 11, 2026
5 checks passed
@willwashburn willwashburn deleted the node-attach-liveness-window branch July 11, 2026 13:01
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