Repository navigation
fix(ai-harness): a forwarded command ends with the owner's result or error - #1872
Conversation
…error A command that reaches a host that does not own the thread now waits for the owner to settle it. It resolves with the owner's result, fails when the command throws, and rejects with InputRejectedError when the owner refuses it (unknown_command, invalid_input). Before, it completed with undefined. Closes #1796
|
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 7a0b833
☁️ 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-computer-local
@tanstack/ai-computer-playwright
@tanstack/ai-computer-sandbox
@tanstack/ai-computer-use
@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-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-svelte
@tanstack/ai-typesafe
@tanstack/ai-utils
@tanstack/ai-vercel-gateway
@tanstack/ai-vertex
@tanstack/ai-vue
@tanstack/ai-worldlabs
@tanstack/openai-base
@tanstack/preact-ai-devtools
@tanstack/react-ai-devtools
@tanstack/solid-ai-devtools
@tanstack/svelte-ai-devtools
commit: |
A child resumes from its stored pending messages. The store keeps a nested child in its own thread, so those messages had no card for it, and the nested child ran again from the start. Keep the nested cards from the client's copy on the stored message of their parent tool call. Also update the harnessText test for the required threadId: one session per thread.
|
| Name | Type |
|---|---|
| @tanstack/ai-harness | Major |
| @tanstack/ai-acp | Patch |
| @tanstack/ai-code-mode | Patch |
| @tanstack/ai-computer-use | Patch |
| @tanstack/ai-harness-cli | Patch |
| @tanstack/ai-mcp | Patch |
| @tanstack/ai-sandbox | Patch |
| @tanstack/ai-skills | Patch |
Click here to learn what changesets are, and how to add one.
Click here if you're a maintainer who wants to add a changeset to this PR
…twin to the chat-only conformance The stopgap in spawn.ts copied nested subagent cards from the client, which trusts the client's messages for a nested child. #1878 fixes the nested resume from the store. Until then, skip the subagent-waits test that needs it. The chat-only conformance test from main had no twin. On this branch memoryPersistence has a log store, so the log-across-instances cases need one.
When a durable host forwards
session.runCommand(...)to the host that owns the thread, the caller gotcompletedwithundefined. This was true also when the owner refused the command or the command threw. Now the forwarding host waits for the owner to settle the input, and ends the operation the same way: with the result, with the error, or withInputRejectedError.🎯 Changes
executeCommandinpackages/ai-harness/src/session/turn.ts: anofferedadmission now goes to a newfollowCommand. It reads the owner'sharness.command.resultevent from the feed and waits onsettled({ inputId }).tests/input-lane.test.ts: a forwarded command that returns a value, one the owner refuses (unknown_command,invalid_input), and one that throws.docs/harness/durable-sessions.md, and one sentence in the unreleasedai-harness-corechangeset. The harness is not released yet, so no new changeset.Fixes for tests that already failed on
feat/harness-core, so CI goes green:subagent-waits.test.ts"waits for an agent that an agent tool calls, and both go on" is skipped, with a link to ai, ai-persistence: a nested subagent that waits runs again from the start under withPersistence #1878. Since fix(ai-persistence): resume interrupts of tool-mode subagents under withPersistence #1806, a nested subagent that waits runs again from the start after the resume. A fix that copies the nested card from the client would trust the client's messages for that child, so this PR does not ship one. ai, ai-persistence: a nested subagent that waits runs again from the start under withPersistence #1878 fixes it from the store.harness-text.test.ts: the test still expected a new session per call with no thread id.threadIdis required now, so it uses one thread id per call.conformance-skip-reporting.test.ts(frommain, fix(ai-persistence): report omitted generation stores as skipped in the conformance suite #1847): on this branchmemoryPersistence()has alogstore, so the conformance suite needs atwin. It now uses a shared log state and a twin, asmemory.conformance.test.tsdoes.✅ Checklist
pnpm run test:pr, or these tests do not apply to this pull request.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. Two durable hosts share one thread. Host A owns it and runs a turn.
hostB.runCommand(...)resolves withundefined, also for an unknown command, bad input, or a command that throws.Cause.
executeCommandtreated theofferedadmission like a duplicate. It calledoperation.finish('completed', undefined)at once, before the owner ran the command.Fix. For
offered, the host waits onsettled({ inputId }). That rejects withInputRejectedErrorwhen the owner refuses the input. Afailedorabortedsettlement fails the operation. Acompletedsettlement finishes it with the result from the owner'sharness.command.resultevent, matched by the owner's operation id.Possible alternatives
queuedreceipt and document that a forwarded command has no result. The caller still cannot tell a refused command from one that ran, so this PR did not take it.harness.input.settled. This changes a log record shape for one case, and the result event is already in the log.Testing
Commands run.
npx vitest run tests/input-lane.test.ts -t "a command"inpackages/ai-harness.feat/harness-core(79a946689) plus the tests only: 3 failed.expected undefined to be 'echo hi', andpromise resolved "undefined" instead of rejecting(twice).tests/input-lane.test.tsin full: 27 passed. The whole@tanstack/ai-harnesssuite passes (one test skipped, see above).ai-persistenceconformance-skip-reporting: 87 passed.pnpm test:typesandpnpm test:oxlintinpackages/ai-harness: pass (only old warnings).oxfmt --check: pass.pnpm test:prand E2E. CI runs them. The E2E suite has no two-host harness setup.Manual test.
twoStores()intests/input-lane.test.ts).runCommand({ name: 'unknown' }). Before this PR it resolved withundefined. Now it rejects withInputRejectedErrorand the reasonunknown_command.How this PR makes testing easy. The three tests under
a commandintests/input-lane.test.tsare the repro.Linked issues
Closes #1796
Risk / rollback
Low. Only a command forwarded to another host changes. If the owner's result event is evicted from the feed before the input settles, the result is
undefined, as before. Revert the PR to undo.