Skip to content

opencode completion output is truncated, producing an unparseable zsh script (unmatched ") #30831

Closed as not planned

Description

@ben181231

Description

opencode completion truncates its output mid-string before the process exits. The resulting script is not valid zsh — it ends in the middle of a quoted literal, so any shell that does eval "$(opencode completion)" in its startup files (a common pattern, and what the opencode completion help text suggests) fails on every new shell with:

(eval):22: unmatched "

This appears to be the same underlying stdout-flush race as #29866 / #26114 / #26399, but the user-visible impact is more severe: it breaks interactive shell startup for anyone who sources the completion script from their rc files, not just a missing trailing newline.

Steps to reproduce

$ opencode --version
1.15.13

$ opencode completion | wc -l
21

$ opencode completion | tail -2
  fi
}
if [[ "'${zsh_eval_co

The script is cut off mid-if — the opening " is never closed.

In a zsh startup file:

eval "$(opencode completion)"

…produces on every new shell:

(eval):22: unmatched "

Expected behavior

opencode completion should emit a complete, syntactically valid shell script that can be safely eval'd.

Environment

  • opencode: 1.15.13 (installed via anomalyco/tap on Homebrew)
  • OS: macOS (Darwin 25.5.0, arm64)
  • Shell: zsh 5.x

Notes

Related


(Filed by Claude Code on behalf of @ben181231)

Activity

  1. ben181231 commented on Jun 5, 2026

    @ben181231
    Author

    Quick source dive — the likely culprit is in packages/opencode/src/index.ts:

    } finally {
    // Some subprocesses don't react properly to SIGTERM and similar signals.
    // Most notably, some docker-container-based MCP servers don't handle such signals unless
    // run using `docker run --init`.
    // Explicitly exit to avoid any hanging subprocesses.
    process.exit()
    }

    } finally {
      // Some subprocesses don't react properly to SIGTERM and similar signals.
      // ...
      // Explicitly exit to avoid any hanging subprocesses.
      process.exit()
    }

    The try { await cli.parse() } block runs yargs, which for the completion subcommand writes the generated shell script directly to process.stdout. Control then falls into finally, which calls process.exit() unconditionally. Node's process.exit() does not wait for writable streams to drain — when stdout is a pipe (as with eval "$(opencode completion)"), the kernel-side pipe buffer isn't fully consumed before the process is torn down, so the tail of the output is dropped. That's how the script ends mid-string with the unmatched ".

    This is consistent with the other reports of this race (#29866 JSON stream, #26114/#28606 --help newline, #29330 export | jq, #26399 debug skill), and looks like exactly what closed PR #29340 ("fix(opencode): flush stdio before cli exit") was attempting to address.

    Suggested fix

    Drain stdio before exiting, e.g.:

    } finally {
      await Promise.all(
        [process.stdout, process.stderr].map(
          (s) => new Promise<void>((resolve) =>
            s.write("") ? resolve() : s.once("drain", () => resolve()),
          ),
        ),
      )
      process.exit()
    }

    Setting process.exitCode and letting Node exit naturally would also work, but conflicts with the existing rationale for the explicit process.exit() (hanging subprocesses), so an explicit drain-then-exit is the safer change.


    (Posted by Claude Code on behalf of @ben181231)

  2. kagura-chen commented on Jun 5, 2026

    @kagura-chen
    Contributor

    I'd like to work on this. The fix should drain stdout/stderr before the process.exit() in the finally block of packages/opencode/src/index.ts. I'll add a drain-then-exit pattern as suggested, with a regression test.

  3. github-actions commented on Aug 5, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions