Repository navigation
Conversation
A tool call that never returns stalls its session forever: a plugin
tool whose worker stopped replying once held a turn for 2.5 hours with
nothing to stop it. Across 616k completed calls, every tool except
shell, subagent and human-wait tools finishes at p99.9 within 2.6
minutes, and every call in that group over 30 minutes was a hang.
Every call now runs under a hard limit (packages/opencode/src/tool/
timeout.ts). A call still running when its limit elapses is ended the
way an abort ends it: the call's abort signal fires, its fiber is
interrupted, and the tool part becomes an error naming the limit and
where it came from, e.g. "tool call exceeded its 30m limit (config:
tool_timeout.default)". The turn continues, so the model sees it.
The most specific setting wins, each level a default for the next:
1. tool_timeout.<tool id> in config, for any built-in, plugin or MCP
tool, higher or lower than the rest;
2. the default the tool's provider declares: `timeout` on a plugin
tool({...}) definition, or `tool_timeout` on an mcp server entry.
question, task and call_omo_agent default to no limit here, so a
level-1 entry still applies to them;
3. tool_timeout.default, else 30 minutes.
`false` means no limit at every level. A per-call timeout the tool
takes itself (bash's `timeout` argument) raises levels 2 and 3 to that
timeout plus a minute of grace, so bash still reports its own timeout
first; a tool_timeout.bash entry is never raised. Time spent waiting on
a permission prompt does not count toward the limit.
MCP's tool_timeout is separate from its `timeout`, which bounds each
request and resets on progress notifications. The MCP spec has no
per-tool duration hint (annotations carry only behavioral hints), so
the server entry is the provider-level default.
Docs: config.mdx (Tool time limits), tools.mdx, custom-tools.mdx (Time
limit), plugins.mdx and the mcp-servers.mdx option tables, plus schema
descriptions; the v2 SDK types are regenerated.
AI-Tool: opencode 1.18.34-vt-207-907b3bc518
AI-Model: anthropic/claude-opus-5-5
AI-Variant: high
AI-Platform: darwin
AI-Harness: Vibeterm d01daadf
AI-Session-ID: ses_ed5bde150ffepnn3xqxHaucDLN
Contributor
|
The following comment was made by an LLM, it may be inaccurate: |
Nowaker
added a commit
to Nowaker/opencode
that referenced
this pull request
Oct 11, 2026
Feature and session records for d107564: tool_timeout, plugin tool({ timeout }) and MCP tool_timeout, upstream PR anomalyco#54503 against v1, the vt-212 install on m4max and vt-213 on desktop, and the desktop serve unit restarts. AI-Tool: opencode 1.18.34-vt-207-907b3bc518 AI-Model: anthropic/claude-opus-5-5 AI-Variant: high AI-Platform: darwin AI-Harness: Vibeterm e2a734d1 AI-Session-ID: ses_ed5bde150ffepnn3xqxHaucDLN
read, write, edit, grep and glob never legitimately run for minutes: a call that does is hung on I/O, such as a read of a named pipe nothing writes to, and it held its turn until the general 30-minute limit. A write that started 2026-10-10 never returned and its turn never ended. These five tools now default to 10 minutes at the provider level (packages/opencode/src/tool/timeout.ts, FILE_TOOL_MS), next to the question/task exemptions. A lower tool_timeout.default still applies to them; a higher one, or false, does not lift them. tool_timeout.<tool> replaces it either way. A call past the limit ends as a tool error the model sees: tool call exceeded its 10m limit (built-in default for read, set tool_timeout.read to change it) grep and glob now pass the call's abort signal to ripgrep, so the child process stops when the call is ended. Docs: config.mdx (Tool time limits), tools.mdx and the tool_timeout schema description.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note: I'm aware v1 is now the maintenance branch and new features go to v2 only. This PR is here for others to use, and I'll submit the v2 version once I've migrated my whole plugin system to it.
Issue for this PR
Closes #53433. Related: #46461, #54340.
Type of change
What does this PR do?
A tool call that never returns stalls its session until someone interrupts it. In one case a plugin tool's worker stopped replying, and the turn hung for 2.5 hours. Across 616k completed calls, every tool except shell, subagent and human-wait tools finished within 2.6 minutes at p99.9, and in that group every call that ran over 30 minutes was a hang.
With this change, every tool call runs under a hard limit (
packages/opencode/src/tool/timeout.ts). When a call is still running at its limit, it is ended the same way an abort ends it: itsabortsignal fires, its fiber is interrupted, and the tool part becomes an error that names the limit and where it came from, for exampletool call exceeded its 30m limit (config: tool_timeout.default). The turn continues, so the model sees that error.The most specific setting wins, and each level only sets the default for the levels below it:
tool_timeout.<tool id>in config, for any built-in, plugin or MCP tool. It overrides the levels below, whether higher or lower.timeouton a plugintool({...})definition, ortool_timeouton anmcpserver entry.questionandtaskdefault to no limit at this level, so a level-1 entry still applies to them.read,write,edit,grepandglobdefault to 10 minutes here, or totool_timeout.defaultwhen that is lower: these tools never legitimately run that long, and a call that does is hung on I/O (for example areadof a named pipe nothing writes to). Their error names the key that changes it:tool call exceeded its 10m limit (built-in default for read, set tool_timeout.read to change it).tool_timeout.default, else 30 minutes.falsemeans no limit at every level. When a tool takes its own per-call timeout (bash'stimeoutargument), that timeout plus one minute of grace raises the limit from levels 2 and 3. This way bash still reports its own timeout first. Atool_timeout.bashentry is never raised. Time spent waiting on a permission prompt does not count toward the limit.grepandglobpass the call's abort signal to ripgrep, so the child process stops when the call is ended.MCP's new
tool_timeoutis separate from its existingtimeout.timeoutbounds each request and resets on progress notifications. The MCP spec has no per-tool duration hint (tool annotations carry only behavioral hints), so the server entry serves as the provider-level default.Documented in
config.mdx("Tool time limits"),tools.mdx,custom-tools.mdx("Time limit"),plugins.mdxand themcp-servers.mdxoption tables, and in the schema descriptions. The v2 SDK types are regenerated.How did you verify your code works?
test/tool/timeout.test.tscovers resolution at every level with higher and lower values, thequestion/taskexemptions and their overrides, the file tools' 10-minute default and howtool_timeout.defaultand per-tool entries interact with it, the bash per-call raise and its limits, the error format, and the case where a hanging fiber is interrupted and its signal aborted.test/session/tools.test.tscovers an MCP tool ended at its server'stool_timeout, a user entry beating the MCP default, a hanging tool ended at its declared limit, and permission-prompt time not counting.test/session/prompt.test.ts, through the real loop and a fake LLM:.opencode/toolthat never resolves ends with the error part, and the next LLM request carries that error.timeoutraises the limit abovetool_timeout.default.tool_timeout.bashstill ends a call that asks for longer.readblocked opening a FIFO ends at a lowertool_timeout.default, and the turn continues.bun typecheckinpackages/core,packages/opencode,packages/pluginandpackages/sdk/js.tool_timeout.default, and a 1.5s per-tool override of a plugin'stimeout: falseeach ended their call with the error above, within about 5ms of the limit.sleepwith bashtimeout: 10000completed normally under the 2s default, and the turn finished with the model's final text.Checklist
This PR is AI generated, but under direct supervision and on request of @Nowaker.