Skip to content

fix: ask the OpenCode binary for --standalone instead of reading its version - #13

Open
alcpereira wants to merge 3 commits into
callstack:mainfrom
alcpereira:fix/opencode-capability-probe
Open

alcpereira wants to merge 3 commits into
callstack:mainfrom
alcpereira:fix/opencode-capability-probe

Conversation

@alcpereira

@alcpereira alcpereira commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up F1 (medium) from the review of #12 (/tmp/review/apex-cli-12/FIX-FOLLOWUPS.md): the version probe misclassifies every non-stable OpenCode 2 build.

The bug

openCodeMajor decided the launch from --version, but every dev, beta and source build of both OpenCode generations reports 0.0.0-* (or local), which the regex never matched, so they all fell back to the v1 launch. Verified against the published @opencode/cli@beta binary:

$ opencode --version
opencode v0.0.0-beta-19507        # probe: no match → major 1
$ apex run opencode
Unrecognized flag: --model in command opencode   # reproduced with origin/main's apex, real beta binary

That is the entire preview channel hitting the exact error #12 exists to prevent — paru -S opencode-beta is in OpenCode's own install docs. And 0.0.0-* cannot be classified by version at all: the old opencode-ai package has published 11k+ 0.0.0-* versions too, so "major 0 → v2" would just move the bug onto v1 dev builds. (The regex widening the fanout review proposed, (?:[-+(]\S*)?, fails the same way: 0 < 2 → v1.)

The fix

Probe the capability, not the version. --standalone exists if and only if the interface is v2, and --help says so — verified against all four real binaries, including the ambiguous 0.0.0-dev-* pair no regex can separate:

Binary --version probe answer
v1 stable opencode-ai@1.18.34 1.18.34 false
v1 dev 0.0.0-dev-202610141901 false
v2 beta @opencode/cli@beta opencode v0.0.0-beta-19507 true
v2 stable opencode v2.0.29 true

(227 ms / 38 ms — same order as the old --version probe.) v1 writes its help to stderr and v2 to stdout, so both streams are read.

openCodeSupportsStandalone answers a tri-state — true (v2), false (v1), null (no answer) — and never rejects: execFile can throw synchronously (a .cmd launcher since CVE-2024-27980), and a --help (capped at 5 s) that fails or times out and prints nothing is no answer either. Even a failing --help that printed the flag is an answer: execFile hands over the captured streams beside its error, so a wrapper that prints the page and exits 1 still decides the launch. On null, apex run adds neither --model nor --standalone, so the assistant still starts on the user's own config and a warning on stderr says the gateway model was not selected — honest about not knowing beats guessing v1 and dying on a flag. The warning and the init summary's note come from launchOptions itself, so the unknown state is worded once, as an unknown interface.

Tests

  • Apex CLI never runs --version any more, so the fake opencode has no --version branch and no version plumbing at all: which generation a build is comes only from what --help prints.
  • Unknown probe launches with neither flag, leaves the inline config alone, and warns; an unparseable OPENCODE_CONFIG_CONTENT still fails the launch before the child starts.
  • New unit test on the probe itself: the flag on either stream, a help page without it, a failing --help with and without the flag in its output, missing binary, directory, a path execFile throws synchronously on (a NUL byte), and null — never rejects.
  • OPENCODE_CONFIG_CONTENT is now deleted in the test fixture; it leaked from the developer's shell into launched fakes (the same class of leak test: keep the developer's gateway key out of the test environment #11 closed for gateway keys — it fails existing tests on this author's machine without it).
  • End-to-end checked against the real binaries both ways: beta now launches, v1 dev still gets --model.

67/67 node --test pass; npm run check clean.

Not in this PR

F2 (probe call-site ordering around --apply, the .cmd guard in init), F3 (scrubbed probe env), F4, F5 and F6 stay separate per the follow-ups doc. The known-inaccurate comment prose merged by #12 (auth list example, "flags only follow a subcommand") is deliberately untouched — that is F6.

…version

The version probe decided the launch from `--version`, but every dev, beta
and source build of *both* OpenCode generations reports `0.0.0-*` (or
`local`), which the regex never matched, so they all fell back to the v1
launch. Verified against the published `@opencode/cli@beta`:

    $ opencode --version
    opencode v0.0.0-beta-19507       <- probe: no match -> major 1
    $ apex run opencode
    Unrecognized flag: --model in command opencode

The whole preview channel hits the exact error PR callstack#12 exists to prevent,
and `0.0.0-*` cannot be classified by version at all: the old `opencode-ai`
package has published over 11k `0.0.0-*` versions too, so "major 0 means
v2" would just move the bug to v1 dev builds.

Probe the capability instead. `--standalone` exists if and only if the
interface is v2, and `--help` says so on all four real binaries, including
the `0.0.0-*` pair no regex can separate: v1 stable, v1 dev, v2 beta and
v2 stable all answer correctly. v1 writes its help to stderr and v2 to
stdout, so both streams are read.

The probe now answers a tri-state: true (OpenCode 2), false (OpenCode 1),
or null (no answer). It never rejects: execFile can throw synchronously (a
.cmd launcher since CVE-2024-27980) and a failed or timed-out --help is no
answer either. On null, `apex run` adds neither --model nor --standalone,
so the assistant still starts on the user's own config, and says so on
stderr rather than guessing a generation and dying on its flag; the init
summary admits the same instead of blindly showing the v1 expansion.

Also scrub OPENCODE_CONFIG_CONTENT in the test fixture: it reached launched
fakes from the developer's shell, the same leak callstack#11 closed for gateway keys.

Docs-only inaccuracies PR callstack#12 merged (the "flags only follow a subcommand"
comment, the `auth list` example) are untouched here and tracked as F6.
The fanout review found no bugs; these are its confirmed findings.

- openCodeSupportsStandalone becomes a short async function over
  promisify(execFile): one try/catch covers the callback's error branch
  and what execFile throws synchronously (D2).
- A failing or timed-out --help that printed --standalone is an answer
  after all; the captured streams on the error are read instead of
  thrown away (D1).
- launchOptions owns the unknown-probe sentence as `warning` and says
  "interface", not "version"; commandRun prints it instead of writing
  its own wording of the same state (D6, D9).
- The init summary drops the note when no binary was found: nothing was
  asked, so nothing is unknown (D5).
- runExpansion joins the command tokens instead of repairing a trailing
  space with trimEnd() (D10).
- Tests: the synchronous-throw case uses a path that actually reaches
  execFile (a NUL byte); the probe asserts a failed --help with and
  without the flag in its output; the CLI fake sheds the --version
  plumbing Apex no longer reads and the stderr cases the probe unit
  test already covers (D3, D8).
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