Skip to content

tools: v2 glob/grep run unbounded — no default timeout, one call hung a session for 21 minutes #39208

Description

@kitlangton

Summary

V2's glob and grep tools have no default wall-clock timeout. A low-match pattern over a large search root runs unbounded — in a real session, one glob call ran for 21 minutes 41 seconds and only ended when the user manually interrupted the turn. V1 had the same missing timeout but wired ctx.abort directly into the ripgrep child process; V2's tool leaves don't pass an abort signal at all and rely solely on fiber interruption.

Environment

  • opencode version: service 0.0.0-next-16305, CLI 0.0.0-next-16307 at time of incident (channel: next)
  • OS: macOS 25.5.0 (arm64)
  • Terminal: WarpTerminal
  • Shell: /bin/zsh
  • Install/channel: next (managed background service)
  • Active plugins: one local plugin (voice.ts), not involved in the tool path

Reproduction

  1. In a session whose agent has the built-in glob/grep tools, ask for a search that has few matches under a large root, e.g. glob with pattern: "**/*.metal", path: "/Users/<user>/code" where that directory contains dozens of repos with large node_modules/target/dist trees between gitignore contexts.
  2. Because matches never reach limit + 1, Stream.take(limit + 1) never short-circuits and ripgrep must walk the entire tree.
  3. Nothing bounds wall-clock time. The tool call stays pending indefinitely; the session shows no progress and queued steer messages wait behind it.

Expected Behavior

Search tools should have a bounded default execution time (the shell tool defaults to 120s). Ideally a timeout returns the partial results collected so far with a note like (Search timed out after 30s — results are partial. Narrow the path or pattern.) rather than an error.

Actual Behavior

Observed incident timeline (from the session event store):

Time (UTC) Event
19:38:44 glob **/*.metal /Users/kit/code, glob **/*{Hex,HEX,hex}*, and a broad grep called in one batch
19:38:56 A find-based shell call in the same batch returns in 12s
19:47:51 User steer message admitted — does not preempt; queues behind the wedged tools
20:00:25 User interrupts; all three tools recorded as session.tool.failed / aborted ("Tool execution interrupted")

Total: 21m41s of pending tool execution with zero output, plus a 12.5-minute queued user message.

Additional Context

Code analysis on origin/v2 (packages/core/src/ripgrep.ts, packages/core/src/tool/plugin/{glob,grep}.ts):

  • Ripgrep.Service's GlobInput/GrepInput already accept an optional signal?: AbortSignal (raced via waitForAbort), but the glob/grep tool leaves never pass one — they call ripgrep.glob({ cwd, pattern, limit }) bare.
  • There is no Effect.timeout/deadline anywhere on the run program in Ripgrep.Service.
  • Early termination exists only via Stream.take(input.limit + 1), i.e. only when the search finds enough matches. Low-match patterns get zero benefit.
  • For comparison, v1 (dev: packages/opencode/src/tool/glob.ts:42, grep.ts:52) plumbed ctx.abort into the ripgrep child directly.

Proposed fix: add a default deadline to Ripgrep.Service.run (bounded, e.g. 30s) that terminates the child and returns partial items with a timedOut/partial marker the tool leaves render as a truncation note. Happy to open a PR with a reproducer test (fake AppProcess whose stdout never closes) and the fix.

Related but separate: a steer-delivery user message admitted mid-turn did not preempt the wedged tool calls and sat queued for 12+ minutes; that likely deserves its own issue.

cc @rekram1-node

Metadata

Metadata

Assignees

No one assigned

    Labels

    2.0bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions