Repository navigation
Conversation
…events off the log A durable host that opened a thread while another host ran a turn wrote its plugin state (for example the MCP plugin's connection status) to the shared log. The owner's next append conflicted, and its turn stopped. The session now reads the claim before its plugins mount, and drops session events while another host owns the thread. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… owned elsewhere On Netlify Play, Blobs requests stalled for ~10 s (the client retries for up to 25 s). The owner's claim renewals did not land and its claim expired, while its run lease renewed and its turn ran a tool. Another host opened the thread, took the claim, skipped the live turn in recovery, and ran the queued follow-ups. The transcript got a user message between a tool call and its result, and the model refused it. `isOwnedElsewhere` also reads the thread's active run: a live lease of another host counts. The inbox drain checks it before it claims. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nt both rules The inbox drain guard is now proven by the test: an input that waits in the inbox for an owner whose claim lapsed stays there until the owner's turn ends. Drop the ownerElsewhere resets after a claim: every accept() reads ownership again. Docs: a live run lease makes the owner, and a host that does not own a thread writes nothing to its log. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: d6d2400 The changes in this PR will be included in the next version bump. This PR includes changesets to release 7 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
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 |
|
View your CI Pipeline Execution ↗ for commit d6d2400
☁️ Nx Cloud last updated this comment at |
@tanstack/ai
@tanstack/ai-acp
@tanstack/ai-angular
@tanstack/ai-anthropic
@tanstack/ai-bedrock
@tanstack/ai-byteplus
@tanstack/ai-claude-code
@tanstack/ai-client
@tanstack/ai-cloudflare
@tanstack/ai-code-mode
@tanstack/ai-code-mode-snippets
@tanstack/ai-codex
@tanstack/ai-cohere
@tanstack/ai-compaction
@tanstack/ai-devtools-core
@tanstack/ai-durable-stream
@tanstack/ai-elevenlabs
@tanstack/ai-event-client
@tanstack/ai-fal
@tanstack/ai-gemini
@tanstack/ai-grok
@tanstack/ai-grok-build
@tanstack/ai-groq
@tanstack/ai-harness
@tanstack/ai-harness-cli
@tanstack/ai-isolate-cloudflare
@tanstack/ai-isolate-daytona
@tanstack/ai-isolate-e2b
@tanstack/ai-isolate-node
@tanstack/ai-isolate-quickjs
@tanstack/ai-isolate-quickjs-bun
@tanstack/ai-llmgateway
@tanstack/ai-lovable
@tanstack/ai-mcp
@tanstack/ai-memory
@tanstack/ai-mistral
@tanstack/ai-octane
@tanstack/ai-ollama
@tanstack/ai-ollaya
@tanstack/ai-openai
@tanstack/ai-opencode
@tanstack/ai-openrouter
@tanstack/ai-perplexity
@tanstack/ai-persistence
@tanstack/ai-preact
@tanstack/ai-react
@tanstack/ai-react-ui
@tanstack/ai-reactor
@tanstack/ai-remix
@tanstack/ai-sandbox
@tanstack/ai-sandbox-blaxel
@tanstack/ai-sandbox-boxd
@tanstack/ai-sandbox-cloudflare
@tanstack/ai-sandbox-daytona
@tanstack/ai-sandbox-docker
@tanstack/ai-sandbox-e2b
@tanstack/ai-sandbox-local-process
@tanstack/ai-sandbox-railway
@tanstack/ai-sandbox-sprites
@tanstack/ai-sandbox-upstash-box
@tanstack/ai-sandbox-vercel
@tanstack/ai-skills
@tanstack/ai-solid
@tanstack/ai-solid-ui
@tanstack/ai-svelte
@tanstack/ai-typesafe
@tanstack/ai-utils
@tanstack/ai-vercel-gateway
@tanstack/ai-vertex
@tanstack/ai-vue
@tanstack/ai-vue-ui
@tanstack/ai-worldlabs
@tanstack/openai-base
@tanstack/preact-ai-devtools
@tanstack/react-ai-devtools
@tanstack/solid-ai-devtools
@tanstack/svelte-ai-devtools
commit: |
Coverage✅ Coverage held across 30 compared package(s). Each package is measured twice in this job — on this PR and on its merge-base with
|
On a durable host with more than one instance, a second instance could stop the turn that the owner runs, or run inputs in the middle of it. This PR fixes two ways that happened. Both were found while hosting the harness on Netlify Play.
Each fix is its own commit:
a host that does not own a thread keeps its session events off the loga live run lease of another host means the thread is owned elsewherecover the inbox drain in the live-lease test, document both rules🎯 Changes
isOwnedElsewherealso reads the thread's active run. A live run lease of another host counts, also when that host's claim lapsed. The inbox drain checks it before it takes the claim.durable-sessions.md(a live run lease makes the owner, and a host that does not own a thread writes nothing to its log).✅ Checklist
pnpm run test:pr, or these tests do not apply to this pull request. (See Testing: the targets that apply were run one by one.test:prcompares againstmain, which this stacked PR is not based on.)docs/for this change, or this change is not user-facing.pnpm changeset), or this PR does not change a published package.🚀 Release Impact
Root cause
Issue. On a durable host with
workClaimsand several instances (serverless, no sticky routing), a second instance can write to the log of a thread while the owner runs a turn. Fix 1: the owner's turn stops with "Another host wrote to this log" and runs again from the start. Fix 2: the second host runs queued inputs mid-turn, and the transcript is invalid for the model.Cause.
pluginState().publish,emitPluginEventandwarnSessionpublish to the session feed. On a durable host that feed is the log writer.open()mounts the plugins before anything checks the claim, so a second host appends to the log. The owner's next append fails the position check, andstopOnLogFailurestops its session.isOwnedElsewhere()reads only the work claim. A claim renewal that stalls (the@netlify/blobsclient retries 5 times, 5 s apart) lets the claim expire while the run lease still renews. ThenrecoverFromLogsees the live lease and skips that one input (continue), and runs the later pending inputs.drainOncetakes the expired claim and applies the inbox entries.Fix. A session keeps its session events off the log while another host owns the thread. A live run lease of another host means the thread is owned elsewhere. So recovery is deferred, new inputs go to the inbox, and the inbox drain leaves them for the owner.
Possible alternatives
isOwnedElsewhere. It fixes recovery only. A new input on the second host would still be accepted and run mid-turn, so the check is where every path reads ownership.Testing
Commands run (on this branch, current
feat/harness-coreatd7e490af0):nx run-many -t buildfor all packages: pass.ai-harness:vitest run1556 passed,tsc --noEmit,oxlinton the changed files: pass.tsc --noEmitintesting/e2e, the full suite: 626 passed. After the last commit,playwright test --grep harness: 57 passed.pnpm test:docsandpnpm test:kiira(1896 snippets): pass.pnpm test:pr,test:knip,test:sherif(they compare againstmain).Repro before and after. Both tests are in
tests/forwarding.test.ts. Run withsrc/atfeat/harness-core, then at this branch:Each new case in
testing/e2e/tests/harness-forwarding.spec.ts(?plugin=1,?stall=1) fails on the base and passes here.Also tested on Netlify Play. A Netlify Server app on this branch with Netlify Blobs stores ran these tests:
Before the fix, the same outage left a user message between a tool call and its result.
Manual test
npx vitest run tests/forwarding.test.ts -t "run lease is live"inpackages/ai-harness. It passes.src/session/base.tstofeat/harness-core, then run the same command. It fails: the second host made 3 model calls while the owner's turn ran.-t "plugin state of another host". It fails on the base with "Another host wrote to this log".How this PR makes testing easy: two cases in
tests/forwarding.test.ts, and two intesting/e2e/tests/harness-forwarding.spec.ts(?plugin=1,?stall=1).Risk / rollback
workClaimsreadsruns.findActiveRun(threadId)when the claim is free. That happens once per input and once per open.🤖 Generated with Claude Code