Reject confirm_code_execution for local code tools on Anthropic path

The Anthropic /v1/messages server-tool path maps a Studio tool alias such as
{"type":"python"} to the local tool loop and runs python/terminal on the
host. That path does not wire the confirmation prompt into its SSE translation
(which is why confirm_tool_calls is already rejected there), so treating
confirm_code_execution as ignored let local code run without the prompt the
flag promises.

Reject confirm_code_execution on this path when a local code-execution tool is
actually selected, mirroring the confirm_tool_calls rejection. The check sits
inside the server-tool branch and is gated on the resolved tool list, so a
non-code request (e.g. web_search) is unaffected and a disabled request
(enable_tools=false / --disable-tools) never reaches it. bypass_permissions
still suppresses the gate.

Update the field docs and split the Anthropic test into a code-tool rejection
case and a non-code ignored case.
This commit is contained in:
danielhanchen 2026-07-07 11:03:42 +00:00
commit 13c012a7b3
3 changed files with 61 additions and 19 deletions

View file

@ -775,7 +775,7 @@ class ChatCompletionRequest(BaseModel):
)
confirm_code_execution: Optional[bool] = Field(
None,
description = "[x-unsloth] When true, pause only before local code-execution tool calls (python/terminal) and wait for the user to allow/deny each via POST /api/inference/tool-confirm; other tools (web_search, render_html, ...) still run without a prompt. Applies to local python/terminal execution only, so it is ignored for external providers and Anthropic server tools (their code runs in the provider's sandbox, not locally). Independent of confirm_tool_calls; requires stream=true when a local code-execution tool is enabled; bypass_permissions still takes precedence.",
description = "[x-unsloth] When true, pause only before local code-execution tool calls (python/terminal) and wait for the user to allow/deny each via POST /api/inference/tool-confirm; other tools (web_search, render_html, ...) still run without a prompt. Supported on the OpenAI-compatible local endpoints (/v1/chat/completions, /v1/responses). It is ignored for external providers (their hosted code runs in the provider's sandbox, not locally); on Anthropic /v1/messages it is rejected when a local code-execution tool is selected, since that path does not wire the confirmation prompt. Independent of confirm_tool_calls; requires stream=true when a local code-execution tool is enabled; bypass_permissions still takes precedence.",
)
bypass_permissions: Optional[bool] = Field(
False,

View file

@ -10227,9 +10227,10 @@ async def anthropic_messages(
),
)
# confirm_code_execution guards only local python/terminal; Anthropic server
# tools (incl. hosted code execution) run server-side, so the flag does not
# apply here and is ignored (documented on the field).
# confirm_code_execution is handled below inside the server-tool branch (it is
# rejected when a local python/terminal tool is actually selected, mirroring
# confirm_tool_calls), so nothing to do pre-switch here: a non-code request is
# unaffected and a disabled request never enters that branch.
# require_vision rejects a swap to a text-only target before it runs, so an
# image request can't evict the resident vision model only to hit the vision
@ -10431,6 +10432,36 @@ async def anthropic_messages(
payload.enabled_tools,
)
# confirm_code_execution guards local python/terminal execution. On this
# path a Studio tool alias like {"type":"python"} maps to the local tool
# loop and runs code on this host -- but the Anthropic Messages SSE
# translation does not wire the confirmation prompt (which is why
# confirm_tool_calls is rejected above). Silently ignoring the flag would
# run python/terminal without the promised prompt, so reject it when a
# local code-execution tool is actually selected. A non-code selection
# (e.g. web_search) is unaffected, and bypass_permissions suppresses the
# gate. Gated on server_tools above, so a disabled request never reaches
# here.
if (
bool(getattr(payload, "confirm_code_execution", False))
and not bool(getattr(payload, "bypass_permissions", False))
and _enables_code_execution_tool(openai_tools)
):
api_monitor.fail(
monitor_id,
"confirm_code_execution is not supported for Anthropic Messages server tools.",
)
raise HTTPException(
status_code = 400,
detail = anthropic_error_body(
"confirm_code_execution is not supported for Anthropic Messages "
"server tools; it only guards local python/terminal execution on "
"the OpenAI-compatible endpoints (/v1/chat/completions).",
status = 400,
err_type = "invalid_request_error",
),
)
# Build tool-use system prompt nudge (same logic as /chat/completions)
_nudge = _build_tool_action_nudge(
tools = openai_tools,

View file

@ -1718,23 +1718,34 @@ class TestAnthropicMessagesToolRouting:
assert "confirm_tool_calls is not supported" in exc.value.detail["error"]["message"]
assert backend.calls == []
def test_confirm_code_execution_ignored_for_server_tools(self, monkeypatch):
# confirm_code_execution guards only local python/terminal execution.
# Anthropic server tools run in the provider's sandbox, not locally, so
# the flag does not apply here: it is ignored (never rejects), whether
# the requested server tool is code execution or not.
for tool in (
{"type": "python", "name": "python"},
{"type": "web_search_20250305", "name": "web_search"},
):
backend = _mock_backend(monkeypatch)
payload = _basic_payload(
confirm_code_execution = True,
tools = [tool],
)
def test_confirm_code_execution_rejected_for_code_server_tools(self, monkeypatch):
# A Studio {"type":"python"} alias runs the local python executor via the
# tool loop, but this path does not wire the confirmation prompt (like
# confirm_tool_calls above). Ignoring the flag would run code without the
# prompt, so it is rejected when a local code-execution tool is selected.
backend = _mock_backend(monkeypatch)
payload = _basic_payload(
confirm_code_execution = True,
tools = [{"type": "python", "name": "python"}],
)
with pytest.raises(HTTPException) as exc:
_drive(anthropic_messages(payload, request = None, current_subject = "t"))
assert backend.calls[0][0] == "tools"
assert exc.value.status_code == 400
assert "confirm_code_execution is not supported" in exc.value.detail["error"]["message"]
assert backend.calls == []
def test_confirm_code_execution_ignored_for_non_code_server_tools(self, monkeypatch):
# web_search is not code execution, so the flag does not apply and the
# request proceeds normally rather than being rejected.
backend = _mock_backend(monkeypatch)
payload = _basic_payload(
confirm_code_execution = True,
tools = [{"type": "web_search_20250305", "name": "web_search"}],
)
_drive(anthropic_messages(payload, request = None, current_subject = "t"))
assert backend.calls[0][0] == "tools"
def test_per_request_enable_tools_false_blocks_server_tool_alias(self, monkeypatch):
backend = _mock_backend(monkeypatch)