Skip to content

fix(opencode): run CLI under a Linux pseudo-TTY so subprocess calls work#283

Open
kazutoshi-ishimori wants to merge 1 commit into
aiming-lab:mainfrom
kazutoshi-ishimori:case5/opencode-pty
Open

fix(opencode): run CLI under a Linux pseudo-TTY so subprocess calls work#283
kazutoshi-ishimori wants to merge 1 commit into
aiming-lab:mainfrom
kazutoshi-ishimori:case5/opencode-pty

Conversation

@kazutoshi-ishimori

Copy link
Copy Markdown
Contributor

Summary

The opencode CLI requires a TTY. When beast mode invokes it via subprocess.run with piped stdout/stderr, it can return exit 0 with empty output and no generated files. The pipeline then treats this as a successful run that produced no code.

This PR wraps the opencode call on Linux with util-linux script so the CLI runs under a pseudo-TTY:

script -q -e -c "<cmd>" /dev/null
  • -q suppresses script's start/done messages
  • -c runs the requested command
  • -e returns the child's exit status — without it script can return 0 even when the child command fails, which would re-mask an opencode failure as success (the very behaviour this wrapper exists to fix)

Why gate on Linux

The script -q -e -c ... /dev/null form is util-linux-specific. BSD/macOS script implementations are not compatible with this argument form, so an unconditional shutil.which("script") check could break non-Linux platforms rather than help them. The wrapper is therefore gated on sys.platform == "linux". On other platforms, and when script is missing, the bridge falls back to direct invocation — identical to current behaviour, with no regression.

Tests

Command construction is extracted into _build_opencode_command with unit tests covering:

  • Linux wraps with script
  • missing script falls back to direct invocation
  • non-Linux platforms do not wrap
  • child exit-status preservation uses -e
  • shell metacharacters are quoted safely

tests/test_opencode_bridge.py passes (51 tests).

Notes

  • Verified on Linux under WSL2 (util-linux 2.39.3): script -qc "exit 7" returns 0, while script -q -e -c "exit 7" correctly returns 7.
  • I don't have a macOS/BSD environment to empirically test the fallback path; it's covered by mocked unit tests only.
  • A fully cross-platform alternative would be Python's pty module, but that's a larger change — happy to follow up if preferred.

The `opencode` CLI requires a TTY. When beast mode invokes it via
`subprocess.run` with piped stdout/stderr, it can return exit 0 with empty
output and no generated files, so the pipeline treats a no-op as a successful
run that produced no code.

Wrap the call on Linux with util-linux `script -q -e -c "<cmd>" /dev/null`:
  * -q  suppresses script's start/done messages,
  * -c  runs the requested command,
  * -e  returns the child's exit status — without it `script` can return 0
        even when the child command fails, which would re-mask an opencode
        failure as success.

The wrapper is gated on `sys.platform == "linux"` because the
`script -q -e -c ... /dev/null` form is util-linux syntax; BSD/macOS `script`
is not compatible with it. On non-Linux platforms, and when `script` is
unavailable, we fall back to direct invocation — the prior behaviour, no
regression.

Command construction is extracted into `_build_opencode_command` and covered
by unit tests (Linux-wraps / no-script-falls-back / non-Linux-no-wrap /
exit-status-preservation / shell-metachar quoting).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant