Skip to content

Emitted OpenCode plugin: hook-augment spawn has no timeout; a hung child blocks every grep/glob/read result #2693

Closed

Description

@woodsonl

Version

0.12.1 (current main, c23f162)

Platform

macOS (Apple Silicon)

Install channel

GitHub release archive / install.sh

Binary variant

standard

What happened, and what did you expect?

cbm_client_adapter_opencode() (src/cli/client_adapter.c) emits the OpenCode plugin's augment() with no timeout on the spawned hook-augment child. The plugin awaits augment() inline inside 'tool.execute.after', so a child that never exits blocks every grep/glob/read tool result in the host OpenCode session for as long as that session lives. A wedged SQLite open or an uninterruptible syscall in the child is enough. The template's comment promises "Every failure path resolves to '' (fail open)", but a hang is a stall today, not a failure path.

Two adjacent gaps in the same emitted function:

  1. Stream errors fire on child.stdin, not on child. With a payload larger than the pipe buffer and a non-reading child, killing the child fails the buffered write with EPIPE as an unhandled 'error' event. Reproduced under Bun 1.4.0 (the OpenCode plugin runtime): the helper resolves '' after the kill timer, then the host process exits 1 with EPIPE: broken pipe, write. Spawn ENOENT hits the same class (end() on a destroyed stream).
  2. child.kill() sends SIGTERM, the weakest signal. hook-augment installs no SIGTERM handler (it arms SIGALRM for its own deadline, src/cli/hook_augment.c:159), so SIGKILL loses nothing and also covers children wedged past TERM delivery.

Reproduction

  1. scripts/build.sh && HOME=<scratch> build/c/codebase-memory-mcp install -y --clients=opencode
  2. In the generated <scratch>/.config/opencode/plugins/cbm-augment.ts, point const BIN at a stub: #!/bin/sh then sleep 60.
  3. Await the default export's hook:
const hooks = await plugin.default.server({ directory: '/tmp' });
await hooks['tool.execute.after'](
  { tool: 'grep', args: { pattern: 'x' }, sessionID: 's1' },
  { output: 'BASE' },
);

The await never resolves (the stub sleeps 60s; a real hang is indefinite).

For the EPIPE crash: repeat with pattern: 'x'.repeat(4 * 1024 * 1024) and add a 10s kill timer by hand. The process exits 1 with the unhandled EPIPE above.

Expected behavior

augment() bounds the wait: kill the child after a fixed budget, resolve '', and handle stream-level errors so the host cannot crash.

Logs

The repro is fully local; no server logs involved.

Diagnostics trajectory (memory / performance / leak issues)

Not memory-related.

Project scale (if relevant)

n/a

Confirmations

  • I searched existing issues and this is not a duplicate.
  • My reproduction uses shareable code (a dummy snippet or a public OSS repository), not proprietary code.

Disclosed: filed by an AI coding agent on behalf of the account owner.

Activity

  1. DeusData commented on Oct 19, 2026

    @DeusData
    Owner

    Thank you for the local stub reproduction and the separate Bun EPIPE example. Those clearly describe the two reported failure modes: an unbounded wait and an unhandled stream error. I have recorded this as an integration/stability bug report; the implementation and signal choice still need review.

  2. DeusData commented on Oct 20, 2026

    @DeusData
    Owner

    Fixed on main by 205343e (PR #2694). Thank you for the precise report and the reproduction, @woodsonl!

  3. added a commit that references this issue on Oct 20, 2026
    6e8e785
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingeditor/integrationEditor compatibility and CLI integrationstability/performanceServer crashes, OOM, hangs, high CPU/memory

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions