Two production-readiness fixes motivated by the v0.22.2 post-mortem.
1. aarch64-linux: native ARM runner instead of cross-compile
v0.22.2 consistently failed `Install aarch64 cross-linker` (apt
gcc-aarch64-linux-gnu) on the ubuntu-latest x86_64 runner. Was
carried as `experimental: true` so non-blocking, but meant no
aarch64-linux Rust tarball ever shipped.
Fix: move to ubuntu-24.04-arm (native ARM64 runner). Rust builds
aarch64-unknown-linux-gnu HOST-NATIVELY — no cross-linker, no
`.cargo/config.toml` linker override. `experimental: false` now —
native path is reliable.
2. Publish step: softprops/action-gh-release → `gh release create` CLI
v0.22.2 softprops/action-gh-release v2.6.2 uploaded all 15 assets
successfully but exited with `failure` due to a metadata-update
race: asset uploaded to GitHub's blob store, then the subsequent
PATCH to set the asset's `name` returned 404 because the Releases
metadata API hadn't caught up yet (eventual consistency). Workflow
failure → Release left in Draft. We had to promote it manually
(`gh release edit --draft=false`) and re-upload one missing sha256.
Fix: replace the action with `gh release create` + `gh release
upload --clobber` in a bash step.
- Idempotent: existing release gets updated in place.
- No metadata PATCH race: CLI never patches, it creates fresh.
- Retry loop: up to 3 tries per asset on transient network errors.
- `--clobber` means re-runs replace cleanly.
- GitHub CLI is pre-installed on every runner, zero new deps.
Verified post-polish on v0.22.2: 16/16 assets present, Release
Published, `kei-mcp-server-darwin-arm64` + `keisei` both execute on
this MacBook (arm64) — adapter list shows Claude Code detected at
project+user scope. SHA256 of `keisei-aarch64-apple-darwin.tar.gz`
verified OK.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Root cause: macos-13 Intel Mac runners were deprecated by GitHub. The
darwin-x64 bun matrix job was sitting queued for 1.5-2.5 hours on every
tag push (v0.21.1, v0.22.0, v0.22.1) and never picked up a runner. The
release job has `needs: [build-release, build-mcp-binary]` so it waited
forever — NO GitHub Releases were created for any v0.22 tag.
Fix: bun cross-compiles to every target (Linux / macOS / Windows,
x64 / arm64) from any host via `--target=<bun-target>`. Consolidate the
entire build-mcp-binary matrix onto ubuntu-latest. Binaries remain
native per-target (correct Mach-O / ELF / PE format preserved by bun
--target flag).
Side effects:
- Faster: Linux runners provision in seconds vs macOS in minutes
- No macOS quota cost (free tier: 2000 min/month, macOS = 10x multiplier)
- No runner starvation on tag push
- `continue-on-error` arm64-linux carve-out removed (no longer needed —
all jobs now on the same runner pool with equal reliability)
Verified locally: bun 1.1+ supports all 5 target triples from Linux host.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three layers of defense against the dtolnay-SHA-class bug reaching main
(today's incident: agent SHA-pinned dtolnay/rust-toolchain with a pin
that was real but semantically wrong — lost 'install current stable'
meaning, locked to rust 1.94.1 branch tip, broke CI).
Layer 1 — actionlint static lint
scripts/install-actionlint.sh (65 LOC) — installs rhysd/actionlint
v1.7.12 [VERIFIED] to ~/.local/bin or suggests brew install.
scripts/lint-workflows.sh (40 LOC) — runs actionlint on
.github/workflows/*.yml, exit 0 on clean, advisory when binary
missing.
Layer 2 — SHA existence check (today's bug class)
scripts/validate-workflow-shas.sh (98 LOC) — extracts every
'uses: <repo>@<40-hex>' from workflow files + dependabot.yml,
checks each via GitHub REST commits API (exit 200/404/422).
Supports 'validate-workflow-shas: skip=<reason>' trailing
comment for intentional exceptions. Falls back to anonymous
API (60/hr quota) if GITHUB_TOKEN probe fails.
DESIGN PIVOT from spec: spec said 'git ls-remote <repo> <sha>'
but that only resolves REFS (branch/tag tips), not arbitrary
commit SHAs — would have given false-positive 100% MISSING
report. Switched to REST API /commits/{sha} for unambiguous
200/404/422.
Layer 3 — CI gate
.github/workflows/ci.yml — new 'workflow-lint' job after
shell-lint. Installs actionlint + runs both scripts on every
push to main and PR. Blocks CI on any fabricated SHA.
Layer 4 — optional pre-commit hook
scripts/pre-commit-workflow-lint.sh (54 LOC) — detects staged
.github/workflows/*.{yml,yaml} + .github/dependabot.yml
changes, runs layers 1+2, blocks commit on failure.
Install via: ln -sf ../../scripts/pre-commit-workflow-lint.sh
.git/hooks/pre-commit
REAL EXECUTION VERIFIED (not claim-only):
- actionlint ran: zero findings on current workflows
- validate-workflow-shas.sh ran: 21 SHA pins checked, 21 OK,
0 MISSING (confirms all current v0.19.1+ pins resolve)
- bash -n on every new script: clean
- bash-3.2 parser bug workaround: case-in-subshell → grep -E
RULE 0.2 exception #6 (shell is external convention for git hooks
+ GH Actions runs — Rust rewrite would add zero value).
RULE 0.13 respected — no git invocations except read-only API calls.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Same fix as ci.yml in f833a36 applied to release.yml — two more
occurrences of the SHA-pinned toolchain that locks to rust 1.94.1
branch tip.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TWO CI failures on v0.19.1 SHA-pin commit cb45a27 traced to:
1. dtolnay/rust-toolchain SHA pin accidentally locked to rust 1.94.1
branch tip, not the stable-latest behaviour.
Validator V-2026-04-22 confirmed the pinned SHA (3c5f7ea) points at
the branch tip that added 1.94.1 patch support — functionally
equivalent to pinning a specific Rust version, not 'install stable'.
Runner image may have had newer / incompatible stable installed
system-wide; mixing caused cargo test failures.
Revert to @stable tag. Documented as explicit exception to RULE H5
(SHA-pin everything) in the line comment — dtolnay is a trusted
maintainer (serde/anyhow/cxx author), @stable is the canonical
semantic pointer for this action.
2. shell-lint job exit 1 despite continue-on-error: true on the
shellcheck step. The flag doesn't always suppress the step-level
exit code in GH Actions annotation stream when the step is the
LAST meaningful step. Add explicit '|| echo warnings' suffix to
guarantee the step exits 0 even on shellcheck findings.
Expected outcome: 3 Rust jobs + shell-lint green on next push.
ts-packages already green (they use actions/setup-node@<sha> which
resolves cleanly to v4.4.0).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bun is a monorepo tool — lockfile lives at workspace root
(_ts_packages/bun.lock), not per-subpackage. Placeholder at
_ts_packages/packages/mcp-server/bun.lock was the wrong path.
Changes:
- Generated real _ts_packages/bun.lock (626 lines) via 'bun install'
(bun 1.3.13, auto-migrated from package-lock.json)
- .github/workflows/release.yml working-directory:
_ts_packages/packages/mcp-server → _ts_packages (workspace root)
- BUILD.md Lockfile section rewritten to document workspace-root
location + coexistence with package-lock.json (L2 audit finding
partially resolved — full consolidation deferred to v0.20)
release.yml build-mcp-binary job now has real lockfile to consume —
H4 'tag build fails on missing lockfile' gate still active but now
there's something actually committed to satisfy it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 1 of exobrain architecture. Ships TS MCP server as a static
binary so users on machines without Node can run KeiSeiKit (USB /
flashdrive / air-gapped scenarios).
.github/workflows/release.yml (+62 LOC) — new build-mcp-binary job:
- 5-target matrix: darwin arm64/x64, linux arm64/x64, windows x64
- bun build --compile, linux arm64 continue-on-error (ARM runners
less reliable)
- Artifact kei-mcp-server-<os>-<arch>[.exe] + sha256
- release job now needs [build-release, build-mcp-binary]
install/lib-rust.sh (+50 LOC) — have_prebuilt_mcp_server() +
report_mcp_server_binary_status(); KEI_SKIP_MCP_BUILD=1 env
flag skips bun/npm install when a prebuilt binary is present.
File 165 LOC (<200 limit).
_ts_packages/packages/mcp-server/package.json — scripts.build:native
+ 5 per-target aliases (macos-arm, macos-x64, linux-x64,
linux-arm, win-x64) for local dev.
_ts_packages/packages/mcp-server/BUILD.md (NEW, 52 LOC) — local
compile guide per platform + Gatekeeper/code-sign notes +
cites bun docs [VERIFIED: https://bun.sh/docs/bundler/executables].
README.md pre-built-binaries section gains 'MCP server binary'
subsection (download, chmod +x, xattr -d com.apple.quarantine for
macOS, UAC note for Windows).
CHANGELOG.md [Unreleased] bullet added.
Output size: ~90 MB per binary (bundled bun runtime). Acceptable
trade for zero-dep USB distribution.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>