Skip to content
Open
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
6be5c61
chore(homebrew): update formula to v0.3.6
Wirasm Apr 12, 2026
9546ea7
feat(providers): add GitHub Copilot community provider
popemkt Apr 21, 2026
f412b83
feat(providers/copilot): wire tool restrictions
popemkt Apr 21, 2026
94b7f47
feat(providers/copilot): wire MCP servers
popemkt Apr 21, 2026
a198290
feat(providers/copilot): wire skills
popemkt Apr 21, 2026
d7719bb
feat(providers/copilot): wire best-effort structured output
popemkt Apr 21, 2026
8a3504d
fix(providers/copilot): address PR review findings
popemkt Apr 21, 2026
31d94d4
feat(providers/copilot): wire sub-agents
popemkt Apr 21, 2026
bc25dee
fix: detect completion signal in any XML tag, not just <promise> (#11…
Wirasm Apr 22, 2026
d7f36b2
fix(web): allow deleting nodes from Workflow Builder (#971) (#1113)
medevs Apr 22, 2026
817186d
fix(workflows): make archon-adversarial-dev sed replacement macOS-saf…
LaplaceYoung Apr 22, 2026
6fea392
fix(workflows): filter user-plugin MCP noise out of workflow warnings…
Wirasm Apr 22, 2026
e0b57a8
fix(providers/copilot): make vendor-path binary-resolver test hermetic
Apr 22, 2026
ae2d936
fix(deps): override transitive axios to ^1.15.0 for CVE-2025-62718 (#…
Wirasm Apr 22, 2026
056707d
fix(cli): surface stale-workspace registration error instead of fake …
Wirasm Apr 22, 2026
d5c1cd9
fix(server,web,workflows): web approval gates auto-resume + reject-wi…
Wirasm Apr 22, 2026
7000f9b
Release 0.3.7
Wirasm Apr 22, 2026
48c81d3
Merge pull request #1352 from coleam00/dev
Wirasm Apr 22, 2026
0de826c
fix(build): drop --bytecode from compiled-binary build (#1354)
Wirasm Apr 22, 2026
4397ed1
chore(release-skill): add pre-flight binary smoke + deterministic-CI-…
Wirasm Apr 22, 2026
5294fcd
fix(providers/pi): lazy-load Pi SDK to unbreak compiled archon binary…
Wirasm Apr 22, 2026
e646cd4
Release 0.3.8
Wirasm Apr 22, 2026
8697508
Merge pull request #1356 from coleam00/dev
Wirasm Apr 22, 2026
876abb1
fix(ci): --no-worktree on release-smoke archon-assist invocations (#1…
Wirasm Apr 22, 2026
889ffc2
Release 0.3.9
Wirasm Apr 22, 2026
7fc4761
Merge pull request #1358 from coleam00/dev
Wirasm Apr 22, 2026
4115ea5
chore: update Homebrew formula for v0.3.9
github-actions[bot] Apr 22, 2026
359b6d3
chore(release-skill): use --help (not version) for Step 1.5 smoke pro…
Wirasm Apr 22, 2026
6f86402
chore(test-release-skill): preserve archon-stable across test cycles
Wirasm Apr 22, 2026
e50e649
Merge upstream/dev into emdash/add-copilot-2er
Apr 22, 2026
efd838e
fix(merge): remove stray <<<<<<< HEAD marker left in package.json
Apr 22, 2026
0e9f1c8
fix(providers/pi): install PI_PACKAGE_DIR shim so Pi workflows run in…
Wirasm Apr 22, 2026
b99cee4
feat(providers): autodetect canonical binary install paths for Claude…
Wirasm Apr 22, 2026
f9f8775
fix(providers/test): use os.homedir() instead of $HOME in claude bina…
coleam00 Apr 23, 2026
5957c6e
fix(server): contain Discord login failure so it doesn't kill the ser…
coleam00 Apr 23, 2026
46874ca
docs(script-nodes): dedicated guide + teach the archon skill (#1362)
Wirasm Apr 24, 2026
2c15439
docs/skill: general hardening — fix inaccuracies, fill workflow/CLI/e…
Wirasm Apr 24, 2026
ad13d83
chore(workflows): switch default Opus pin to opus[1m] alias (#1395)
Wirasm Apr 24, 2026
f094f2a
fix(workflow): migrate piv-loop plan handoff to $ARTIFACTS_DIR (#1398)
coleam00 Apr 24, 2026
a57d628
test(workflows): add anyFailed status derivation coverage for DAG exe…
coleam00 Apr 24, 2026
9122673
docs/skill: add parameter-matrix.md quick-lookup reference
coleam00 Apr 24, 2026
b286ad9
docs: point contributors at PR template and Closes #N convention
Wirasm Apr 27, 2026
d35b193
feat(workflows): add maintainer-standup workflow for daily PR/issue t…
Wirasm Apr 27, 2026
3868f89
feat(workflows): support explicit tags in workflow YAML (#1190)
lraphael Apr 27, 2026
bf8734b
fix(copilot): address CodeRabbit review on PR #1351
popemkt Apr 27, 2026
6c94355
feat(workflows): add maintainer-review-pr and group maintainer workfl…
Wirasm Apr 27, 2026
686bec6
feat(pi): use ModelRegistry to support custom models and skip auth fo…
matt2000 Apr 27, 2026
4929c54
chore(workflows): group smoke-test workflows under test-workflows/ + …
Wirasm Apr 27, 2026
ef950ff
fix(maintainer-review): address CodeRabbit findings on #1430 (#1432)
Wirasm Apr 27, 2026
e2a4427
fix(workflows): approval gate bypass after reject-with-redraft on res…
Wirasm Apr 27, 2026
8cfd598
feat(workflows): add mutates_checkout to allow concurrent runs on liv…
Wirasm Apr 27, 2026
eec09ff
docs: replace String.raw with direct assignment in script node exampl…
Wirasm Apr 27, 2026
06c3f9e
Merge upstream/dev into emdash/add-copilot-2er
popemkt Apr 27, 2026
454dbcb
test(copilot): cover MCP, skills, agents in e2e-copilot-all-features
popemkt Apr 27, 2026
a1d6209
chore(workflows): group experimental workflows under .archon/workflow…
Wirasm Apr 27, 2026
f9bad03
test(copilot): harden skill + agent assertions with unguessable tokens
popemkt Apr 27, 2026
ef16bac
docs(copilot): clarify single-auth-model + drop redundant outer quote…
popemkt Apr 27, 2026
e71c496
fix(workflows): export ARTIFACTS_DIR, LOG_DIR, BASE_BRANCH to bash no…
avro198 Apr 27, 2026
dcfb9d1
fix(workflow): substitute $nodeId.output refs in approval messages (#…
atlas-architect Apr 27, 2026
287bb35
feat(workflows): expose $LOOP_PREV_OUTPUT in loop node prompts (#1286…
voidborne-d Apr 27, 2026
6cf9883
feat(maintainer-standup): surface contributor replies since last run …
Wirasm Apr 28, 2026
2220ffe
feat(maintainer-workflows): cross-workflow review memory (#1458)
Wirasm Apr 28, 2026
0afbeb3
chore(deps): bump claude-agent-sdk to 0.2.121, codex-sdk to 0.125.0 (…
Wirasm Apr 28, 2026
ff90111
fix(claude): stop passing --no-env-file to native binary in dev mode …
Wirasm Apr 28, 2026
784444a
Merge remote-tracking branch 'upstream/dev' into emdash/add-copilot-2er
popemkt Apr 28, 2026
bf1f471
refactor(workflows): trust the SDK for model validation (#1463)
Wirasm Apr 28, 2026
7d06773
fix(cli): lazy-import bundled skill files so non-setup commands don't…
Wirasm Apr 28, 2026
d256c71
fix(docker): register safe.directory for all repos on bind-mount rest…
kagura-agent Apr 29, 2026
ff924e8
feat(maintainer): Pi/Minimax variant of maintainer-standup + dual-for…
Wirasm Apr 29, 2026
a0d4884
fix(providers/pi): tolerate prose preamble in structured-output respo…
Wirasm Apr 29, 2026
fccfe42
fix(workflows): concise failure messages for bash/script nodes (#1389…
Wirasm Apr 29, 2026
cbcca8c
fix(orchestrator): clear stale session ID on error_during_execution t…
kagura-agent Apr 29, 2026
4885ee6
fix(claude): honor CLAUDE_BIN_PATH in dev mode for libc-mismatch host…
Wirasm Apr 29, 2026
7e4ea40
fix(workflows): skip markdown code blocks in $nodeId.output validatio…
truffle-dev Apr 29, 2026
d3fc706
Merge remote-tracking branch 'upstream/dev' into emdash/add-copilot-2er
popemkt Apr 29, 2026
4f666c7
refactor(providers/copilot): drop isModelCompatible per #1463
popemkt Apr 29, 2026
2c65569
fix(providers/copilot): lazy-load @github/copilot-sdk to protect comp…
popemkt Apr 29, 2026
8ebacbf
fix(providers/copilot): explicit useLoggedInUser config wins over env…
popemkt Apr 29, 2026
5d0a90d
fix: ensure all PR-creating workflows target $BASE_BRANCH (#1479)
ericsoriano Apr 29, 2026
25531df
chore(deps): remove stale package-lock.json to clear Dependabot alert…
Wirasm Apr 29, 2026
db1c005
Release '0.3.10'
Wirasm Apr 29, 2026
f51600a
fix(ci): expand $HOME for CLAUDE_BIN_PATH in e2e-smoke jobs
Wirasm Apr 29, 2026
58d4886
Release 0.3.10 (#1488)
Wirasm Apr 29, 2026
fd6d75e
chore(homebrew): update formula to v0.3.10
Wirasm Apr 29, 2026
398afe0
Merge main into dev (restore pre-squash-merge history for open PRs)
Wirasm Apr 29, 2026
8847da8
fix(release-workflow): use regular merge for dev/main sync after squa…
Wirasm Apr 29, 2026
2945f2e
fix(homebrew): restore v0.3.10 formula on dev (#1491)
Wirasm Apr 29, 2026
8295ece
fix(workflows): stop sweeping scratch artifacts from every git add -A…
Wirasm Apr 30, 2026
1820a35
docs(release-skill): warn against --ff-only and reset --hard for dev/…
Wirasm May 1, 2026
4631b8e
feat(cli): add archon skill install command (#1445)
leex279 May 1, 2026
69b2c89
fix(docker): resolve Claude binary to glibc variant on Debian image (…
leex279 May 1, 2026
34302e0
Merge upstream/dev into emdash/add-copilot-2er
popemkt May 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .archon/workflows/defaults/archon-adversarial-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ nodes:
"status": "running"
}
STATEEOF
sed -i "s/SPRINT_COUNT_PLACEHOLDER/$SPRINT_COUNT/" "$ARTIFACTS/state.json"
STATE_TMP="$ARTIFACTS/state.json.tmp"
sed "s/SPRINT_COUNT_PLACEHOLDER/$SPRINT_COUNT/" "$ARTIFACTS/state.json" > "$STATE_TMP"
mv "$STATE_TMP" "$ARTIFACTS/state.json"

echo "{\"totalSprints\": $SPRINT_COUNT, \"appDir\": \"$ARTIFACTS/app\", \"artifactsDir\": \"$ARTIFACTS\"}"
timeout: 30000
Expand Down
32 changes: 32 additions & 0 deletions .archon/workflows/e2e-copilot-smoke.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# E2E smoke test — GitHub Copilot community provider
# Verifies: provider registration, SDK session start, simple prompt response.
# Auth: run `copilot login`, or provide COPILOT_GITHUB_TOKEN / GH_TOKEN / GITHUB_TOKEN.
name: e2e-copilot-smoke
description: 'Smoke test for the GitHub Copilot community provider.'
provider: copilot
model: gpt-5-mini

nodes:
- id: simple
prompt: 'Reply with exactly COPILOT_OK'
idle_timeout: 30000

- id: assert
bash: |
# The workflow engine substitutes $simple.output as a literal string
# before bash runs. Wrap in single quotes so command substitution
# (`$(...)`, backticks) inside a model response is treated as data,
# not executed. A literal `'` in the value would cause a syntax
# error, which fails the test loudly rather than running arbitrary
# shell — strict improvement over double-quoted interpolation.
output_raw='$simple.output'
if [ -z "$output_raw" ]; then
echo "FAIL: simple node returned empty output"
exit 1
fi
printf '%s\n' "$output_raw" | grep -F -q -- 'COPILOT_OK' || {
printf 'FAIL: expected COPILOT_OK, got: %s\n' "$output_raw"
exit 1
}
printf 'PASS: simple=%s\n' "$output_raw"
depends_on: [simple]
173 changes: 169 additions & 4 deletions .claude/skills/release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,67 @@ git fetch origin main

If not on dev or working tree is dirty, abort with a clear message.

### Step 1.5: Pre-flight compiled-binary smoke test (MANDATORY before any other step)

> **Why this is first**: releases have ended up with zero working binaries because a module-init crash or bundler bug only surfaces in `bun build --compile` output, not in `bun run`. CI catches it — but only AFTER the tag is pushed and a GitHub Release is created. By then the damage (empty release, broken `releases/latest`, broken `install.sh`) is already live. Failing here, before any user-visible change, keeps the blast radius at "no release was cut."

Run locally on the native target. This takes ~15-30s and is cheaper than discovering the problem after tag+release.

```bash
# Guard: only run this for Node/Bun projects with a CLI entry point + build-binaries script.
if [ -f scripts/build-binaries.sh ] && [ -f packages/cli/src/cli.ts ]; then
TMP_BINARY=$(mktemp)
trap "rm -f $TMP_BINARY" EXIT

# Compile for the native target only (not full cross-compile — that's CI's job).
# Match the real release flags so any bundler quirk reproduces locally.
bun build \
--compile \
--minify \
--target=bun \
--outfile="$TMP_BINARY" \
packages/cli/src/cli.ts

# Smoke test: the binary must start and exit 0 on a safe, non-interactive command.
# Use `--help` (NOT `version`). The `version` command's compiled-binary code
# path depends on BUNDLED_IS_BINARY=true, which is set by scripts/build-binaries.sh
# — but we're doing a bare `bun build --compile` here to keep the smoke fast,
# so BUNDLED_IS_BINARY is still `false`. That sends `version` down the dev
# branch of version.ts which tries to read package.json from a path that only
# exists in node_modules, producing a false-positive ENOENT. `--help` has no
# such dev/binary branch and exercises the same module-init graph we're
# actually testing. Must NOT touch network, database, or require env vars.
if ! "$TMP_BINARY" --help > /tmp/archon-preflight.log 2>&1; then
echo "ERROR: compiled binary crashed at startup"
cat /tmp/archon-preflight.log
echo ""
echo "This usually means a dependency has a module-init-time side effect that"
echo "fails in a compiled binary context (readFileSync of a path that only"
echo "exists in node_modules, etc.). Fix before cutting the release — do NOT"
echo "proceed to version bump."
exit 1
fi

# Also grep for known crash markers that exit 0 but print a fatal error
# (some module-init errors are caught by top-level try/catch but still log).
if grep -qE "Expected CommonJS module|TypeError:|ReferenceError:|SyntaxError:" /tmp/archon-preflight.log; then
echo "ERROR: compiled binary emitted a runtime error despite exit 0"
cat /tmp/archon-preflight.log
exit 1
fi

echo "Pre-flight binary smoke: PASSED"
fi
```

If this fails, **abort the release entirely** — do not bump version, do not modify CHANGELOG, do not create a PR. Surface the error to the user, point at the failing output, and stop. Recovery is: fix the bundler / dependency issue on a feature branch, merge to dev, re-run `/release`.

**Common failure modes this catches:**
- Bun `--bytecode` flag producing broken bytecode for the current module graph
- A dependency (e.g. an SDK) reading `package.json` or other files at module top level via paths that resolve fine in `node_modules/` but not next to a compiled binary
- Circular imports that break under minification but work under plain `bun run`
- A newly added package that ships CJS with an unusual wrapper shape

### Step 2: Detect Stack and Current Version

Detect the project's package manager and version file:
Expand Down Expand Up @@ -211,21 +272,53 @@ After the tag is pushed, `.github/workflows/release.yml` builds platform binarie

```bash
echo "Waiting for release workflow to finish uploading binaries..."
WORKFLOW_FAILED=0
for i in {1..30}; do
ASSET_COUNT=$(gh release view "vx.y.z" --repo coleam00/Archon --json assets --jq '.assets | length')
# Expect 7 assets: 5 binaries (darwin-arm64, darwin-x64, linux-arm64, linux-x64, windows-x64.exe) + archon-web.tar.gz + checksums.txt
if [ "$ASSET_COUNT" -ge 7 ]; then
echo "All $ASSET_COUNT assets uploaded"
break
fi

# Short-circuit: if the release workflow itself has failed, stop waiting.
# Hanging for 15 min when CI already crashed just delays the recovery path.
WORKFLOW_STATUS=$(gh run list --workflow release.yml --event push --limit 1 --json conclusion,status --jq '.[0] | "\(.status)|\(.conclusion)"')
if [[ "$WORKFLOW_STATUS" == "completed|failure" ]]; then
echo "Release workflow FAILED — no point waiting longer"
WORKFLOW_FAILED=1
break
fi

echo " Assets so far: $ASSET_COUNT/7 — waiting 30s (attempt $i/30)..."
sleep 30
done

if [ "$ASSET_COUNT" -lt 7 ]; 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
if [ "$WORKFLOW_FAILED" -eq 1 ] || [ "$ASSET_COUNT" -lt 7 ]; then
# Triage: rerun once in case it's transient, then check again.
RUN_ID=$(gh run list --workflow release.yml --event push --limit 1 --json databaseId --jq '.[0].databaseId')
echo "Release workflow failed on run $RUN_ID. Rerunning failed jobs once to confirm..."
gh run rerun "$RUN_ID" --failed
gh run watch "$RUN_ID" --exit-status --interval 30 || true

# Re-check asset count + run status after rerun.
ASSET_COUNT=$(gh release view "vx.y.z" --repo coleam00/Archon --json assets --jq '.assets | length')
if [ "$ASSET_COUNT" -ge 7 ]; then
echo "Rerun succeeded — all assets now present"
else
echo ""
echo "===== DETERMINISTIC CI FAILURE ====="
echo "The release workflow failed on two consecutive runs. This is NOT a flake."
echo "The tag and release exist but have no (or incomplete) assets."
echo ""
echo "install.sh and similar 'releases/latest' paths are now 404-ing."
echo "Proceeding with Homebrew/tap sync would publish a formula pointing at"
echo "missing or inconsistent binaries."
echo ""
echo "Jump to the 'Recovery: deterministic release CI failure' section at the"
echo "bottom of this skill and execute it. Do NOT continue past this point."
exit 1
fi
fi
Comment on lines +298 to 337

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Workflow-status detection and rerun logic has a few edge cases worth tightening.

Three issues, all in the same block:

  1. Non-failure terminal states are missed. [[ "$WORKFLOW_STATUS" == "completed|failure" ]] only short-circuits on failure. A cancelled, timed_out, or startup_failure conclusion will keep the loop spinning for the full 15 minutes and then fall through to the rerun path anyway. Consider [[ "$WORKFLOW_STATUS" == completed\|* && "$WORKFLOW_STATUS" != "completed|success" ]].

  2. Run lookup isn't pinned to the release tag. gh run list --workflow release.yml --event push --limit 1 returns the newest release.yml run — not necessarily the run triggered by vx.y.z. If any other push event lands on a watched ref between tag-push and this check, you'll read/rerun the wrong workflow. Safer: filter by tag SHA, e.g. --commit "$(git rev-parse vx.y.z)".

  3. gh run rerun --failed is a no-op on a still-running or already-succeeded run. The outer condition on line 297 also triggers on a pure loop timeout (WORKFLOW_FAILED=0 but ASSET_COUNT<7). In that case the rerun does nothing, gh run watch returns immediately, and we fall straight into the deterministic-failure path even though CI may still be mid-upload. Gate the rerun on WORKFLOW_FAILED=1 and handle the timeout case separately (e.g. extend the wait or bail with a distinct message).

🔧 Sketch of the tightened block
-  WORKFLOW_STATUS=$(gh run list --workflow release.yml --event push --limit 1 --json conclusion,status --jq '.[0] | "\(.status)|\(.conclusion)"')
-  if [[ "$WORKFLOW_STATUS" == "completed|failure" ]]; then
+  TAG_SHA=$(git rev-parse "vx.y.z" 2>/dev/null || echo "")
+  WORKFLOW_STATUS=$(gh run list --workflow release.yml ${TAG_SHA:+--commit "$TAG_SHA"} --limit 1 --json conclusion,status --jq '.[0] | "\(.status)|\(.conclusion)"')
+  # Treat any terminal non-success as failed (failure, cancelled, timed_out, startup_failure).
+  if [[ "$WORKFLOW_STATUS" == completed\|* && "$WORKFLOW_STATUS" != "completed|success" ]]; then
     echo "Release workflow FAILED — no point waiting longer"
     WORKFLOW_FAILED=1
     break
   fi
@@
-if [ "$WORKFLOW_FAILED" -eq 1 ] || [ "$ASSET_COUNT" -lt 7 ]; then
-  # Triage: rerun once in case it's transient, then check again.
-  RUN_ID=$(gh run list --workflow release.yml --event push --limit 1 --json databaseId --jq '.[0].databaseId')
-  echo "Release workflow failed on run $RUN_ID. Rerunning failed jobs once to confirm..."
-  gh run rerun "$RUN_ID" --failed
-  gh run watch "$RUN_ID" --exit-status --interval 30 || true
+if [ "$WORKFLOW_FAILED" -eq 1 ]; then
+  # Triage: rerun once in case it's transient, then check again.
+  RUN_ID=$(gh run list --workflow release.yml ${TAG_SHA:+--commit "$TAG_SHA"} --limit 1 --json databaseId --jq '.[0].databaseId')
+  echo "Release workflow failed on run $RUN_ID. Rerunning failed jobs once to confirm..."
+  gh run rerun "$RUN_ID" --failed
+  gh run watch "$RUN_ID" --exit-status --interval 30 || true
+elif [ "$ASSET_COUNT" -lt 7 ]; then
+  echo "Timed out waiting for assets but workflow has not failed. Inspect the run manually before retrying."
+  exit 1
+fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/skills/release/SKILL.md around lines 283 - 322, The workflow-status
short-circuit and rerun logic is brittle: update the check on WORKFLOW_STATUS
(set by gh run list) to treat any completed terminal conclusion other than
"completed|success" as a failure (e.g., match completed\|* and exclude
completed|success), change the run lookup that sets RUN_ID to pin to the tag's
commit (use the commit SHA for "vx.y.z" instead of the latest workflow run) so
you rerun the correct run, and only call gh run rerun --failed when
WORKFLOW_FAILED=1 (i.e., the workflow actually completed with a non-success
conclusion); if ASSET_COUNT timed out without WORKFLOW_FAILED, handle that
separately (either extend wait or bail with a clear timeout message) rather than
performing a no-op rerun and falling into the deterministic-failure path.

```

Expand Down Expand Up @@ -376,9 +469,81 @@ Also run `/test-release curl-mac x.y.z` to cover the curl install path. The two

If you have a VPS available, also run `/test-release curl-vps x.y.z <vps-target>` to verify the Linux binary.

## Recovery: deterministic release CI failure

Reached here because Step 10 detected two consecutive workflow failures. The tag `vx.y.z` is pushed, the GitHub release exists, but assets are missing or incomplete. Every `install.sh` run currently resolves `releases/latest` to this broken release and 404s on download. Homebrew users are safe because Step 10's atomic formula update was blocked.

**Do not re-run the release workflow a third time hoping it succeeds.** If the failure was reproducible twice, it's a code bug — you need to ship code to fix it.

### Immediate mitigation (restore `install.sh`)

Delete the GitHub Release so `releases/latest` falls back to the previous version. Keep the git tag — tag immutability matters and there are no shipped artifacts pointing at it anyway.

```bash
gh release delete "vx.y.z" --yes
# Do NOT delete the tag:
# git push --delete origin vx.y.z ← do not run
# Tag stays so git history records the attempt; no release means no assets
# means releases/latest resolves to the prior working release.
```

Verify:

```bash
gh api repos/coleam00/Archon/releases/latest --jq '.tag_name'
# should now print the prior version (e.g. v0.3.6), not vx.y.z
```

### Diagnose

The release workflow logs tell you which target failed and at what stage (compile vs. smoke-test vs. upload):

```bash
gh run list --workflow release.yml --limit 2 --json databaseId,conclusion
gh run view <RUN_ID> --log-failed
```

Common causes:
- **Bundler/bytecode bug** — Bun `--bytecode` produces invalid output for the current module graph. Symptom: `TypeError: Expected CommonJS module to have a function wrapper` at binary startup. Historically caused by a new dependency's CJS/ESM shape interacting with `--bytecode` — dropping the flag or lazy-importing the offending module has been the fix.
- **Module-init crash** — a dependency does `readFileSync('package.json')` or similar at module top level via a path that exists in `node_modules/` but not next to a compiled binary. Symptom: every binary subcommand crashes immediately; error typically mentions a missing file adjacent to `process.execPath`. Fix by lazy-importing the dependency behind the code path that actually uses it.
- **Smoke-test timeout on Windows** — not actually a bug in the code; the Windows runner is slow. Rerun once; if it recurs, bump the test timeout.

Step 1.5 now runs a local compiled-binary smoke test before any user-visible step. If the failure mode above reproduces locally, you've found it. If it doesn't, the bug is platform-specific (Windows cross-compile, Linux glibc, etc.) and you need the CI logs.

### Fix and re-release as the NEXT patch

**Do not reuse `vx.y.z`.** Cut `vx.y.(z+1)` (or next-minor if warranted) with the fix. Rationale:
- Tag immutability: `vx.y.z` is already recorded in git history and release cache
- Semver clarity: users and tooling should see a new version number when the bits change
- Audit trail: "v0.3.7 was cut but had no shipped binaries; v0.3.8 is the first release with <fix>" is cleaner than rewriting v0.3.7

Steps:

1. Cut a fix branch off dev, implement the fix, PR to dev, merge.
2. Re-run `/release` (it will bump to the next patch — e.g. `0.3.8` — automatically).
3. Step 1.5's pre-flight smoke will catch the same bug locally if the fix didn't actually fix it. Iterate until it passes before tagging.

### CHANGELOG note for the hotfix release

Include a line in the new release's CHANGELOG that references the broken prior version so users understand why there's no binary artifact under that tag:

```markdown
### Fixed

- **First release with working compiled binaries after vx.y.z's <bug>.** vx.y.z was tagged but its binary smoke test failed deterministically (see RUN_ID in CI history). The tag is preserved for history; this release (vx.y.(z+1)) is the first with shipped binaries. `install.sh` and Homebrew were never updated to vx.y.z, so users were not exposed to the broken state.
```

### What NOT to do

- **Do not force-push or rewrite the tag.** Once a tag exists, it's a public promise of that SHA. Deleting and re-creating to a different SHA is tag-spoofing and breaks any downstream that cached the original.
- **Do not skip this recovery path to "just push more binaries to the broken release".** The release exists with a specific commit SHA; uploading binaries built from a newer SHA creates binary/source drift that is hard to diagnose later.
- **Do not update the Homebrew formula before v0.3.(z+1) is fully shipped.** The formula should always point at a version with all 7 assets uploaded and `/test-release brew` passing.

## Important Rules

- NEVER force push
- **NEVER skip Step 1.5 (pre-flight compiled-binary smoke).** If the stack is a Bun/Node project with a build-binaries script, the `bun build --compile` smoke test runs before version bump, PR, or tag. Skipping it means every bundler regression or module-init crash only surfaces after the tag is pushed — by which point `releases/latest` is already 404-ing for every user. The ~30s cost is paid to keep the failure mode local.
- If Step 1.5 fails, **abort the release** and fix the underlying issue on a feature branch. Do not "just skip it" and hope CI doesn't repro the problem.
- NEVER skip the review step — always show the changelog before committing
- NEVER include "Co-Authored-By: Claude" or any AI attribution in the commit
- NEVER add emoji to changelog entries unless the user asks
Expand Down
33 changes: 33 additions & 0 deletions .claude/skills/test-release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ About to test:
Path: brew (Homebrew tap on macOS)
Version: 0.3.1 (expected)
Cleanup: will uninstall after tests (brew uninstall + untap)
If `archon-stable` symlink is detected in Phase 2, it will be
restored at the end of Phase 5 by reinstalling the tap formula.

Proceed? (y/N)
```
Expand Down Expand Up @@ -112,6 +114,18 @@ gh release view v<version> --repo coleam00/Archon --json tagName,assets --jq '{t

If the release does not exist or has no assets, abort with a clear message. Do not proceed to install a non-existent release.

4. **Detect persistent `archon-stable` install (brew path only).** If the user has renamed a prior brew install to `archon-stable` (the dual-homebrew pattern — see `~/.config/fish/functions/brew-upgrade-archon.fish`), Phase 5's `brew uninstall` will wipe it. Capture the state so Phase 5b can restore it:

```bash
ARCHON_STABLE_WAS_INSTALLED=""
if [ -L /opt/homebrew/bin/archon-stable ] || [ -L /usr/local/bin/archon-stable ]; then
ARCHON_STABLE_WAS_INSTALLED="yes"
echo "Detected persistent archon-stable — will restore after Phase 5 uninstall."
fi
```

Export `ARCHON_STABLE_WAS_INSTALLED` into the environment used by Phase 5b. Only applies to the `brew` path — `curl-mac` and `curl-vps` don't go through brew and don't disturb `archon-stable`.
Comment on lines +117 to +127

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Scope archon-stable detection to the active Homebrew prefix.

Checking both common prefixes can false-positive when the other Homebrew installation has archon-stable; restore then uses brew --prefix and may recreate it in the wrong prefix.

Proposed fix
 ARCHON_STABLE_WAS_INSTALLED=""
-if [ -L /opt/homebrew/bin/archon-stable ] || [ -L /usr/local/bin/archon-stable ]; then
+BREW_BIN="$(brew --prefix)/bin"
+if [ -L "$BREW_BIN/archon-stable" ]; then
   ARCHON_STABLE_WAS_INSTALLED="yes"
-  echo "Detected persistent archon-stable — will restore after Phase 5 uninstall."
+  echo "Detected persistent archon-stable at $BREW_BIN/archon-stable — will restore after Phase 5 uninstall."
 fi
 if [ -n "$ARCHON_STABLE_WAS_INSTALLED" ]; then
   echo "Restoring archon-stable (detected before test)..."
   brew tap coleam00/archon
   brew install coleam00/archon/archon
   BREW_BIN="$(brew --prefix)/bin"
   if [ -e "$BREW_BIN/archon" ]; then
-    mv "$BREW_BIN/archon" "$BREW_BIN/archon-stable"
+    mv -f "$BREW_BIN/archon" "$BREW_BIN/archon-stable"
     echo "archon-stable restored: $(archon-stable version 2>/dev/null | head -1)"

Also applies to: 371-383

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/skills/test-release/SKILL.md around lines 117 - 127, The detection
currently checks both /opt/homebrew and /usr/local and can false-positive;
change detection to use the active Homebrew prefix (via brew --prefix) and only
test that prefix's bin for the archon-stable symlink, set/export
ARCHON_STABLE_WAS_INSTALLED accordingly, and ensure Phase 5b reads that exported
variable to restore into the same prefix; update the logic that currently
references /opt/homebrew/bin/archon-stable and /usr/local/bin/archon-stable to
instead derive the path from brew --prefix so restore uses the correct prefix.


## Phase 3 — Install

### Path: brew
Expand Down Expand Up @@ -352,6 +366,25 @@ archon version | head -1
# should match the dev version captured in Phase 2
```

**Restore `archon-stable` if it existed before the test** (dual-homebrew pattern — see Phase 2 item 4):

```bash
if [ -n "$ARCHON_STABLE_WAS_INSTALLED" ]; then
echo "Restoring archon-stable (detected before test)..."
brew tap coleam00/archon
brew install coleam00/archon/archon
BREW_BIN="$(brew --prefix)/bin"
if [ -e "$BREW_BIN/archon" ]; then
mv "$BREW_BIN/archon" "$BREW_BIN/archon-stable"
echo "archon-stable restored: $(archon-stable version 2>/dev/null | head -1)"
else
echo "WARNING: brew install succeeded but $BREW_BIN/archon missing — check formula"
fi
fi
```

> **Note on the restored version**: this reinstalls from whatever the tap currently ships, which is typically the release you just tested (so `archon-stable` ends up at the newly-tested version). That's usually what the operator wants — you just verified the new release works, and you want `archon-stable` pointed at it. If you were testing an older version for back-version QA, the restored `archon-stable` will be the *current* tap formula, not the pre-test version. For that rare case, the operator should re-run `brew-upgrade-archon` manually after the test.

### Path: curl-mac

```bash
Expand Down
10 changes: 8 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,12 @@ jobs:

# Run without CLAUDE_BIN_PATH set. Expect a clean resolver error.
# Capture both stdout and stderr; we only care that the resolver message is present.
# --no-worktree skips isolation: this test exercises the Claude resolver
# path, not worktree setup. Without it we hit the worktree auto-sync added
# in #1310 first and fail on "neither origin/HEAD nor origin/main exist"
# because the fresh `git init` tmp repo has no origin configured.
set +e
OUTPUT=$(env -u CLAUDE_BIN_PATH "$BIN" workflow run archon-assist "hello" 2>&1)
OUTPUT=$(env -u CLAUDE_BIN_PATH "$BIN" workflow run archon-assist --no-worktree "hello" 2>&1)
EXIT_CODE=$?
set -e
echo "$OUTPUT"
Expand Down Expand Up @@ -181,8 +185,10 @@ jobs:
git init -q
git -c user.email=ci@example.com -c user.name=ci commit --allow-empty -q -m init

# --no-worktree: same rationale as the negative-case test above — this
# test exercises the Claude subprocess spawn path, not worktree setup.
set +e
OUTPUT=$(CLAUDE_BIN_PATH="$CLI_PATH" "$BIN" workflow run archon-assist "hello" 2>&1)
OUTPUT=$(CLAUDE_BIN_PATH="$CLI_PATH" "$BIN" workflow run archon-assist --no-worktree "hello" 2>&1)
EXIT_CODE=$?
set -e
echo "$OUTPUT"
Expand Down
Loading