script_run error script_tool_forbidden: discovery calls belong outside

script_tool_forbidden means a Sume script called a discovery tool (tools_list, tools_schema, mcp_health, search_tools) or script_run. Call them from the turn.

4 min readSume
All posts

script_tool_forbidden is one of four stop codes from the Sume script_run tool. It means the script called a discovery tool (search_tools, get_tool_details, call_tool, tools_list, tools_schema or mcp_health) or script_run itself. Make those calls from the turn, not from a script.

What the server tells the agent

The tool's next steps say the same thing in one line: call discovery tools from the turn. The error is deterministic. The same script will fail the same way until the call is removed, so retrying it is wasted work.

script_run stop codes (read 2026-10-05 against the Sume codebase)
CodeMeaningFix
script_tool_forbiddenscript called a discovery tool or script_runmove the call to the turn
script_timeoutthe script ran past its time limitsplit the work
script_call_budget_exceededmore tool calls than max_callssplit the batch or raise max_calls within its ceiling
script_paid_budget_exceededmore paid calls than max_paid_callssame, for paid calls

The right shape

Discover first, then script. In the turn, call tools_list and, for the one tool you will loop, tools_schema. Then write a script that only calls the loop tool and returns job ids. The progressive discovery post describes the two-step read. Inside the script each paid create still needs its own idempotency_key, since the gates are the same ones the turn would apply.

Pre-flight lint

A cheap check in your harness catches the mistake before a call is spent. The list below is the one from the tool's guidance.

FORBIDDEN = {
    'search_tools', 'get_tool_details', 'call_tool',
    'tools_list', 'tools_schema', 'mcp_health', 'script_run',
}

def lint(script_source: str) -> list[str]:
    return sorted(n for n in FORBIDDEN if n in script_source)

print(lint('await tools.tts_create({})'))
print(lint('await tools.tools_list({})'))

Limits

A substring check is crude and can flag a name that appears only in a comment. Treat it as a hint. The server remains the source of truth, and it is the server that returns the stop code.

Why discovery is excluded

A script is meant to be a fixed loop over known tools: the same calls with the same gates, run on the Sume side, with only the returned value coming back. Discovery and health tools exist to decide what to call, which is a decision for the turn. Letting a script discover tools would also let a script call the script runner again.

Checklist before you ship

  • Run tools_list, tools_schema and mcp_health from the turn, before the script.
  • Pass the discovered facts into the script as constants.
  • Never call script_run from inside a script.
  • Treat script_tool_forbidden as a script bug, not as a retryable error.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume