Skip to content

fix(runner): refuse a Claude-on-Daytona run whose tools are all client-kind - #5366

Merged
mmabrouk merged 1 commit into
release/v0.105.4from
fix/runner-daytona-client-only-tools-gate
Jul 17, 2026
Merged

fix(runner): refuse a Claude-on-Daytona run whose tools are all client-kind#5366
mmabrouk merged 1 commit into
release/v0.105.4from
fix/runner-daytona-client-only-tools-gate

Conversation

@mmabrouk

Copy link
Copy Markdown
Member

Symptom

A Claude run on a Daytona sandbox that requests only client-kind tools (for example a single request_connection browser tool) passes validation, starts, and then runs with zero tools and returns ok: 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.ts only refused a remote-sandbox tool run when the provider was not Daytona:

if (!isPi && isRemoteSandbox && toolSpecs.length > 0) {
  if (!isDaytona) {
    return { ok: false, error: REMOTE_TOOLS_UNSUPPORTED_MESSAGE };
  }
}

So an all-client-tool set on Daytona fell through and advertised nothing. mcp.ts already 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 combinations run-plan.ts refuses 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:

if (executableToolSpecsForRun.length === 0) {
  return { ok: false, error: DAYTONA_CLIENT_ONLY_TOOLS_UNSUPPORTED_MESSAGE };
}

The new message follows the REMOTE_TOOLS_UNSUPPORTED_MESSAGE style (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

  • Flipped the now-incorrect unit test allows claude x daytona x client-only tools to expect refusal, asserting the message and that it fails before any cwd is created (an up-front gate).
  • Existing coverage already pins the unaffected cases: 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).
  • Full runner suite green: pnpm run typecheck clean, pnpm test 1158 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

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

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Daytona 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.

Changes

Daytona tool delivery

Layer / File(s) Summary
Client-only tool boundary refusal
services/runner/src/engines/sandbox_agent/run-plan.ts, services/runner/tests/unit/sandbox-agent-run-plan.test.ts
The run-plan builder rejects Daytona non-Pi runs with zero executable tools, and tests verify the error message and that no Daytona cwd is created.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed It clearly summarizes the main change: refusing Claude-on-Daytona runs when only client-kind tools are requested.
Description check ✅ Passed It accurately describes the validation bug, the Daytona refusal change, and the affected test updates.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/runner-daytona-client-only-tools-gate

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.

@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview, Comment Jul 17, 2026 8:37pm

Request Review

@mmabrouk

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@mmabrouk
mmabrouk marked this pull request as ready for review July 17, 2026 21:33
@dosubot dosubot Bot added the size:S This PR changes 10-29 lines, ignoring generated files. label Jul 17, 2026
@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Railway Preview Environment

Status Destroyed (PR closed)

Updated at 2026-07-17T22:37:32.344Z

@mmabrouk
mmabrouk changed the base branch from main to release/v0.105.4 July 17, 2026 22:01
@mmabrouk
mmabrouk merged commit 254ea6c into release/v0.105.4 Jul 17, 2026
53 of 55 checks passed
mmabrouk added a commit that referenced this pull request Jul 18, 2026
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
mmabrouk added a commit that referenced this pull request Jul 18, 2026
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
mmabrouk added a commit that referenced this pull request Jul 18, 2026
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S This PR changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant