From 272e041833b8f7b909d039f6f86f03d5615d28d6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 8 Apr 2026 10:16:30 +0000 Subject: [PATCH 1/5] chore: update Homebrew formula for v0.3.0 --- homebrew/archon.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/homebrew/archon.rb b/homebrew/archon.rb index 0f01ac2202..d52f4d42aa 100644 --- a/homebrew/archon.rb +++ b/homebrew/archon.rb @@ -7,28 +7,28 @@ class Archon < Formula desc "Remote agentic coding platform - control AI assistants from anywhere" homepage "https://github.com/coleam00/Archon" - version "0.2.13" + version "0.3.0" license "MIT" on_macos do on_arm do url "https://github.com/coleam00/Archon/releases/download/v#{version}/archon-darwin-arm64" - sha256 "e62eb73547b3740d56f242859b434a91d3830360a0d18f14de383da0fd7a0be6" + sha256 "2ff39add5306d839b28e05e58a98442a55d7b1a27d3045999ca62e9ccc7557b9" end on_intel do url "https://github.com/coleam00/Archon/releases/download/v#{version}/archon-darwin-x64" - sha256 "d5291ce5484e3bd43242d24ba92e0791c504b5bef84f0dbf59cda044e67f6096" + sha256 "7d5719a00e95d05303e0fd2586f6d69c41102bde1e23b11aa7e662905c235100" end end on_linux do on_arm do url "https://github.com/coleam00/Archon/releases/download/v#{version}/archon-linux-arm64" - sha256 "923d562e45c371719bda5d42236cc5c0ff0ff1942321c41bddc1410e79aded3a" + sha256 "8bf7c0a335455b10f7362902d78b2b9a90778d4d2e979153ab5b114d4edb996c" end on_intel do url "https://github.com/coleam00/Archon/releases/download/v#{version}/archon-linux-x64" - sha256 "0cf83e15e6af228e3c3473467ca30fa7525b6d7069818d85f97a115ea703d708" + sha256 "f1f730ebea4d77e6fa533a8fbdd194fb356ddc441160fae5f63f6225c27ff8fc" end end From 4579422e4195607fcea95b362decf181ba204272 Mon Sep 17 00:00:00 2001 From: Rasmus Widing Date: Wed, 8 Apr 2026 14:36:34 +0300 Subject: [PATCH 2/5] skill(test-release): add smoke-test skill for released binaries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automates release verification via three install paths: brew (Homebrew tap), curl-mac (install.sh to a sandboxed tmp dir on macOS), and curl-vps (install.sh on a remote Linux VPS). Runs a fixed suite (version, workflow list, assist workflow, env-leak gate, isolation list), captures SHA256 of the tested binary, and cleans up on exit so the dev bun-link binary is never disturbed. Use when cutting a new release or reproducing a user bug report on the released version. NOT for testing dev changes — those use bun run validate or direct source invocation. See issue (to be filed) for the release workflow fix that unblocks the brew and curl-mac paths end-to-end. --- .claude/skills/test-release/SKILL.md | 394 +++++++++++++++++++++++++++ 1 file changed, 394 insertions(+) create mode 100644 .claude/skills/test-release/SKILL.md diff --git a/.claude/skills/test-release/SKILL.md b/.claude/skills/test-release/SKILL.md new file mode 100644 index 0000000000..b9001bc92d --- /dev/null +++ b/.claude/skills/test-release/SKILL.md @@ -0,0 +1,394 @@ +--- +name: test-release +description: | + Verify a released archon binary works end-to-end via a specific install path. + Use when: cutting a new release, reproducing a user bug report on the released + version, or validating that a hotfix binary actually works after a re-tag. + Triggers: "test the release", "test 0.3.1 via brew", "verify the curl install", + "smoke test the binary", "did the release binary work", + "run /test-release", "verify the release". + NOT for: testing dev work (use bun link directly), testing unreleased changes + (build locally via scripts/build-binaries.sh first), or running the full + validate suite (bun run validate is separate). +argument-hint: "[brew|curl-mac|curl-vps] [optional: version to verify] [optional: vps-target]" +--- + +# Test Release + +Automated smoke test for a released archon binary. Covers three install paths: + +- `brew` — Homebrew tap on macOS (tests the formula and checksums) +- `curl-mac` — `curl install.sh` on macOS (tests the install script, sandboxed to a temp dir) +- `curl-vps` — `curl install.sh` on a remote Linux VPS (tests the Linux binary and full install path) + +Every path installs the binary, runs a fixed smoke test suite, and cleans up. The dev `bun link` binary is never touched and remains the default `archon` on PATH throughout. + +**When NOT to use this skill:** + +- There is no release yet — build a local binary via `bash scripts/build-binaries.sh` and run it from `dist/binaries/` directly +- You want to test the dev clone — use `bun run validate` or invoke source directly via `bun packages/cli/src/cli.ts` +- You want to test the full server + web UI deploy flow — use the cloud-init from `deploy/cloud-init.yml` on a real VPS + +## Phase 1 — Determine scope + +Parse the arguments. The skill takes up to three: + +1. **Install path** (`brew` | `curl-mac` | `curl-vps`): which install flow to exercise +2. **Expected version** (optional): the version tag the release should report, e.g. `0.3.1`. If not provided, fetch it: + +```bash +gh release list --repo coleam00/Archon --limit 1 --json tagName --jq '.[0].tagName' +``` + +3. **VPS target** (only for `curl-vps`): SSH target in the form `user@host` or `host` (uses default SSH config) + +If any argument is missing, ask the user for clarification BEFORE doing anything. Never guess the install path or the expected version. + +Confirm the plan with the user before proceeding to Phase 2. Output should look like: + +``` +About to test: + Path: brew (Homebrew tap on macOS) + Version: 0.3.1 (expected) + Cleanup: will uninstall after tests (brew uninstall + untap) + +Proceed? (y/N) +``` + +Do not continue without explicit confirmation. Release testing touches install state and the user should be aware. + +## Phase 2 — Pre-flight + +Before touching anything: + +1. Capture the current dev binary state for reference: + +```bash +which -a archon +archon version 2>&1 | head -5 +``` + +Record the path and version of the dev binary so the final report can show "dev binary was untouched". + +2. Verify prerequisites for the chosen path: + + - **brew**: `brew --version` must succeed. If not, abort with "Homebrew not installed — see https://brew.sh/" + - **curl-mac**: `curl --version` must succeed (effectively always true on macOS) + - **curl-vps**: `ssh 'uname -a'` must succeed. If not, abort with "Cannot SSH to ". Also verify `ssh 'command -v curl'` returns a path. + +3. Confirm the release exists on GitHub: + +```bash +gh release view v --repo coleam00/Archon --json tagName,assets --jq '{tag: .tagName, assetCount: (.assets | length)}' +``` + +If the release does not exist or has no assets, abort with a clear message. Do not proceed to install a non-existent release. + +## Phase 3 — Install + +### Path: brew + +```bash +brew tap coleam00/archon +brew install coleam00/archon/archon +BINARY="$(brew --prefix coleam00/archon/archon)/bin/archon" +``` + +Capture `$BINARY` for Phase 4. Verify the file exists and is executable. + +### Path: curl-mac + +Install to a dedicated tmp directory so the dev `bun link` binary stays on PATH unchanged: + +```bash +INSTALL_DIR=/tmp/archon-test-release-$(date +%s) +mkdir -p "$INSTALL_DIR" +INSTALL_DIR="$INSTALL_DIR" curl -fsSL https://raw.githubusercontent.com/coleam00/Archon/main/scripts/install.sh | bash +BINARY="$INSTALL_DIR/archon" +``` + +Verify `$BINARY` exists and is executable. Capture the install directory for cleanup. + +### Path: curl-vps + +Run the install script on the VPS: + +```bash +ssh 'curl -fsSL https://raw.githubusercontent.com/coleam00/Archon/main/scripts/install.sh | bash' +``` + +Determine where the binary landed — `install.sh` uses `/usr/local/bin/archon` by default, or falls back to `$HOME/.local/bin/archon` if `/usr/local/bin` is not writable: + +```bash +ssh 'command -v archon' +``` + +Capture the remote path as `$REMOTE_BINARY`. For the rest of Phase 4, wrap every command as `ssh ''`. + +### Capture SHA256 and version + +Immediately after install, capture: + +```bash +# Local paths (brew / curl-mac) +shasum -a 256 "$BINARY" | awk '{print $1}' +"$BINARY" version 2>&1 + +# Remote path (curl-vps) +ssh "shasum -a 256 $REMOTE_BINARY || sha256sum $REMOTE_BINARY" | awk '{print $1}' +ssh "$REMOTE_BINARY version" 2>&1 +``` + +Record both for the report. The SHA256 lets us confirm later that a user reporting a bug is running the exact same artifact we tested. + +## Phase 4 — Smoke tests + +Run these in order against `$BINARY` (or `ssh $REMOTE_BINARY` for curl-vps). **Always use the full binary path, never the `archon` on PATH**, so there is no ambiguity about which binary is under test. + +Each test should capture the full command output for the final report. If a test fails, continue to the next test (so the report is complete) but mark the overall result as FAIL. + +### Test 1 — Version reports correctly + +```bash +"$BINARY" version +``` + +**Pass criteria:** + +- Exit code 0 +- Output contains `Archon CLI v` +- Output contains `Build: binary` (not `Build: source (bun)`) +- Output contains a non-`unknown` git commit (i.e., `Git commit: `) + +**Common failures:** + +- Exit code non-zero → pino-pretty crash (#960) or similar startup failure +- Wrong version reported → binary is stale or the build script failed to update `bundled-build.ts` +- `Build: source (bun)` → `BUNDLED_IS_BINARY` was not set to `true` during the build (regression of #979) +- `Git commit: unknown` → build script did not capture the commit + +### Test 2 — Bundled workflows load + +Create a temporary git repository so the CLI has something to operate on: + +```bash +TESTREPO=/tmp/archon-test-repo-$(date +%s) +mkdir -p "$TESTREPO" +cd "$TESTREPO" +git init -q +git commit -q --allow-empty -m init +"$BINARY" workflow list +``` + +**Pass criteria:** + +- Exit code 0 +- Output lists at least 20 bundled workflows (archon-assist, archon-fix-github-issue, archon-comprehensive-pr-review, etc.) +- No errors about missing workflow files or JSON parse failures + +**Common failures:** + +- Empty list → bundled defaults were not embedded in the binary (regression of the `isBinaryBuild` detection path) +- `Not in a git repository` → working directory handling bug +- Parse errors → the embedded JSON is corrupt or stale + +### Test 3 — SDK path works (assist workflow) + +In the same `$TESTREPO`: + +```bash +"$BINARY" workflow run assist "say hello and nothing else" 2>&1 | tee /tmp/archon-test-assist.log +``` + +**Pass criteria:** + +- Exit code 0 +- The Claude subprocess spawns successfully (no `spawn EACCES`, `ENOENT`, or `process exited with code 1` in the early output) +- A response is produced (any response — even just "hello" — proves the SDK round-trip works) + +**Common failures:** + +- `Credit balance is too low` → auth is pointing at an exhausted API key (check `CLAUDE_USE_GLOBAL_AUTH` and `~/.archon/.env`) +- `unable to determine transport target for "pino-pretty"` → #960 regression, binary crashes on TTY +- `package.json not found (bad installation?)` → #961 regression, `isBinaryBuild` detection broken +- Process exits before producing output → generic spawn failure, capture stderr + +### Test 4 — Env-leak gate refuses a leaky .env (optional, for releases including #1036/#1038/#983) + +Create a second throwaway repo with a fake sensitive key: + +```bash +LEAKREPO=/tmp/archon-test-leak-$(date +%s) +mkdir -p "$LEAKREPO" +cd "$LEAKREPO" +git init -q +git commit -q --allow-empty -m init +printf 'ANTHROPIC_API_KEY=sk-ant-test-fake\n' > .env +"$BINARY" workflow run assist "hello" 2>&1 | tee /tmp/archon-test-leak.log +``` + +**Pass criteria:** + +- The command exits with a non-zero code, OR produces an error message containing `Cannot add codebase` or `Cannot run workflow` +- The error mentions the dangerous key name (`ANTHROPIC_API_KEY`) +- No Claude subprocess was actually spawned (the gate short-circuited) + +**Common failures:** + +- Command proceeds normally → the env-leak gate is not active (regression of #1036) +- Error is generic or unclear → the context-aware error message from #983 has regressed +- Gate blocks but with wrong remediation text → `formatLeakError` context detection is broken + +Clean up the leak test repo: + +```bash +rm -rf "$LEAKREPO" +``` + +### Test 5 — Isolation list works (sanity check) + +In the same `$TESTREPO`: + +```bash +"$BINARY" isolation list +``` + +**Pass criteria:** + +- Exit code 0 +- No errors (the list may be empty if no worktrees have been created, which is fine) + +This catches regressions in the isolation subsystem that would not surface from the other tests. + +### Test 6 — Cleanup test repos + +```bash +rm -rf "$TESTREPO" +``` + +For `curl-vps` path, also clean up any remote test repos created via SSH. + +## Phase 5 — Uninstall + +**Always run uninstall, even if Phase 4 failed.** The goal is to leave the system in the same state as before the test. + +### Path: brew + +```bash +brew uninstall coleam00/archon/archon +brew untap coleam00/archon +``` + +Verify the dev binary is still the default: + +```bash +which -a archon +# should show only the ~/.bun/bin/archon path, not a brew path + +archon version | head -1 +# should match the dev version captured in Phase 2 +``` + +### Path: curl-mac + +```bash +rm -rf "$INSTALL_DIR" +``` + +### Path: curl-vps + +```bash +ssh "sudo rm -f /usr/local/bin/archon || rm -f \$HOME/.local/bin/archon" +``` + +Optional: the user may want to LEAVE the VPS binary installed for ongoing QA. Ask before removing. + +## Phase 6 — Report + +Produce a structured report with: + +- **Header**: release version tested, install path, timestamp, SHA256 of the tested binary +- **Environment**: dev binary path + version (proof the dev install was not disturbed) +- **Test results table**: one row per test with PASS / FAIL / SKIP +- **Captured output**: for any FAIL, include the exact command, exit code, and last 20 lines of stderr/stdout +- **Overall verdict**: PASS if all tests passed, FAIL if any test failed +- **Next steps**: if FAIL, suggest concrete actions (file a hotfix issue, re-tag, check the build workflow, etc.) + +Example PASS report: + +``` +Test Release Report — archon v0.3.1 via brew +──────────────────────────────────────────── +Tested at: 2026-04-08 15:42 UTC +Binary SHA: e62eb73547b3740d56f242859b434a91d3830360a0d18f14de383da0fd7a0be6 +Binary path: /opt/homebrew/Cellar/archon/0.3.1/bin/archon +Dev binary: /Users/rasmus/.bun/bin/archon → ../install/.../cli.ts (unchanged) + + [PASS] Test 1 version reports 0.3.1, Build: binary, commit abc1234 + [PASS] Test 2 workflow list returned 21 bundled workflows + [PASS] Test 3 workflow run assist produced output + [PASS] Test 4 env-leak gate refused leaky .env with context-aware error + [PASS] Test 5 isolation list executed without errors + [PASS] Cleanup brew uninstall + untap clean, dev binary unchanged + +Overall: PASS + +This release is safe to announce. Next steps: + - Update release notes on GitHub if not done already + - Announce on whatever channels you use +``` + +Example FAIL report: + +``` +Test Release Report — archon v0.3.1 via curl-vps +──────────────────────────────────────────────── +Tested at: 2026-04-08 15:42 UTC +Binary SHA: 0cf83e15e6af228e3c3473467ca30fa7525b6d7069818d85f97a115ea703d708 +Binary path: user@vps:/usr/local/bin/archon +Dev binary: /Users/rasmus/.bun/bin/archon (unchanged) + + [PASS] Test 1 version reports 0.3.1, Build: binary + [FAIL] Test 2 workflow list returned 0 workflows + + Command: archon workflow list + Exit: 0 + Output: + Discovering workflows in: /tmp/archon-test-repo-1712590923 + Found 0 workflow(s): + + [SKIP] Test 3 SDK test skipped because Test 2 failed + [SKIP] Test 4 env-leak gate test skipped because Test 2 failed + [PASS] Test 5 isolation list executed without errors + [PASS] Cleanup VPS binary removed + +Overall: FAIL + +Likely cause: bundled workflows were not embedded in the binary. +Check the build workflow for missing asset embedding, or verify that +BUNDLED_WORKFLOWS in packages/workflows/src/defaults/bundled-defaults.ts +was populated at build time. + +Next steps: + 1. File a P0 hotfix issue with the captured output + 2. Do NOT announce v0.3.1 until the hotfix ships as v0.3.2 + 3. Consider adding a CI guard that blocks releases if BUNDLED_WORKFLOWS is empty +``` + +## Key behaviors + +- **Never touch the dev `bun link` binary.** Always use the installed binary path for Phase 4 tests. Verify before and after. +- **Clean up on failure.** If Phase 4 fails mid-way, still run Phase 5 so the next run starts clean. +- **Capture SHA256 immediately after install.** This lets bug reports reference the exact artifact under test. +- **Explicit confirmation before install.** Never surprise the user by installing a second binary. +- **Report the dev binary state in both preamble and postamble.** Proof that the test did not disturb the dev environment. +- **Exit non-zero if any test failed.** The skill should propagate failure so automated wrappers (CI, scripts) can detect it. + +## Related + +- `scripts/build-binaries.sh` — builds the binary artifacts that end up in releases +- `.github/workflows/release.yml` — builds and publishes the binary on tag push +- `homebrew/archon.rb` — Homebrew tap formula (updated per release) +- `scripts/install.sh` — the curl install script +- `scripts/install-local.sh` / `install-local.ps1` — local-file install harnesses (for pre-release QA of binaries built from a branch, not from GitHub releases) +- `/release` skill — the release procedure itself (opposite side of the flow) From 9adc54afdd1b865a99f484f19b53c5d94534974d Mon Sep 17 00:00:00 2001 From: Rasmus Widing <152263317+Wirasm@users.noreply.github.com> Date: Wed, 8 Apr 2026 15:02:09 +0300 Subject: [PATCH 3/5] fix(release): wire release workflow to scripts/build-binaries.sh (#986) (#987) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Investigate issue #986: release workflow bypasses build-binaries.sh * fix(release): wire release workflow to scripts/build-binaries.sh (#986) The release workflow called `bun build --compile` inline, bypassing the build-time-constants rewrite in scripts/build-binaries.sh. Released binaries shipped with BUNDLED_IS_BINARY=false, causing `archon version` to crash with "package.json not found (bad installation?)" on v0.2.13 and v0.3.0. Changes: - Refactor scripts/build-binaries.sh to support single-target mode via TARGET/OUTFILE env vars; preserve multi-target local-dev mode unchanged. Always --minify; skip --bytecode for Windows targets. - Update .github/workflows/release.yml to call the script with the matrix target/outfile, stripping the 'v' prefix and shortening the SHA. - Add a post-build smoke test on bun-linux-x64 that asserts the binary reports "Build: binary" and the tag version (would have caught both broken releases). - Document local CI-equivalent build invocation in the test-release skill. Fixes #986 * chore: archive investigation for issue #986 * skill(release): document Homebrew formula SHA sync and verification The release skill previously stopped at tag creation and GitHub release creation. Formula updates were happening manually outside the skill and consistently drifting — v0.3.0's homebrew/archon.rb had the correct version string but SHAs from v0.2.13, because whoever updated it did so before the release workflow had built the v0.3.0 binaries. Add three new steps to close the gap: - Step 10: wait for release workflow, fetch checksums.txt, update homebrew/archon.rb atomically with new version AND new SHAs in a single commit. The formula is regenerated from a template rather than edited in place, eliminating the risk of partial updates. - Step 11: sync the rewritten formula to coleam00/homebrew-archon tap repo (the file users actually install from). Fails loudly if push access is missing instead of silently skipping. - Step 12: run /test-release brew and /test-release curl-mac to verify the install path actually works end-to-end before announcing the release. A release that installs but crashes is worse than no release at all. Also: - Add a prominent warning at the top about the chicken-and-egg relationship between version and SHAs (they must move atomically, and SHAs can only be known after binaries exist). - Add three new rules to "Important Rules": * never update version without also updating SHAs * never skip the tap sync (main repo formula is just a template) * never announce a release that failed /test-release Related to #986 (release workflow bypasses build-binaries.sh) — both bugs block the next working release; fixing only one leaves the install path broken. * fix(release): address review feedback on smoke test and restore trap - release.yml: use inputs.version on workflow_dispatch so the build step doesn't embed the branch name as the binary version - release.yml: compare smoke-test version against the stripped semver instead of the raw ref, so the check doesn't rely on the CLI re-adding a 'v' prefix - release.yml: fail fast if the binary crashes on first invocation instead of falling through to the 'wrong build type' branch - release.yml: add a second smoke step that runs 'workflow list' in a temp repo to catch the class of bug where bundled defaults fail to embed in the binary - build-binaries.sh: drop '2>/dev/null' on the EXIT trap so restore failures surface in the log with a clear WARNING - test-release skill: fix the single-target verification path --- .claude/PRPs/issues/completed/issue-986.md | 261 +++++++++++++++++++++ .claude/skills/release/SKILL.md | 191 ++++++++++++++- .claude/skills/test-release/SKILL.md | 28 +++ .github/workflows/release.yml | 74 +++++- scripts/build-binaries.sh | 82 ++++--- 5 files changed, 603 insertions(+), 33 deletions(-) create mode 100644 .claude/PRPs/issues/completed/issue-986.md diff --git a/.claude/PRPs/issues/completed/issue-986.md b/.claude/PRPs/issues/completed/issue-986.md new file mode 100644 index 0000000000..40ed76fc70 --- /dev/null +++ b/.claude/PRPs/issues/completed/issue-986.md @@ -0,0 +1,261 @@ +# Investigation: Release workflow bypasses scripts/build-binaries.sh — v0.2.13 and v0.3.0 binaries are broken + +**Issue**: #986 (https://github.com/coleam00/Archon/issues/986) +**Type**: BUG +**Investigated**: 2026-04-08 + +### Assessment + +| Metric | Value | Reasoning | +| ---------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| Severity | CRITICAL | Two consecutive releases (v0.2.13, v0.3.0) ship binaries that crash on `archon version`; no user can run the released CLI, no workaround. | +| Complexity | MEDIUM | Touches 3 files (`scripts/build-binaries.sh`, `.github/workflows/release.yml`, `test-release` skill) with moderate bash/YAML refactoring risk. | +| Confidence | HIGH | Root cause is verified: `release.yml:51-59` calls `bun build --compile` inline and never rewrites `packages/paths/src/bundled-build.ts`. | + +--- + +## Problem Statement + +The release workflow builds binaries by calling `bun build --compile` inline, bypassing `scripts/build-binaries.sh` which is the only place that rewrites `packages/paths/src/bundled-build.ts` with `BUNDLED_IS_BINARY=true`. As a result, released binaries bake in the dev defaults (`BUNDLED_IS_BINARY=false`, `BUNDLED_VERSION='dev'`), `isBinaryBuild()` returns false at runtime, and `archon version` falls into `getDevVersion()` which tries to read `package.json` from Bun's `/$bunfs/` virtual filesystem and crashes with "Failed to read version: package.json not found (bad installation?)". + +--- + +## Analysis + +### Root Cause + +PR #982 replaced runtime binary detection with build-time constants, centralizing the rewrite logic in `scripts/build-binaries.sh`. The release workflow was never updated to call the script — it still invokes `bun build --compile` directly, so the constants rewrite step is skipped entirely in CI. + +### Evidence Chain + +WHY: `archon version` fails with "Failed to read version: package.json not found" +↓ BECAUSE: `isBinaryBuild()` returns `false` in the released binary, so version lookup falls into the dev-mode `package.json` read path +Evidence: `packages/paths/src/bundled-build.ts:16` — committed dev default is `export const BUNDLED_IS_BINARY = false;` + +↓ BECAUSE: `bundled-build.ts` was never rewritten before `bun build --compile` ran in CI +Evidence: `.github/workflows/release.yml:51-59` — "Build binary" step runs `bun build --compile --minify [--bytecode] --target=... --outfile=... packages/cli/src/cli.ts` directly, with no preceding rewrite step + +↓ ROOT CAUSE: The release workflow does not call `scripts/build-binaries.sh`, which is the sole writer of the build-time constants +Evidence: `scripts/build-binaries.sh:15-31` — the file-rewrite + EXIT-trap-restore logic lives only here, and nothing in `release.yml` references this script + +### Affected Files + +| File | Lines | Action | Description | +| ---------------------------------------- | ------ | ------ | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `scripts/build-binaries.sh` | 1-86 | UPDATE | Add single-target mode via `TARGET`/`OUTFILE` env vars; add `--minify` by default; skip `--bytecode` for Windows targets. | +| `.github/workflows/release.yml` | 51-59 | UPDATE | Replace inline `bun build --compile` with `bash scripts/build-binaries.sh` invocation; pass `VERSION`/`GIT_COMMIT`/`TARGET`/`OUTFILE`. | +| `.github/workflows/release.yml` | ~60 | CREATE | New step: post-build smoke test on `bun-linux-x64` target that runs `archon version` and asserts "Build: binary" + correct tag version. | +| `.claude/skills/test-release/SKILL.md` | — | UPDATE | Add "Local build for pre-release QA" section documenting the env vars for reproducing CI builds locally. | + +### Integration Points + +- `scripts/build-binaries.sh:15-31` — sole writer of `bundled-build.ts` build-time constants (EXIT trap restores dev defaults) +- `packages/paths/src/bundled-build.ts:16-18` — consumed by `isBinaryBuild()`, `getVersion()`, and git-commit reporting; any code path that needs to know "am I a compiled binary?" reads these +- `.github/workflows/release.yml:51-59` — the divergent build path that bypasses the constants rewrite +- Matrix has 5 targets (linux x64/arm64, windows x64, darwin x64/arm64); all 5 are currently broken the same way + +### Git History + +- **PR #982** introduced the build-time constants approach but only wired it into `scripts/build-binaries.sh`, not `release.yml` +- **PRs #962/#963** previously fixed the same class of bug using runtime detection, which would have worked against the current release workflow because it didn't depend on the build script running +- **Implication**: Regression introduced by an incomplete refactor; the CI path was never exercised before shipping. + +--- + +## Implementation Plan + +### Step 1: Refactor `scripts/build-binaries.sh` for single-target mode + +**File**: `scripts/build-binaries.sh` +**Action**: UPDATE + +**Required changes**: + +1. Accept `TARGET` and `OUTFILE` env vars. If both set → build only that target (CI mode). If neither set → build all 4 local targets (unchanged local-dev behavior). If only one set → error out. +2. Always pass `--minify` (matches current CI behavior). +3. Skip `--bytecode` for Windows targets (Bun cross-compile inconsistency; matches current CI behavior). +4. Preserve the existing EXIT trap that restores `packages/paths/src/bundled-build.ts`. +5. Preserve the existing min-size check (`MIN_BINARY_SIZE=1000000`). +6. Keep `VERSION` / `GIT_COMMIT` env var precedence; defaults unchanged. + +See the issue body (#986) for the full script rewrite — use it verbatim as the target state. + +**Why**: Single canonical build entry point eliminates the drift risk between local dev and CI. + +--- + +### Step 2: Update `.github/workflows/release.yml` to call the script + +**File**: `.github/workflows/release.yml` +**Lines**: 51-59 +**Action**: UPDATE + +**Current code**: + +```yaml +- name: Build binary + run: | + mkdir -p dist + # --bytecode excluded for Windows cross-compile (inconsistent Bun support) + if [[ "${{ matrix.target }}" == *windows* ]]; then + bun build --compile --minify --target=${{ matrix.target }} --outfile=dist/${{ matrix.binary }} packages/cli/src/cli.ts + else + bun build --compile --minify --bytecode --target=${{ matrix.target }} --outfile=dist/${{ matrix.binary }} packages/cli/src/cli.ts + fi +``` + +**Required change**: + +```yaml +- name: Build binary + env: + VERSION: ${{ github.ref_name }} + GIT_COMMIT: ${{ github.sha }} + TARGET: ${{ matrix.target }} + OUTFILE: dist/${{ matrix.binary }} + run: | + # Strip 'v' prefix from tag (e.g. v0.3.1 → 0.3.1) + VERSION="${VERSION#v}" + # Short commit (first 8 chars of SHA) + GIT_COMMIT="${GIT_COMMIT::8}" + mkdir -p dist + VERSION="$VERSION" GIT_COMMIT="$GIT_COMMIT" TARGET="$TARGET" OUTFILE="$OUTFILE" bash scripts/build-binaries.sh +``` + +**Why**: Delegates all build logic (including the constants rewrite) to the canonical script. + +--- + +### Step 3: Add post-build smoke test + +**File**: `.github/workflows/release.yml` +**Action**: CREATE (new step after "Build binary") + +Add the smoke-test YAML block from issue #986 verbatim. Runs only on `bun-linux-x64` + Linux runner. Asserts: + +1. Output contains neither "Failed to read version" nor "package.json not found" nor "bad installation" +2. Output contains "Build: binary" +3. Output contains the tag version + +**Why**: Would have caught both v0.2.13 and v0.3.0 before publishing. One target per class of bug is enough. + +--- + +### Step 4: Update `test-release` skill docs + +**File**: `.claude/skills/test-release/SKILL.md` +**Action**: UPDATE + +Add the "Local build for pre-release QA" section from issue #986 verbatim. Documents how to invoke the script in both multi-target and single-target modes for local reproduction of CI builds. + +**Why**: Lets the next contributor exercise the CI code path locally before tagging. + +--- + +### Step 5: No test code changes + +The build script is bash and has no unit tests; validation is manual (see Validation section). No TypeScript test additions required. + +--- + +## Patterns to Follow + +The script rewrite should preserve the existing patterns in `scripts/build-binaries.sh`: + +```bash +# SOURCE: scripts/build-binaries.sh:15-16 +# Pattern: EXIT trap restore — keep dev tree clean even on failure +BUNDLED_BUILD_FILE="packages/paths/src/bundled-build.ts" +trap 'echo "Restoring ${BUNDLED_BUILD_FILE}..."; git checkout -- "${BUNDLED_BUILD_FILE}"' EXIT +``` + +```bash +# SOURCE: scripts/build-binaries.sh:68-78 +# Pattern: portable stat + min-size sanity check +if stat -f%z "$outfile" >/dev/null 2>&1; then + size=$(stat -f%z "$outfile") +else + size=$(stat --printf="%s" "$outfile") +fi +if [ "$size" -lt "$MIN_BINARY_SIZE" ]; then + echo "ERROR: Build output suspiciously small ($size bytes): $outfile" >&2 + exit 1 +fi +``` + +--- + +## Edge Cases & Risks + +| Risk/Edge Case | Mitigation | +| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | +| EXIT trap fails in CI (repo is detached HEAD during tag checkout) | `git checkout -- ` works on detached HEAD; also guard with `|| true` to avoid failing the step on restore errors after successful build. | +| `VERSION` still has `v` prefix when passed to script | Strip in `release.yml` before invocation (`VERSION="${VERSION#v}"`). | +| Windows smoke test can't run on Linux CI runner | Smoke test gated to `bun-linux-x64` only; documented as acceptable because the bug class is cross-platform. | +| `scripts/build-binaries.sh` invoked with only `TARGET` or only `OUTFILE` | Script errors out with clear message before doing any work. | +| Backwards compatibility for local `bash scripts/build-binaries.sh` with no env | Script falls through to multi-target mode unchanged (builds all 4 targets into `dist/binaries/`). | +| `bytecode` flag support regresses on a target | Per-target `*windows*` pattern match preserves current CI behavior exactly. | + +--- + +## Validation + +### Automated Checks + +```bash +# Shell syntax check +bash -n scripts/build-binaries.sh + +# Workflow YAML validity (actionlint if available, else yamllint) +actionlint .github/workflows/release.yml || yamllint .github/workflows/release.yml + +# Repo validation +bun run validate +``` + +### Manual Verification (local, pre-merge) + +1. **Backwards compatibility**: `bash scripts/build-binaries.sh` (no env). Confirm all 4 targets build into `dist/binaries/`. +2. **Single-target mode**: `VERSION=0.3.1-test GIT_COMMIT=test1234 TARGET=bun-darwin-arm64 OUTFILE=/tmp/test-single-target bash scripts/build-binaries.sh`. Confirm binary exists. +3. **Build-time constants embedded**: `/tmp/test-single-target version` → reports `v0.3.1-test`, `Build: binary`, `Git commit: test1234`. +4. **EXIT trap restore**: `git status packages/paths/src/bundled-build.ts` shows clean. +5. **Error handling**: Run with only `TARGET` set (no `OUTFILE`). Script exits with clear error. + +### CI Verification (post-merge) + +1. Trigger the release workflow via `workflow_dispatch` with a test tag (e.g. `v0.3.1-rc1`). +2. Confirm the new smoke-test step executes and passes for `bun-linux-x64`. +3. `gh release view v0.3.1-rc1` shows all 5 binaries + `checksums.txt`. +4. Download `archon-darwin-arm64` and run `./archon-darwin-arm64 version` — must report the tag version + `Build: binary`. + +### Post-release Verification + +1. `/test-release curl-mac 0.3.1` passes. +2. `/test-release curl-linux 0.3.1` passes. + +--- + +## Scope Boundaries + +**IN SCOPE:** + +- Refactor `scripts/build-binaries.sh` for single-target mode +- Wire `release.yml` to call the script +- Add post-build smoke test for `bun-linux-x64` +- Document local build env vars in `test-release` skill + +**OUT OF SCOPE (do not touch):** + +- Homebrew tap sync gap (separate issue — `coleam00/homebrew-archon` formula still at v0.2.0) +- Telemetry / `BUNDLED_POSTHOG_KEY` (#980) — separate feature, will benefit from this refactor automatically +- Windows / macOS smoke tests (can't run on Linux runner; one target catches the class of bug) +- Runtime detection fallback (deliberately removed in #982; don't re-introduce) +- `update-homebrew` job structure (works as-is post-fix) + +--- + +## Metadata + +- **Investigated by**: Claude +- **Timestamp**: 2026-04-08 +- **Artifact**: `.claude/PRPs/issues/issue-986.md` diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md index b8008532c9..4649987076 100644 --- a/.claude/skills/release/SKILL.md +++ b/.claude/skills/release/SKILL.md @@ -13,7 +13,20 @@ description: | # Release Skill -Creates a release by comparing dev to main, generating changelog entries from commits, bumping the version, and creating a PR. +Creates a release by comparing dev to main, generating changelog entries from commits, bumping the version, and creating a PR. After the tag is pushed and the release workflow finishes building binaries, updates the Homebrew formula with the real SHA256 values from the published `checksums.txt`, syncs the `coleam00/homebrew-archon` tap, and verifies the end-to-end install path via `/test-release`. + +> **⚠️ CRITICAL — Homebrew formula SHAs cannot be known until after the release workflow builds binaries.** +> +> The `version` field in `homebrew/archon.rb` and the `sha256` fields must be updated **atomically**. Never update one without the other. +> +> The correct sequence is: +> 1. Tag is pushed → release workflow fires → binaries built → `checksums.txt` uploaded +> 2. Fetch `checksums.txt` from the published release +> 3. Parse the SHA256 per platform +> 4. Update `homebrew/archon.rb` with the new version AND the new SHAs in a single commit +> 5. Sync to the `coleam00/homebrew-archon/Formula/archon.rb` tap repo +> +> Updating the formula's `version` field without also updating the `sha256` values creates a stale, misleading formula that looks valid but produces checksum mismatches on install. This has happened before (v0.3.0: version updated to 0.3.0 but SHAs were still from v0.2.13). Always do both or neither. ## Process @@ -177,6 +190,179 @@ The GitHub Release is distinct from the git tag — without it, the release won' If the user merges the PR themselves and comes back, still offer to tag, release, and sync. +### Step 10: Wait for Release Workflow and Update Homebrew Formula + +After the tag is pushed, `.github/workflows/release.yml` builds platform binaries and uploads them to the GitHub release. This takes 5-10 minutes. The Homebrew formula SHA256 values cannot be known until these binaries exist. + +**Wait for all assets to appear on the release:** + +```bash +echo "Waiting for release workflow to finish uploading binaries..." +for i in {1..30}; do + ASSET_COUNT=$(gh release view "vx.y.z" --repo coleam00/Archon --json assets --jq '.assets | length') + # Expect 6 assets: 5 binaries (darwin-arm64, darwin-x64, linux-arm64, linux-x64, windows-x64.exe) + checksums.txt + if [ "$ASSET_COUNT" -ge 6 ]; then + echo "All $ASSET_COUNT assets uploaded" + break + fi + echo " Assets so far: $ASSET_COUNT/6 — waiting 30s (attempt $i/30)..." + sleep 30 +done + +if [ "$ASSET_COUNT" -lt 6 ]; then + echo "ERROR: Release workflow did not finish uploading assets after 15 minutes" + echo "Check https://github.com/coleam00/Archon/actions for the release workflow run" + exit 1 +fi +``` + +**Fetch checksums.txt and extract SHA256 values:** + +```bash +TMP_DIR=$(mktemp -d) +gh release download "vx.y.z" --repo coleam00/Archon --pattern "checksums.txt" --dir "$TMP_DIR" + +DARWIN_ARM64_SHA=$(awk '/archon-darwin-arm64$/ {print $1}' "$TMP_DIR/checksums.txt") +DARWIN_X64_SHA=$(awk '/archon-darwin-x64$/ {print $1}' "$TMP_DIR/checksums.txt") +LINUX_ARM64_SHA=$(awk '/archon-linux-arm64$/ {print $1}' "$TMP_DIR/checksums.txt") +LINUX_X64_SHA=$(awk '/archon-linux-x64$/ {print $1}' "$TMP_DIR/checksums.txt") + +# Sanity check — all four must be present and non-empty +for var in DARWIN_ARM64_SHA DARWIN_X64_SHA LINUX_ARM64_SHA LINUX_X64_SHA; do + if [ -z "${!var}" ]; then + echo "ERROR: $var is empty — checksums.txt may be malformed" + cat "$TMP_DIR/checksums.txt" + exit 1 + fi +done + +rm -rf "$TMP_DIR" +``` + +**Update `homebrew/archon.rb` in the main repo atomically with version AND SHAs:** + +Rewrite the formula file using the exact template below. Do NOT edit in place with sed — the whole file should be regenerated from this template so there is zero risk of partial updates. + +```bash +cat > homebrew/archon.rb << EOF +# Homebrew formula for Archon CLI +# To install: brew install coleam00/archon/archon +# +# This formula downloads pre-built binaries from GitHub releases. +# For development, see: https://github.com/coleam00/Archon + +class Archon < Formula + desc "Remote agentic coding platform - control AI assistants from anywhere" + homepage "https://github.com/coleam00/Archon" + version "x.y.z" + license "MIT" + + on_macos do + on_arm do + url "https://github.com/coleam00/Archon/releases/download/v#{version}/archon-darwin-arm64" + sha256 "${DARWIN_ARM64_SHA}" + end + on_intel do + url "https://github.com/coleam00/Archon/releases/download/v#{version}/archon-darwin-x64" + sha256 "${DARWIN_X64_SHA}" + end + end + + on_linux do + on_arm do + url "https://github.com/coleam00/Archon/releases/download/v#{version}/archon-linux-arm64" + sha256 "${LINUX_ARM64_SHA}" + end + on_intel do + url "https://github.com/coleam00/Archon/releases/download/v#{version}/archon-linux-x64" + sha256 "${LINUX_X64_SHA}" + end + end + + def install + binary_name = case + when OS.mac? && Hardware::CPU.arm? + "archon-darwin-arm64" + when OS.mac? && Hardware::CPU.intel? + "archon-darwin-x64" + when OS.linux? && Hardware::CPU.arm? + "archon-linux-arm64" + when OS.linux? && Hardware::CPU.intel? + "archon-linux-x64" + end + + bin.install binary_name => "archon" + end + + test do + # Basic version check - archon version should exit with 0 on success + assert_match version.to_s, shell_output("#{bin}/archon version") + end +end +EOF +``` + +**Commit the formula update to main, then sync back to dev:** + +```bash +git checkout main +git pull origin main +git add homebrew/archon.rb +git commit -m "chore(homebrew): update formula to vx.y.z" +git push origin main + +# Sync dev with main so the formula update is on both branches +git checkout dev +git pull origin main +git push origin dev +``` + +### Step 11: Sync the Homebrew Tap Repo + +The `coleam00/homebrew-archon` repository hosts the actual tap formula that Homebrew reads when users run `brew tap coleam00/archon && brew install coleam00/archon/archon`. The file `coleam00/Archon/homebrew/archon.rb` is the source-of-truth template; the file `coleam00/homebrew-archon/Formula/archon.rb` is what users actually install from. These must be kept in sync. + +```bash +TAP_DIR=$(mktemp -d) +git clone git@github.com:coleam00/homebrew-archon.git "$TAP_DIR" +cp homebrew/archon.rb "$TAP_DIR/Formula/archon.rb" + +cd "$TAP_DIR" +if git diff --quiet; then + echo "Tap formula already matches — no sync needed" +else + git add Formula/archon.rb + git commit -m "chore: sync formula to vx.y.z" + git push origin main +fi +cd - +rm -rf "$TAP_DIR" +``` + +If the `git clone` fails with a permissions error, the user running the release skill does not have push access to `coleam00/homebrew-archon`. Ask them to request push access from the repo owner, or to perform the sync manually via the GitHub web UI. Do not skip this step silently — the release is not complete until the tap is synced. + +### Step 12: Verify the Release End-to-End + +After the formula is synced, the final verification step is to actually install the released binary via Homebrew and run smoke tests. Use the `test-release` skill: + +``` +/test-release brew x.y.z +``` + +This will: +- Install via `brew tap coleam00/archon && brew install coleam00/archon/archon` +- Verify the binary reports the correct version and `Build: binary` +- Verify bundled workflows load +- Verify the SDK spawn path works (a minimal assist workflow) +- Verify the env-leak gate is active (if shipped in this release) +- Uninstall cleanly +- Produce a PASS/FAIL report + +**If `/test-release brew` fails, the release is not ready to announce.** File a hotfix issue for whatever broke, cut `x.y.z+1` with the fix, and re-run this skill. Do NOT advertise a release that fails `test-release`. + +Also run `/test-release curl-mac x.y.z` to cover the curl install path. The two install paths test slightly different things (Homebrew tests the tap formula, curl tests `install.sh` and checksums from the release) and both need to work for users to have a reliable install experience. + +If you have a VPS available, also run `/test-release curl-vps x.y.z ` to verify the Linux binary. + ## Important Rules - NEVER force push @@ -185,3 +371,6 @@ If the user merges the PR themselves and comes back, still offer to tag, release - NEVER add emoji to changelog entries unless the user asks - If the user says "ship it" without specifying bump type, default to patch - The commit message is just `Release x.y.z` — clean and simple +- **NEVER update `homebrew/archon.rb` version field without also updating the `sha256` values**. They must move together atomically. The correct SHAs only exist after the release workflow finishes building binaries — see Step 10. Updating the version field alone produces a stale formula that looks valid but causes checksum mismatches on install. +- **NEVER skip Step 11 (tap sync).** The `coleam00/Archon/homebrew/archon.rb` file is only a template; users install from `coleam00/homebrew-archon/Formula/archon.rb`. If you update one without the other, users get stale or wrong data. +- **NEVER announce a release that failed `/test-release brew`.** A release that installs but crashes on first invocation is worse than no release — it burns user trust. If the release verification fails, cut a hotfix before telling anyone the release exists. diff --git a/.claude/skills/test-release/SKILL.md b/.claude/skills/test-release/SKILL.md index b9001bc92d..c8cfc3c4f3 100644 --- a/.claude/skills/test-release/SKILL.md +++ b/.claude/skills/test-release/SKILL.md @@ -29,6 +29,34 @@ Every path installs the binary, runs a fixed smoke test suite, and cleans up. Th - You want to test the dev clone — use `bun run validate` or invoke source directly via `bun packages/cli/src/cli.ts` - You want to test the full server + web UI deploy flow — use the cloud-init from `deploy/cloud-init.yml` on a real VPS +## Local build for pre-release QA + +To build a binary locally with the exact same flags and constants that CI uses, +invoke `scripts/build-binaries.sh` directly. The script supports two modes: + +```bash +# Multi-target mode (builds all 4 local platforms into dist/binaries/) +VERSION=0.3.1 GIT_COMMIT=abc12345 bash scripts/build-binaries.sh + +# Single-target mode (matches one CI matrix job) +VERSION=0.3.1 \ +GIT_COMMIT=abc12345 \ +TARGET=bun-darwin-arm64 \ +OUTFILE=dist/test-archon-darwin-arm64 \ +bash scripts/build-binaries.sh + +# Verify the binary — use the path from the mode you built: +# multi-target → ./dist/binaries/archon-darwin-arm64 +# single-target → the OUTFILE you passed above +./dist/test-archon-darwin-arm64 version +# Expected: Archon CLI v0.3.1, Build: binary, Git commit: abc12345 +``` + +Run this **before tagging a release** to catch build-time-constant issues +locally. The script is the canonical entry point — both local dev and the +release workflow call it the same way, so a green local build means the CI +build will exercise the same code path. + ## Phase 1 — Determine scope Parse the arguments. The skill takes up to three: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 29518dda0d..1de8f29034 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,13 +49,79 @@ jobs: run: bun install --frozen-lockfile - name: Build binary + env: + # On workflow_dispatch, github.ref_name is the branch name (e.g. 'main'), + # not the version tag — fall back to the user-supplied `version` input. + VERSION: ${{ github.event_name == 'workflow_dispatch' && inputs.version || github.ref_name }} + GIT_COMMIT: ${{ github.sha }} + TARGET: ${{ matrix.target }} + OUTFILE: dist/${{ matrix.binary }} run: | + # Strip 'v' prefix from tag (e.g. v0.3.1 → 0.3.1) + VERSION="${VERSION#v}" + # Short commit (first 8 chars of SHA) + GIT_COMMIT="${GIT_COMMIT::8}" mkdir -p dist - # --bytecode excluded for Windows cross-compile (inconsistent Bun support) - if [[ "${{ matrix.target }}" == *windows* ]]; then - bun build --compile --minify --target=${{ matrix.target }} --outfile=dist/${{ matrix.binary }} packages/cli/src/cli.ts + VERSION="$VERSION" GIT_COMMIT="$GIT_COMMIT" TARGET="$TARGET" OUTFILE="$OUTFILE" bash scripts/build-binaries.sh + + - name: Smoke-test built binary + if: matrix.target == 'bun-linux-x64' && runner.os == 'Linux' + env: + RAW_VERSION: ${{ github.event_name == 'workflow_dispatch' && inputs.version || github.ref_name }} + run: | + chmod +x dist/${{ matrix.binary }} + if ! VERSION_OUTPUT=$(./dist/${{ matrix.binary }} version 2>&1); then + echo "::error::Binary failed to execute" + echo "$VERSION_OUTPUT" + exit 1 + fi + echo "$VERSION_OUTPUT" + + # Must not error with "Failed to read version" or similar + if echo "$VERSION_OUTPUT" | grep -qE "Failed to read version|package\.json not found|bad installation"; then + echo "::error::Binary is broken — version command cannot read embedded version" + echo "::error::This means BUNDLED_IS_BINARY was not set to true at build time." + exit 1 + fi + + # Must report 'Build: binary', not 'Build: source' + if ! echo "$VERSION_OUTPUT" | grep -q "Build: binary"; then + echo "::error::Binary reports wrong build type" + echo "::error::Expected 'Build: binary' in version output" + exit 1 + fi + + # Must report the (stripped) tag version. Compare against the same + # value that was baked into the binary (VERSION#v), not the raw ref, + # so the check doesn't rely on the CLI re-adding a 'v' prefix. + EXPECTED_VERSION="${RAW_VERSION#v}" + if echo "$VERSION_OUTPUT" | grep -qE "v?${EXPECTED_VERSION}(\s|$)"; then + echo "::notice::Binary correctly reports version ${EXPECTED_VERSION}" + else + echo "::error::Binary does not report version ${EXPECTED_VERSION}" + exit 1 + fi + + - name: Smoke-test bundled defaults load + if: matrix.target == 'bun-linux-x64' && runner.os == 'Linux' + run: | + # `workflow list` requires running from a git repo + BIN="$PWD/dist/${{ matrix.binary }}" + TMP_REPO=$(mktemp -d) + cd "$TMP_REPO" + git init -q + git -c user.email=ci@example.com -c user.name=ci commit --allow-empty -q -m init + if ! OUTPUT=$("$BIN" workflow list 2>&1); then + echo "::error::workflow list failed to execute" + echo "$OUTPUT" + exit 1 + fi + echo "$OUTPUT" + if echo "$OUTPUT" | grep -q "archon-assist"; then + echo "::notice::Bundled workflows loaded correctly" else - bun build --compile --minify --bytecode --target=${{ matrix.target }} --outfile=dist/${{ matrix.binary }} packages/cli/src/cli.ts + echo "::error::Bundled workflows did not load — embedded JSON may be missing from the binary" + exit 1 fi - name: Upload binary artifact diff --git a/scripts/build-binaries.sh b/scripts/build-binaries.sh index ddc0498394..c683c47ac7 100755 --- a/scripts/build-binaries.sh +++ b/scripts/build-binaries.sh @@ -1,19 +1,31 @@ #!/usr/bin/env bash # scripts/build-binaries.sh -# Build standalone CLI binaries for all supported platforms +# Build standalone CLI binaries for all supported platforms. +# +# Modes: +# - Multi-target (local dev): no env vars → builds all 4 local targets into dist/binaries/ +# - Single-target (CI): TARGET + OUTFILE both set → builds only that target +# +# Env vars: +# VERSION - version string (default: from package.json) +# GIT_COMMIT - short git commit (default: from `git rev-parse --short HEAD`) +# TARGET - bun target triple (e.g. bun-darwin-arm64); CI mode +# OUTFILE - output path for the built binary; CI mode set -euo pipefail -# Get version from package.json or git tag VERSION="${VERSION:-$(grep '"version"' package.json | head -1 | cut -d'"' -f4)}" GIT_COMMIT="${GIT_COMMIT:-$(git rev-parse --short HEAD 2>/dev/null || echo 'unknown')}" +TARGET="${TARGET:-}" +OUTFILE="${OUTFILE:-}" + echo "Building Archon CLI v${VERSION} (commit: ${GIT_COMMIT})" # Update build-time constants in source before compiling. # The file is restored via an EXIT trap so the dev tree is never left dirty, # even if `bun build --compile` fails mid-way. See GitHub issue #979. BUNDLED_BUILD_FILE="packages/paths/src/bundled-build.ts" -trap 'echo "Restoring ${BUNDLED_BUILD_FILE}..."; git checkout -- "${BUNDLED_BUILD_FILE}"' EXIT +trap 'echo "Restoring ${BUNDLED_BUILD_FILE}..."; git checkout -- "${BUNDLED_BUILD_FILE}" || echo "WARNING: failed to restore ${BUNDLED_BUILD_FILE} — working tree may be dirty" >&2' EXIT echo "Updating build-time constants (version=${VERSION}, is_binary=true)..." cat > "$BUNDLED_BUILD_FILE" << EOF @@ -30,56 +42,70 @@ export const BUNDLED_VERSION = '${VERSION}'; export const BUNDLED_GIT_COMMIT = '${GIT_COMMIT}'; EOF -# Output directory -DIST_DIR="dist/binaries" -mkdir -p "$DIST_DIR" - -# Define build targets -# Format: bun-target:output-name -TARGETS=( - "bun-darwin-arm64:archon-darwin-arm64" - "bun-darwin-x64:archon-darwin-x64" - "bun-linux-x64:archon-linux-x64" - "bun-linux-arm64:archon-linux-arm64" -) +# Determine which targets to build +if [ -n "$TARGET" ] && [ -n "$OUTFILE" ]; then + # Single-target mode (CI): one target, caller-supplied output path + TARGETS=("$TARGET:$OUTFILE") +elif [ -n "$TARGET" ] || [ -n "$OUTFILE" ]; then + echo "ERROR: TARGET and OUTFILE must be set together (CI mode) or both unset (local mode)" >&2 + exit 1 +else + # Multi-target mode (local dev) + DIST_DIR="dist/binaries" + mkdir -p "$DIST_DIR" + TARGETS=( + "bun-darwin-arm64:${DIST_DIR}/archon-darwin-arm64" + "bun-darwin-x64:${DIST_DIR}/archon-darwin-x64" + "bun-linux-x64:${DIST_DIR}/archon-linux-x64" + "bun-linux-arm64:${DIST_DIR}/archon-linux-arm64" + ) +fi # Minimum expected binary size (1MB - Bun binaries are typically 50MB+) MIN_BINARY_SIZE=1000000 # Build each target for target_pair in "${TARGETS[@]}"; do - IFS=':' read -r target output_name <<< "$target_pair" - echo "Building for $target..." + IFS=':' read -r target outfile <<< "$target_pair" + echo "Building $target → $outfile" + + # --bytecode excluded for Windows cross-compile (inconsistent Bun support) + BYTECODE_FLAG="" + if [[ "$target" != *windows* ]]; then + BYTECODE_FLAG="--bytecode" + fi + # Always --minify to match release parity bun build \ --compile \ + --minify \ + $BYTECODE_FLAG \ --target="$target" \ - --outfile="$DIST_DIR/$output_name" \ + --outfile="$outfile" \ packages/cli/src/cli.ts # Verify build output exists - if [ ! -f "$DIST_DIR/$output_name" ]; then - echo "ERROR: Build failed - $DIST_DIR/$output_name not created" + if [ ! -f "$outfile" ]; then + echo "ERROR: Build failed - $outfile not created" >&2 exit 1 fi # Verify minimum reasonable size (Bun binaries are typically 50MB+) # Use portable stat command (works on both macOS and Linux) - if stat -f%z "$DIST_DIR/$output_name" >/dev/null 2>&1; then - size=$(stat -f%z "$DIST_DIR/$output_name") + if stat -f%z "$outfile" >/dev/null 2>&1; then + size=$(stat -f%z "$outfile") else - size=$(stat --printf="%s" "$DIST_DIR/$output_name") + size=$(stat --printf="%s" "$outfile") fi if [ "$size" -lt "$MIN_BINARY_SIZE" ]; then - echo "ERROR: Build output suspiciously small ($size bytes): $DIST_DIR/$output_name" - echo "Expected at least $MIN_BINARY_SIZE bytes for a Bun-compiled binary" + echo "ERROR: Build output suspiciously small ($size bytes): $outfile" >&2 + echo "Expected at least $MIN_BINARY_SIZE bytes for a Bun-compiled binary" >&2 exit 1 fi - echo " -> $DIST_DIR/$output_name ($size bytes)" + echo " -> $outfile ($size bytes)" done echo "" -echo "Build complete! Binaries in $DIST_DIR:" -ls -lh "$DIST_DIR" +echo "Build complete." From a711347764f9f36cd0b1f5b0e44a1bd1da6e12fd Mon Sep 17 00:00:00 2001 From: Rasmus Widing <152263317+Wirasm@users.noreply.github.com> Date: Wed, 8 Apr 2026 15:05:34 +0300 Subject: [PATCH 4/5] fix(sqlite): add allow_env_keys column to codebases schema + migration (#988) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #983 added the allow_env_keys consent bit via PostgreSQL migrations (migrations/000_combined.sql and migrations/021_*.sql) but did not update packages/core/src/db/adapters/sqlite.ts, which has its own independent schema bootstrap path. Result: every SQLite database has been broken since #983 landed: - Fresh installs: createSchema() creates remote_agent_codebases without the column, and POST /api/codebases fails on every call with "table remote_agent_codebases has no column named allow_env_keys". - Existing installs upgraded from v0.2.x: CREATE TABLE IF NOT EXISTS is a no-op on the existing table and migrateColumns() never adds the column, same failure. Cole's deployed server at archon-youtube.smartcode.diy hit this live — every "add project" request returned 500 because the VPS runs docker-compose with the SQLite default (no separate postgres service). Two surgical changes to packages/core/src/db/adapters/sqlite.ts: 1. createSchema(): add `allow_env_keys INTEGER DEFAULT 0` to the remote_agent_codebases CREATE TABLE block so fresh databases get the column. SQLite has no true BOOLEAN — INTEGER with 0/1 matches the existing pattern used for `hidden` on conversations. 2. migrateColumns(): add a new idempotent try/catch block that PRAGMA-checks the codebases table for `allow_env_keys` and ALTERs it in if missing. Pattern matches the existing migration blocks for Conversations, Workflow runs, and Sessions columns. The JavaScript read path in db/codebases.ts and the clients already uses truthy checks (`if (!codebase?.allow_env_keys)`), which works for both SQLite integer (0/1) and JS boolean (false/true) storage. No other changes needed. Fixes the live incident blocking Cole's demo and unblocks v0.3.1. --- packages/core/src/db/adapters/sqlite.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/core/src/db/adapters/sqlite.ts b/packages/core/src/db/adapters/sqlite.ts index 485706d040..2864e4fc43 100644 --- a/packages/core/src/db/adapters/sqlite.ts +++ b/packages/core/src/db/adapters/sqlite.ts @@ -215,6 +215,22 @@ export class SqliteAdapter implements IDatabase { } catch (e: unknown) { getLog().warn({ err: e as Error }, 'db.sqlite_migration_session_columns_failed'); } + + // Codebases columns (added in #983 — env-leak gate consent bit) + try { + const cbCols = this.db.prepare("PRAGMA table_info('remote_agent_codebases')").all() as { + name: string; + }[]; + const cbColNames = new Set(cbCols.map(c => c.name)); + + if (!cbColNames.has('allow_env_keys')) { + this.db.run( + 'ALTER TABLE remote_agent_codebases ADD COLUMN allow_env_keys INTEGER DEFAULT 0' + ); + } + } catch (e: unknown) { + getLog().warn({ err: e as Error }, 'db.sqlite_migration_codebases_columns_failed'); + } } /** @@ -236,6 +252,7 @@ export class SqliteAdapter implements IDatabase { default_cwd TEXT NOT NULL, default_branch TEXT DEFAULT 'main', ai_assistant_type TEXT DEFAULT 'claude', + allow_env_keys INTEGER DEFAULT 0, commands TEXT DEFAULT '{}', created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')) From 452529328ee48537c5d2aee404eb2166b27d3519 Mon Sep 17 00:00:00 2001 From: Rasmus Widing Date: Wed, 8 Apr 2026 15:09:05 +0300 Subject: [PATCH 5/5] Release 0.3.1 --- CHANGELOG.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9239f9f1da..fd4d032f52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.3.1] - 2026-04-08 + +Patch release: SQLite migration fix for existing databases and release build pipeline fix. + +### Fixed + +- **SQLite migration for `allow_env_keys`**: add the missing `allow_env_keys` column to the `codebases` schema and a migration so databases created before v0.3.0 upgrade cleanly instead of erroring on first query (#988). +- **Release workflow binary builds**: wire `.github/workflows/release.yml` back to `scripts/build-binaries.sh` so tagged releases actually produce platform binaries and `checksums.txt` (#986, #987). + ## [0.3.0] - 2026-04-08 Env-leak gate hardening, SSE reliability fixes, isolation cleanup smarter merge detection, build/version improvements, and deploy hardening. diff --git a/package.json b/package.json index 1e5d61ac30..f25d9ceb76 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "archon", - "version": "0.3.0", + "version": "0.3.1", "private": true, "workspaces": [ "packages/*"