fix(runner): refuse a Claude-on-Daytona run whose tools are all client-kind - #5366
Conversation
…t-kind
The remote-tools gate only refused when the sandbox was remote AND not Daytona.
A non-Pi (Claude) run on Daytona requesting ONLY client-kind tools therefore
passed validation, then silently advertised zero tools: the in-sandbox stdio
MCP shim delivers only executable (gateway/callback) tools and omits client
tools, so nothing remained to advertise. mcp.ts's own log admits this case
('run-plan should have refused this run'), and its doc comment documents
refusal as the intent.
Implement that intent: inside the Daytona branch of the gate, refuse when no
executable tool remains after excluding client-kind tools, with a message in
the REMOTE_TOOLS_UNSUPPORTED_MESSAGE style. A mix of client + executable tools
is unaffected (the executable ones are delivered). Flip the now-incorrect
'allows claude x daytona x client-only tools' unit test to expect refusal.
📝 WalkthroughWalkthroughDaytona Claude runs with only client-kind tools now fail during run-plan construction when no executable tools remain. The change adds an exported error message and verifies that sandbox cwd creation is skipped. ChangesDaytona tool delivery
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 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 |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
Railway Preview Environment
Updated at 2026-07-17T22:37:32.344Z |
Advertise browser-fulfilled client tools to the in-sandbox stdio MCP shim and add a paused relay answer so a parked client tool ends the shim's tools/call cleanly while the runner ends the turn. The browser result returns on the cold-replay resume. Deletes the interim #5366 client-only Daytona refusal. Closes #5256; closes the residual mixed-set drop of #4984. The client-tool pause disposition is a closed set (pi-native / cold-acknowledge, with warm-hold reserved) at the client-tool boundary; the relay consumes the derived writePausedAnswer switch. Claude-Session: https://claude.ai/code/session_01DnWRxU3dCJ11hgDidm26vW
Advertise browser-fulfilled client tools to the in-sandbox stdio MCP shim and add a paused relay answer so a parked client tool ends the shim's tools/call cleanly while the runner ends the turn. The browser result returns on the cold-replay resume. Deletes the interim #5366 client-only Daytona refusal. Closes #5256; closes the residual mixed-set drop of #4984. The client-tool pause disposition is a closed set (pi-native / cold-acknowledge, with warm-hold reserved) at the client-tool boundary; the relay consumes the derived writePausedAnswer switch. Claude-Session: https://claude.ai/code/session_01DnWRxU3dCJ11hgDidm26vW
Advertise browser-fulfilled client tools to the in-sandbox stdio MCP shim and add a paused relay answer so a parked client tool ends the shim's tools/call cleanly while the runner ends the turn. The browser result returns on the cold-replay resume. Deletes the interim #5366 client-only Daytona refusal. Closes #5256; closes the residual mixed-set drop of #4984. The client-tool pause disposition is a closed set (pi-native / cold-acknowledge, with warm-hold reserved) at the client-tool boundary; the relay consumes the derived writePausedAnswer switch. Claude-Session: https://claude.ai/code/session_01DnWRxU3dCJ11hgDidm26vW
Symptom
A Claude run on a Daytona sandbox that requests only client-kind tools (for example a single
request_connectionbrowser tool) passes validation, starts, and then runs with zero tools and returnsok: true. The user asked for tools; the agent silently got none.Why
On Daytona a non-Pi harness receives its tools through the in-sandbox stdio MCP shim, which advertises only executable (gateway/callback) tools. Client tools are intentionally omitted (the shim's blocking relay call cannot pause for a browser round-trip yet).
The up-front gate in
run-plan.tsonly refused a remote-sandbox tool run when the provider was not Daytona:So an all-client-tool set on Daytona fell through and advertised nothing.
mcp.tsalready knows this is wrong: its log for the case says"...run-plan should have refused this run", and its doc comment lists"client tools on Daytona"as one of the "undeliverable combinationsrun-plan.tsrefuses before a session is ever built." The refusal was documented but never implemented. This is the F1 silent zero-tools drop, one configuration over.Change
Implement the documented intent. Inside the Daytona branch of the same gate, refuse when nothing executable remains after excluding client-kind tools:
The new message follows the
REMOTE_TOOLS_UNSUPPORTED_MESSAGEstyle (states the constraint, the remedies, and the tracking doc). A mix of client + executable tools is unaffected: the executable ones are delivered and the client ones are dropped, exactly as before. Pi on Daytona is unaffected (it delivers client tools through its extension + file relay). Local Claude is unaffected (the loopback MCP channel carries client tools).Tests
allows claude x daytona x client-only toolsto expect refusal, asserting the message and that it fails before any cwd is created (an up-front gate).claude x daytona x mixed tools(allowed, keeps only executable specs),pi x daytona x client-only tools(allowed),claude x local x client-only tools(allowed).pnpm run typecheckclean,pnpm test1158 passed across 75 files.Scope / risk
Two files, additive gate branch plus one new message constant. No wire-shape change. Behavior change is strictly a loud up-front refusal replacing a silent zero-tools run.
https://claude.ai/code/session_01RZnMTx5et6hRo5EHN187tT