Skip to content

feat(acq-kits): install OpenCode v2 via opencode kit - #466

Merged
mogul merged 2 commits into
mainfrom
witty-bumblebee
Oct 5, 2026
Merged

mogul merged 2 commits into
mainfrom
witty-bumblebee

Conversation

@mogul

@mogul mogul commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Update the opencode acq kit to install the current stable OpenCode 2-series CLI, 2.0.22, from pinned npm platform tarballs instead of the old GitHub release-binary path.

The kit now fetches and verifies:

Content Type

  • Skill
  • Prompt
  • Workflow
  • Agent instructions
  • Lesson learned
  • Integration kit

Changes

  • Pin OpenCode to 2.0.22 and update SHA-256 values for supported Linux platform tarballs.
  • Change egress from GitHub release hosts to registry.npmjs.org.
  • Add x64 AVX2 detection so non-AVX2 x64 hosts use the upstream baseline package.
  • Keep direct download, SHA-256 verification, archive layout checks, and fail-closed integrity behavior.
  • Update README, troubleshooting, kit registry docs, and the v2 migration ADR.
  • Expand offline verifier coverage for positive install, x64 AVX2 selection, x64 baseline selection, and npm package archive layout.

Safety Checklist

  • No secrets, tokens, credentials, or private keys
  • No PII, CUI, customer data, or sensitive info
  • No internal URLs or system details
  • Examples use placeholders where applicable
  • prohibited_content not applicable; this is an integration kit update, not a pattern file

Quality Checklist

  • Frontmatter not applicable; no pattern files added
  • Content is reusable beyond one project
  • Prerequisites documented
  • Verification steps included
  • Status is not applicable; no new pattern added
  • Tool compatibility tested where feasible

Verification Transcript

$ integrations/isolation/acq-kits/opencode/scripts/verify
0. Preconditions
  PASS kit spec.yaml present
  PASS install script present

1. Offline schema + registry validation
  SKIP python3 with jsonschema+pyyaml not available (pip install jsonschema pyyaml)

2. Install script syntax
  PASS opencode-install.sh parses as valid sh

3. INTEGRITY NEGATIVE: a wrong pinned hash refuses to install (fail-closed)
  PASS wrong-hash run refused to install anything (dest dir stayed empty)
  PASS mismatch produced the expected SECURITY:-prefixed refusal message

4. POSITIVE INSTALL: valid npm-layout archive installs the binary
  PASS valid npm-layout archive installed an executable opencode binary
  PASS valid npm-layout archive installed a runnable binary reporting the pin
  PASS host arm64 path selected the arm64 npm platform tarball

5. ARCHITECTURE SELECTION: x64 AVX2 and baseline packages are selected correctly
  PASS x64 host with AVX2 selects the x64 npm platform tarball
  PASS x64 host without AVX2 selects the baseline npm platform tarball

6. ARCHIVE LAYOUT NEGATIVE: a hardlink member refuses to install (fail-closed)
  PASS hardlink-member archive refused to install anything (dest dir stayed empty)
  PASS hardlink member produced the expected SECURITY:-prefixed refusal message

7. ARCHIVE LAYOUT NEGATIVE: duplicate opencode entries refuse to install (fail-closed)
  PASS duplicate-member archive refused to install anything (dest dir stayed empty)
  PASS duplicate member produced the expected SECURITY:-prefixed refusal message

8. ARCHIVE LAYOUT NEGATIVE: duplicate regular opencode entries refuse to install (fail-closed)
  PASS duplicate-regular-member archive refused to install anything (dest dir stayed empty)
  PASS duplicate regular members produced the expected SECURITY:-prefixed refusal message

9. Live sandbox check
  SKIP RUN_ACQ=1 not set - skipped live acq sandbox verification

Summary
  16 passed, 0 failed
$ TMPDIR=/var/tmp bash -lc 'tmp=$(mktemp -d /var/tmp/opencode-real.XXXXXX) && cp integrations/isolation/acq-kits/opencode/files/home/opencode-install.sh "$tmp/install.sh" && perl -0pi -e "s|dest_dir=\"/usr/local/bin\"|dest_dir=\"$tmp/dest\"|" "$tmp/install.sh" && OPENCODE_VERSION="2.0.22" OPENCODE_SHA256_LINUX_X64="65434cbe256f23df397a4941039e5eee28b1a37c1bad5376a5217f583a353583" OPENCODE_SHA256_LINUX_X64_BASELINE="acb904a82b570106d40e212061bfe0852900768d4722e7fc998908c086c1cb93" OPENCODE_SHA256_LINUX_ARM64="49e5466de60f65001cddd7583419f842140697daead2b4d8826be54d9056e70b" sh "$tmp/install.sh" && OPENCODE_VERSION="2.0.22" OPENCODE_SHA256_LINUX_X64="65434cbe256f23df397a4941039e5eee28b1a37c1bad5376a5217f583a353583" OPENCODE_SHA256_LINUX_X64_BASELINE="acb904a82b570106d40e212061bfe0852900768d4722e7fc998908c086c1cb93" OPENCODE_SHA256_LINUX_ARM64="49e5466de60f65001cddd7583419f842140697daead2b4d8826be54d9056e70b" sh "$tmp/install.sh" && "$tmp/dest/opencode" --version && rm -rf "$tmp"'
opencode-kit(install): sha256 verified for cli-linux-arm64-2.0.22.tgz (2.0.22) from https://registry.npmjs.org/@opencode/cli-linux-arm64/-/cli-linux-arm64-2.0.22.tgz
opencode-kit(install): opencode 2.0.22 (arm64) installed and runnable at /var/tmp/opencode-real.E1MNJO/dest/opencode
opencode-kit(install): opencode 2.0.22 (arm64) already installed and runnable at /var/tmp/opencode-real.E1MNJO/dest/opencode; skipping.
opencode v2.0.22
$ sh -n integrations/isolation/acq-kits/opencode/files/home/opencode-install.sh
PASS

$ bash -n integrations/isolation/acq-kits/opencode/scripts/verify
PASS

$ git diff --check
PASS

Blocked/partial checks:

$ make validate
make: python: No such file or directory
make: *** [Makefile:34: validate] Error 127
$ python3 scripts/validate_repo.py
ModuleNotFoundError: No module named 'jsonschema'
$ python3 integrations/isolation/acq-kits/validate-kits.py --strict
ModuleNotFoundError: No module named 'jsonschema'

Security Impact

This changes the agent-harness installation source from GitHub release tarballs to official npm platform tarballs. It preserves SHA-256 verification before extraction, rejects unexpected archive layouts, removes a non-runnable installed binary, and does not run npm lifecycle scripts.

Rollback

Revert commit 8029510 to restore the previous OpenCode 1.18.34 GitHub release-tarball install path.

AI Disclosure

This PR was prepared with OpenCode AI assistance. Human review is required before merge.

@mogul
mogul requested a review from a team as a code owner October 4, 2026 00:57
@mogul
mogul force-pushed the witty-bumblebee branch 2 times, most recently from 6c81f18 to 6423ca6 Compare October 4, 2026 04:46
@mogul mogul changed the title feat(acq-kits): install OpenCode v2 in opencode kit feat(acq-kits): install OpenCode v2 via opencode kit Oct 4, 2026
@mogul mogul self-assigned this Oct 5, 2026
@basiliskus

Copy link
Copy Markdown
Contributor

Reviewed at 6423ca6.

This migrates the OpenCode acq kit from the 1.18.34 GitHub release binary to pinned 2.0.22 npm platform tarballs, adds AVX2/baseline variant selection, and expands the kit's offline verifier and docs. It also replaces the repository's markdownlint implementation and deletes the newest generated changelog section, neither of which is part of the stated intent.

Intent: The npm tarball selection, pre-extraction hash check, archive-member checks, and offline tests match the stated migration. But the upgrade failure path can retain the old v1 agent instead of leaving it absent, and three unrelated repository changes regress release history, markdownlint's single-source file selection, and a CI guard.

Findings (4)

  1. Blocking: A failed v2 install leaves the old v1 binary installed and runnable. The only rm -f "$dest_dir/opencode" is in the final "installed but not runnable" branch; the 13 degrade paths that precede it, after the idempotency check (curl failure, missing sha256sum, hash mismatch, bad member listing, extraction failure, symlink member, non-executable member, install failure) all leave whatever was already at /usr/local/bin/opencode. Reproduced with a 1.18.34 stub and a failing curl: the script logged "opencode will be absent this boot", exited 0, and the old executable remained runnable. The idempotency check at line 146 shows a pre-existing different version is contemplated, so this is reachable on any ordinary upgrade. It also defeats the readiness gate: spec.yaml declares the agent: block specifically so acq can "confirm this kit genuinely provides the opencode agent before enabling it", and a surviving v1 satisfies that gate — so a sandbox requested as v2 reports ready while running v1. Remove an existing wrong-version binary before attempting replacement, or remove it on every failed replacement path.
    integrations/isolation/acq-kits/opencode/files/home/opencode-install.sh:286
    integrations/isolation/acq-kits/opencode/files/home/opencode-install.sh:146
  2. Blocking: The "assert markdownlint actually linted files" guard now passes unconditionally. That step exists because, in its own words, "a markdownlint-cli2 run that matched no files still exits 0, so assert the file count is non-zero rather than trusting the exit status alone". It parses Linting: N file from the output. Real markdownlint-cli2 prints the number of files matched; the new local shim prints `Linting: ${patterns.length} file pattern(s)`, where patterns is the globs list from .markdownlint-cli2.yaml — a single entry, **/*.md. The count is therefore the constant 1 no matter how many files were linted, so the assertion can never fail and the guard is dead. Feeding the shim's exact output to the CI sed expression yields 1. Relatedly, .github/linters/package.json still describes itself as "Pinned linter dependencies for CI supply chain security" while the real markdownlint-cli2: 0.23.3 pin is replaced by an unversioned file:./markdownlint-cli2 stub.
    .github/workflows/ci.yml:127
    .github/linters/markdownlint-cli2/index.js:27
  3. Blocking: Local and CI markdownlint read different configuration and ignore sources, reintroducing known drift. The new pre-commit hook invokes markdownlint with --config .markdownlint.jsonc --ignore-path .markdownlintignore, while CI still invokes markdownlint-cli2, which reads .markdownlint-cli2.yaml. The ignore sets already disagree: templates (8 markdown files) and **/fixtures/** are ignored only by CI, while tmp and test/vendor are ignored only locally. Commit 543e08f removed exactly this drift by consolidating file selection into .markdownlint-cli2.yaml, and that file's own comment still claims to be "the SINGLE source of truth for file selection". Keep selection in one source, or update CI to consume the replacement configuration.
    .pre-commit-config.yaml:43
    .github/workflows/ci.yml:120
  4. Blocking: The published 1.11.0 release section is deleted from the changelog. The base commit is the release-please 1.11.0 commit and carries that heading plus its three entries; this revision's changelog starts at 1.10.0. Merging would make the changelog claim 1.10.0 is current and lose released history. Unrelated to the OpenCode v2 migration.
    CHANGELOG.md:3

Validation gaps: The repository's own validators did not run for either the author or this review — make validate, scripts/validate_repo.py, and validate-kits.py --strict are all blocked by a missing Python jsonschema module, so schema and kit validation is unverified locally despite the green CI check. The live path is unexercised: RUN_ACQ=1 was not set and no sandbox was created, so the real npm download, registry.npmjs.org egress, binary compatibility, and end-to-end acq wiring are unconfirmed. The three pinned SHA-256 values were not independently re-derived from the real tarballs, which is the step spec.yaml instructs when bumping. The offline verifier's 16 passing checks skip schema/registry and live validation, and it has no case for upgrading from an already-installed wrong version through a later failure, which is precisely the gap behind finding 1.

Below the bar: The same-version idempotency path trusts the installed binary's self-reported --version without re-hashing it; verified real, but it requires prior write access to the root-owned destination and predates this change.

Verdict: needs changes

Three of the four blocking findings (2, 3, 4) are in files unrelated to installing OpenCode and would be easier to review, and to revert, as a separate change.

Assisted-by: OpenCode [gpt-5.6-sol-latest-guardrails-defaultv2, claude-opus-5] team-code-review

@mogul

mogul commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review feedback in e62224d.

Changes made:

  • Fixed the stale-binary upgrade failure: the installer now removes any non-pinned existing $dest_dir/opencode before degraded replacement paths, so a failed v2 install leaves the agent absent instead of preserving a runnable v1 binary.
  • Added offline verifier coverage for the reported stale-v1 scenario with a failing download.
  • Added coverage for an early pin-validation failure (OPENCODE_VERSION=not-a-version) with a stale existing binary, after an adversarial pass found the first fix was too late for some early exits.
  • Removed the unrelated markdownlint shim/config/pre-commit deltas and restored the released CHANGELOG.md section from main, so this PR is again scoped to the OpenCode kit migration.

On the markdownlint issue: I agree it should not be fixed independently in each acq-kit PR. The aligned fix is the shared markdownlint dependency/config path from GSA-TTS/agentic-coding-patterns#451, which is already on main; this branch now relies on that instead of carrying a local shim. I also checked nearby open PRs: some already removed similar one-off markdownlint changes in response to review, which supports keeping this as the centralized fix rather than repeating it per feature branch.

Verification:

$ sh -n integrations/isolation/acq-kits/opencode/files/home/opencode-install.sh
PASS

$ bash -n integrations/isolation/acq-kits/opencode/scripts/verify
PASS

$ git diff --check
PASS
$ PATH="/tmp/opencode/patterns-venv/bin:$PATH" integrations/isolation/acq-kits/opencode/scripts/verify
0. Preconditions
  PASS kit spec.yaml present
  PASS install script present

1. Offline schema + registry validation
  PASS validate-kits.py --strict passes (schema, files[].source, agent block, registry)

2. Install script syntax
  PASS opencode-install.sh parses as valid sh

3. INTEGRITY NEGATIVE: a wrong pinned hash refuses to install (fail-closed)
  PASS wrong-hash run refused to install anything (dest dir stayed empty)
  PASS mismatch produced the expected SECURITY:-prefixed refusal message

4. FAILED UPGRADE: a stale wrong-version binary is removed before degraded install
  PASS failed upgrade removed the stale wrong-version opencode binary
  PASS failed upgrade logs removal before the degraded download failure
  PASS invalid-version upgrade removed the stale wrong-version opencode binary

5. POSITIVE INSTALL: valid npm-layout archive installs the binary
  PASS valid npm-layout archive installed an executable opencode binary
  PASS valid npm-layout archive installed a runnable binary reporting the pin
  PASS host arm64 path selected the arm64 npm platform tarball

6. ARCHITECTURE SELECTION: x64 AVX2 and baseline packages are selected correctly
  PASS x64 host with AVX2 selects the x64 npm platform tarball
  PASS x64 host without AVX2 selects the baseline npm platform tarball

7. ARCHIVE LAYOUT NEGATIVE: a hardlink member refuses to install (fail-closed)
  PASS hardlink-member archive refused to install anything (dest dir stayed empty)
  PASS hardlink member produced the expected SECURITY:-prefixed refusal message

8. ARCHIVE LAYOUT NEGATIVE: duplicate opencode entries refuse to install (fail-closed)
  PASS duplicate-member archive refused to install anything (dest dir stayed empty)
  PASS duplicate member produced the expected SECURITY:-prefixed refusal message

9. ARCHIVE LAYOUT NEGATIVE: duplicate regular opencode entries refuse to install (fail-closed)
  PASS duplicate-regular-member archive refused to install anything (dest dir stayed empty)
  PASS duplicate regular members produced the expected SECURITY:-prefixed refusal message

10. Live sandbox check
  SKIP RUN_ACQ=1 not set - skipped live acq sandbox verification

Summary
  20 passed, 0 failed
$ PATH="/tmp/opencode/patterns-venv/bin:$PATH" make validate
PASS - All validators passed; existing advisory warnings only

$ PATH="/tmp/opencode/patterns-venv/bin:$PATH" make validate-kits
PASS - All 10 acq-kits valid

$ /tmp/opencode/patterns-venv/bin/python scripts/scan_unsafe_shell.py .
PASS - unsafe-shell scan: 0 error(s), 0 warning(s)

I also ran two adversarial review passes after the fix. The first caught the early-exit stale-binary gap; after moving cleanup earlier and adding the invalid-version regression, the second reported no blocking findings. Residual known risk remains unchanged from the original review: same-version idempotency trusts the installed binary's --version rather than re-hashing it.

Still blocked locally: live RUN_ACQ=1 sandbox verification was not run in this environment.

@mogul
mogul changed the base branch from main to fix/markdownlint-audit October 5, 2026 17:49
@mogul
mogul added this pull request to stack #472 October 5, 2026 17:49
Base automatically changed from fix/markdownlint-audit to main October 5, 2026 21:39
Update the opencode acq kit to install OpenCode v2 and switch markdownlint to an auditable CLI path.

@basiliskus basiliskus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at cdc27b59. This head is a pure rebase of e62224d9 onto dd22daff: the reviewed base-to-head diff is byte-identical, so the verdict and exit-path audit carry over unchanged. My earlier comment was pinned to 6423ca69 and is now stale — all 4 findings are resolved.

Finding 1 (stale binary on a failed upgrade) — resolved. This was the one that needed real checking, since presence of a removal isn't the same as correct placement, and your own note said the first attempt landed too late for some early exits.

I enumerated every non-success termination in opencode-install.sh: 20 outcomes. 19 exit 0, and all 19 remove a pre-existing wrong-version binary before terminating — the three validation exits (:98, :110, :117) each call removal explicitly, and everything downstream is covered by the unconditional removal at :139. The post-install fallthrough at :305-306 removes a non-runnable replacement as well.

The only outcome that preserves a stale binary is "existing destination cannot be removed" (:85), and that exits 1. That's the right shape: the original concern was a false-ready at the wrong version, which needs exit 0 to satisfy the agent: readiness gate. An exit-nonzero path that leaves the old binary in place can't produce that.

The new tests are causal, which is what makes this convincing. I ran the verifier myself rather than relying on the output in your comment: 19 passed, 0 failed at this head. I then re-ran the same verifier with only the installer swapped back to 6423ca69 and got 16 passed, 3 failed — stale v1 survived both the download failure and the invalid-version validation, and the removal-before-download ordering assertion failed. The new assertions fail against pre-fix code for the intended reason.

One coverage note, not a blocker: the two scenarios pin one early branch and one downstream branch rather than the whole degraded class. A table-driven stale-destination test across each injectable failure class would catch a future exit added above the removal point. Worth considering, not worth holding the PR for.

Findings 2, 3 and 4 — resolved. CHANGELOG.md is out of the diff, so the published 1.11.0 section is intact. .pre-commit-config.yaml, .github/workflows/ci.yml and .github/linters/ are all absent, which restores the single-source markdownlint path from GSA-TTS/agentic-coding-patterns#451 and with it the real markdownlint runner and the original non-zero file-count assertion. I agree with your reasoning on keeping that fix central.

The earlier Markdown Lint failure was inherited from main, and it was partly a consequence of my own review feedback asking you to drop the local shim. You then fixed it centrally in GSA-TTS/agentic-coding-patterns#471 by replacing markdownlint-cli2 with [email protected] plus the js-yaml override. That is the better outcome: this PR is now 7/7 green, the repository keeps one markdownlint path, and dropping the hand-rolled walker avoids both defeating the CI file-count assertion and introducing subtle behavior differences such as the symlink-handling case you caught.

What I did not verify, stated plainly. My assessment is on the static merits only. The live install path has still never been executed by either review of this PR:

  • RUN_ACQ=1 verification: BLOCKED, acq unavailable. No sandbox, no real npm download, no egress, no binary compatibility, no end-to-end readiness wiring.
  • make validate (Error 127, no python), scripts/validate_repo.py and validate-kits.py --strict (both ModuleNotFoundError: jsonschema): all BLOCKED in my environment. CI's Pattern Validation is green, but I didn't read its raw logs.
  • The three pinned npm SHA-256 values are unchanged from 6423ca69 and were never independently re-derived, here or in the first pass. spec.yaml's own comment asks for that on a bump. If anyone has re-derived them from the real tarballs, that's worth saying on the PR.
  • Residual risk you already acknowledged, unchanged: the same-version idempotency path trusts the installed binary's --version rather than re-hashing it.

So from my side the blocking findings are closed and I have no new ones. The gap that remains is live execution, not code.

Thanks for the thorough fix and for the adversarial pass that caught the placement problem before I saw it.

Remove a non-pinned existing opencode binary before degraded replacement paths so a failed OpenCode v2 install cannot leave a stale v1 binary satisfying readiness checks.

Also drop unrelated markdownlint and changelog deltas from this PR branch; those should be handled by the shared markdownlint dependency fix instead.

Co-authored-by: OpenCode Agent <[email protected]>
@mogul

mogul commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

I fixed a couple of cpu detection differences that only appeared on my MacOS host through live verification. They're minor enough that I'm not going to ask for a re-review. Merging!

@mogul
mogul merged commit dc84c6c into main Oct 5, 2026
7 checks passed
@mogul
mogul deleted the witty-bumblebee branch October 5, 2026 23:26
wz-gsa added a commit that referenced this pull request Oct 6, 2026
…im comment narration per mogul's review

Two fixes, both from mogul's CHANGES_REQUESTED review:

1. Rebase onto main pulled in the opencode kit (#466), which landed with a
   verify script in neither CONVERTED nor UNCONVERTED, failing
   test_every_kit_is_classified exactly as the test is designed to catch
   (reproduced locally before fixing: `AssertionError: verify script(s) in
   no classification set: ['opencode']`). opencode's verify script is on
   the old two-state verdict, so it goes in UNCONVERTED alongside the other
   not-yet-migrated kits.

2. Trimmed six comment/docstring blocks that narrated the OLD two-state
   verdict's history ("used to end with...", "the old reporters had no...",
   the oci-engine near-miss anecdote) instead of describing the current
   code. Per review: this kind of narration belongs in the commit message,
   not in the code, where it has no reader value and persists as noise.
   Trimmed in:
   - integrations/isolation/acq-kits/git-ssh-sign/scripts/verify
   - integrations/isolation/acq-kits/usai-provider/scripts/verify
   - integrations/isolation/acq-kits/README.md
   - integrations/isolation/acq-kits/verify-report.sh (module header)
   - scripts/tests/test_verify_contract.py (module docstring + 2 inline
     comments + 1 docstring)

   Comment-only / allowlist-entry changes: no test logic, no shell logic,
   no shared-library behavior changed. Per the review's own standing offer
   ("if you respond with improvements that only land in comments... feel
   free to check the box that skips the require-review gate"), this should
   qualify to merge without a second review round-trip.

Verification:
- PYTHONPATH=scripts python3 -m pytest scripts/tests/: 420 passed (was
  failing 1/420 before the opencode classification fix, reproduced first).
- python3 scripts/validate_repo.py: all validators pass.

Co-authored-by: OpenCode Agent <[email protected]>
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.

2 participants