🤖 Hermit defaults `CARGO_HOME` to each checkout's `.hermit/rust`, so
every worktree sees dependency sources at a different absolute path.
sccache keys Rust compiles on those paths, which means the same
dependency built in two worktrees almost never hits the cache. This pins
`CARGO_HOME` to `${HOME}/.cargo` in `bin/hermit.hcl`, the same setting
buzz-app already uses.
CI also lines up better: the workflows that activate Hermit and cache
`~/.cargo/registry` / `~/.cargo/git` were caching a directory cargo
wasn't using (cargo wrote to `$GITHUB_WORKSPACE/.hermit/rust`). With
this change those cache paths are where cargo actually puts the registry
and git checkouts. The `MESH_ROOT` lookups in `_ci-desktop-macos.yml`,
`release.yml`, and `signed-macos-canary.yml` already read
`${CARGO_HOME:-$HOME/.cargo}`, so they follow either way.
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Older macOS versions display Buzz’s opaque icon as an oversized square.
Bake rounded corners and transparent padding into the macOS `.icns`,
preserving the original bee artwork and leaving other platforms
unchanged.
Generation uses Hermit-pinned uv, Python 3.14.3, and Pillow 12.2.0.
`scripts/macos-icon.sh --check` regenerates temporarily and compares all
decoded representations; macOS CI runs it before packaging. Pixel
reproducibility is verified independently of Apple’s ICNS container
encoding.
Validated native AppKit preview, all 10 representations, fresh-cache
generation, one-pixel/missing-representation drift rejection, workflow
lint, and pre-push checks. Full local `just ci` is running. Packaged
Dock checks on older/current macOS remain outstanding.
---------
Signed-off-by: kenny lopez <klopez4212@gmail.com>
Codex CLI can leave a PTY descendant holding the action's inherited
stdio after the turn completes. The `runCodexExec.ts` wrapper waits on a
`close` event that never fires, so the `Review pull request` step hangs
until the job timeout kills it — discarding the finished review the CLI
already wrote to disk.
The CLI writes the completed review to the `--output-last-message` file
(exposed as `output-file`) **before** the hang. This PR adds a salvage
step that recovers it, and sets the step and job timeouts to preserve
the full 30-minute Codex execution budget.
**Changes (`codex-security-review.yml`):**
- Add `output-file: ${{ runner.temp }}/codex-review.json` to the `Review
pull request` step so the CLI writes the result before the hang.
(`runner` context is valid in `steps.with`; not in `jobs.env`.)
- Add `timeout-minutes: 30` and `continue-on-error: true` to the Codex
step — a hang now costs ≤30 minutes instead of 40, and the salvage step
still runs.
- Set job `timeout-minutes: 40` to give setup, step cancellation, and
salvage sufficient headroom without colliding with the Codex execution
budget. The original 30-minute job timeout was too narrow: evidence from
run
[33114428326](https://github.com/block/buzz/actions/runs/33114428326/job/98665369165)
shows completed output appearing 28m46s after step start, meaning a
20-minute step timeout could kill a legitimate review before the salvage
file exists.
- Add a `Salvage review output` step with `if: always()`: prefers
`steps.run_codex.outputs.final-message` on a clean exit; falls back to
the output file when the step timed out. The output file path is set in
the step's own `env` block (`CODEX_OUTPUT_FILE: ${{ runner.temp
}}/codex-review.json`), where `runner` is valid. Validates shape
(non-empty JSON object, has `overall_risk`); fails the job hard if
neither source is present.
- Wire the job `outputs.review_json` to
`steps.salvage.outputs.review_json`.
**Changes (`Justfile`, `ci.yml`):**
- Add `actionlint .github/workflows/codex-security-review.yml` to
`security-review-check` so expression-validity errors are caught
locally.
- Provision `actionlint` via Hermit (pinned v1.7.12) rather than a
one-off `Install actionlint` curl step, so the same binary is used
locally and in CI.
**Security posture is unchanged:** the salvage step reads the action's
own output and a file written to `runner.temp` — neither is
PR-controlled. Credential-stripping env block on the Codex step is
untouched.
Note this is a temporary workaround until
https://github.com/openai/codex-action/issues/169 is addressed
---------
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Lefthook selected pre-push lanes from the file set `git diff --name-only
HEAD @{push}` returns, which is overinclusive two different ways:
- **Rebase/merge poisoning.** `@{push}` is a two-dot diff against the
branch's last-pushed tip. After a rebase onto newer main — or the `git
merge origin/main` that `check-branch-skew.sh` itself instructs agents
to run — that diff includes everything `main` changed since the last
push, so `mobile-checks`, the `desktop-*` lanes, and `rust-tests` fire
on branches that never touched those trees (a recent relay-only push
discovered 266 files, 26 of them `mobile/`).
- **Brand-new branch in a worktree.** When `@{push}` doesn't resolve,
lefthook reads `$GIT_DIR/refs/remotes/origin/HEAD` — which doesn't exist
in a linked worktree's git-dir — then falls back to the
alphabetically-first `HEAD ->` remote. In a clone with a stale
third-party remote sorted before `origin`, that diffs against a frozen
tip and runs the full suite. All agent work happens in worktrees.
## Fix
Add `files: git diff --name-only origin/main...HEAD` to the six globbed
pre-push lanes (`rust-tests`, `desktop-check`, `desktop-typecheck`,
`desktop-test`, `desktop-tauri-checks`, `mobile-checks`). The three-dot
form is the branch's merge-base diff against `origin/main` — the same
base resolution `scripts/check-file-sizes-core.mjs` (`resolveBaseRef`)
and CI's dorny/paths-filter already use. A lane now fires only when this
branch actually changed a file it covers; `main`-side changes never
re-trigger it, and a new branch diffs against `origin/main` (empty diff
→ all globbed lanes skip) rather than a stale remote. `branch-skew` and
`file-size-check` stay unfiltered by design.
Also pin hook dispatch to the Hermit-managed lefthook. The generated
`.git/hooks/*` dispatchers prefer `$LEFTHOOK_BIN`, then a `lefthook` on
`PATH` — so a Homebrew lefthook shadowed the pinned 2.1.3. A `rc:
bin/.lefthookrc` (a lefthook config key `lefthook install` bakes into
every dispatcher) exports `LEFTHOOK_BIN` to `bin/lefthook`, so a push
runs the pinned version regardless of `PATH`. `just hooks` already runs
`lefthook install`, so re-running it picks up the `rc` line — no recipe
change needed.
`AGENTS.md` documents that lanes are scoped by merge-base diff and that
agent shells should lead `PATH` with `./bin` so pinned tools win.
## Verification
Reproduced both bugs in a scratch repo mirroring the worktree +
stale-remote topology, with lanes stubbed to echo on selection:
- **Bug 1:** relay-only branch, `git merge origin/main` (main carried
`mobile/` + `desktop/` changes). Without the fix, `desktop-check` and
`mobile-checks` fired; with it, three-dot diff = only the relay file,
all globbed lanes skip.
- **Bug 2:** brand-new branch, no upstream, in a linked worktree with an
alphabetically-first stale remote. Confirmed `@{push}` is `fatal`,
`refs/remotes/origin/HEAD` is absent from the worktree git-dir, and the
stale remote's `HEAD ->` sorts first. Without the fix it fired lanes off
the stale tip; with it, lanes scope to `origin/main` merge-base and
skip.
- **Positive controls:** a `mobile/**` commit still fires
`mobile-checks`; a `crates/**` commit still fires `rust-tests` +
`desktop-tauri-checks`.
- **Dispatch:** with Homebrew lefthook 2.1.10 on `PATH`, running the
real generated `.git/hooks/pre-push` under `LEFTHOOK_VERBOSE=1` shows it
sourcing `bin/.lefthookrc`, exporting `LEFTHOOK_BIN`, and running
`lefthook v2.1.3`.
- This PR's own push (a brand-new branch in a linked worktree — the bug
2 case) ran only `branch-skew` and `file-size-check`; no
mobile/desktop/rust lanes fired, since the branch touches only
`lefthook.yml`, `AGENTS.md`, and `bin/.lefthookrc`.
---------
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
## Problem
User report:
```
Buzz Node mismatch: Buzz supplies Node 24.14.0; OpenClaw requires >=24.15.0. All 10 ACP workers immediately crash.
```
Buzz supplies Node to agent processes from two places, and both were
below OpenClaw's floor:
| Supply path | Was | Now |
|---|---|---|
| hermit dev env (`bin/.node-*.pkg`) — the 24.14.0 in the report |
24.14.0 | **24.15.0** (newest hermit publishes; satisfies `>=24.15.0`) |
| Desktop managed runtime (`managed_node.rs` / `managed_node_paths.rs`)
| v24.11.0 | **v24.18.0** (current latest v24) |
The managed runtime sits **first** on the worker PATH
(`managed_agents/runtime/path.rs`), so a user-installed newer Node can't
mask a stale managed one — the pin itself has to move.
## Verification
- SHA-256 digests for all six platform artifacts taken from
`https://nodejs.org/dist/v24.18.0/SHASUMS256.txt`; darwin-arm64
independently re-verified by downloading the tarball (hash match),
extracting, and running `bin/node --version` → `v24.18.0`.
- All artifacts within `MANAGED_NODE_MAX_BYTES` (largest linux-x64 at 57
MB < 90 MB cap); tar.gz layout keeps the `node-vX-platform/bin/node`
shape `verify_node_tree` expects.
- `cargo test --lib` in `desktop/src-tauri`: **1801 passed, 0 failed**
at this commit; `cargo fmt --check` + `cargo clippy --lib -D warnings`
clean.
- Existing readiness check (`node --version == MANAGED_NODE_VERSION`)
makes upgrade automatic: installed v24.11.0 trees fail readiness and the
installer stages v24.18.0 atomically (rename with `.old` rollback —
existing logic, unchanged).
Note: CI `node-version: 24.14.1` pins in
`release.yml`/`windows-canary.yml` are build-env only (already `>=`
nothing OpenClaw touches) and left alone to keep this minimal.
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>