Skip to content

opencode engine: --ignore-scripts install cannot satisfy the adjacent opencode --version verify step for opencode-ai 1.15.1+ #50114

Description

@funkymonkeyjam

Summary

The compiled workflow for engine: opencode emits two adjacent steps that contradict each other. The install step skips npm lifecycle scripts; the verify step immediately requires the binary that only the skipped postinstall provides.

This is masked today by the default version pin. gh-aw pins opencode-ai@1.2.14, which predates the point at which that package's postinstall became load-bearing, so smoke-opencode passes. Any user who sets engine.version to 1.15.1 or later -- including the current latest, 1.16.2 -- gets a guaranteed hard failure before the agent container starts.

The generated steps

From .github/workflows/smoke-opencode.lock.yml on main (lines 580-584, and again at 1608-1612 for the non-rootless job):

- name: Install OpenCode
  run: npm install --ignore-scripts -g opencode-ai@1.2.14
  env:
    NPM_CONFIG_MIN_RELEASE_AGE: '3'
- name: Verify OpenCode CLI installation
  run: opencode --version

Observed failure

With engine: { id: opencode, version: "1.18.4" }:

Error: opencode-ai's postinstall script was not run.

This occurs when using --ignore-scripts during installation, or when using a
package manager like pnpm that does not run postinstall scripts by default.

To fix this, run the postinstall script manually:
  cd node_modules/opencode-ai && node postinstall.mjs

Or reinstall opencode-ai without the --ignore-scripts flag.
##[error]Process completed with exit code 1.

Reproduced on gh-aw v0.83.2 and v0.83.3, byte-identical in both. The two steps are unchanged on main as of v0.84.3.

Why the pinned version hides it

The postinstall script exists in both old and new opencode-ai; what changed is whether it is mandatory.

1.2.14 (gh-aw default) 1.15.0 1.15.1 1.16.2 (latest)
bin.opencode bin/opencode bin/opencode bin/opencode.exe bin/opencode.exe
scripts.postinstall present present present present
survives --ignore-scripts yes yes no no

At tag v1.15.1 of anomalyco/opencode, packages/opencode/script/publish.ts began generating the published bin entry as a stub whose entire body is the error above followed by exit 1:

await Bun.file(`./dist/${pkg.name}/bin/${pkg.name}.exe`).write(
  [
    `echo "Error: ${pkg.name}-ai's postinstall script was not run." >&2`,
    ...
    "exit 1",

At v1.15.0 the same script ships the real launcher instead (cp -r ./bin ./dist/${pkg.name}/bin), and that launcher resolves the native binary at runtime by walking node_modules, with the native binaries arriving as optionalDependencies -- which npm installs regardless of --ignore-scripts. So before 1.15.1 the postinstall was an optimization; from 1.15.1 it is the only thing that puts a working binary in place.

The v1.15.1 release notes do not describe this as a breaking change, which is likely why it has gone unnoticed here.

Environment

  • gh-aw v0.83.2, v0.83.3; steps confirmed unchanged at v0.84.3
  • engine: { id: opencode, version: "1.18.4" }
  • runner ubuntu-latest, workflow_dispatch
  • AWF v0.27.41 (repo main now pins v0.27.43), rootless
  • Job order observed: Install AWF binary -> Install OpenCode -> Verify OpenCode CLI installation (fails) -> all container steps skipped

Why a user cannot work around it

Root-level steps: are emitted before Install OpenCode in the agent job, so a user-supplied step cannot run the postinstall after the fact. There is no frontmatter knob to drop --ignore-scripts, and editing the compiled .lock.yml defeats the point of compiling. The only available workaround is to stay on a pre-1.15.1 opencode-ai.

Suggested fix

I want to be explicit that I am not asking for --ignore-scripts to be dropped globally. #30832 established that it is a deliberate supply-chain control -- the npm install runs on the host runner before the AWF sandbox starts, with the workflow's secrets in scope -- and PR #31379 restored it after a regression. That reasoning is sound and this report does not ask to weaken it.

Preferred: treat opencode the way crush was treated in #29735. That PR changed GetInstallationSteps to call GenerateNpmInstallSteps with runInstallScripts=true for the crush engine only, described as "mirroring how the Claude engine handles its install", and added a post-install crush --version step to force deferred binary downloads. The justification given there -- "requires post-install scripts to download native binaries. With --ignore-scripts, these scripts are skipped and the binary is never downloaded, causing the crush command to fail at runtime" -- now describes opencode-ai at 1.15.1+ exactly. opencode already has the equivalent verify step; that step is precisely what fails. The global default and every other engine stay untouched.

One nit worth not copying forward: the review on #29735 flagged the commandName / EngineConfig.Command block preceding versionStep as unreachable dead code, and it merged unfixed.

Alternative, if a narrower blast radius is preferred: keep --ignore-scripts and run only the top-level package's postinstall explicitly, so transitive dependency scripts still never execute:

- name: Install OpenCode
  run: |
    npm install --ignore-scripts -g opencode-ai@1.2.14
    node "$(npm root -g)/opencode-ai/postinstall.mjs"

This is strictly tighter than the crush precedent, since it executes exactly one known script rather than the whole tree's. It does mean the compiler carries a package-specific path.

Either way, worth adding a smoke case pinned to a 1.15.1+ opencode-ai. The current smoke-opencode pin cannot catch this class of regression.

Context

Found while running a security probe that treats the agent as hostile and tests whether the provider credential is recoverable from inside the agent container. The probe is designed so that a run which does not happen cannot be reported as a pass -- which is how this surfaced as a hard stop rather than a silent clean sweep.

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions