fix: redirect Bazaar Zed installs - #1011
Conversation
hanthor
left a comment
There was a problem hiding this comment.
spawn_brew() in hooks.py runs {brew} tap --trust {tap} && {brew} install --cask {app} for the new Zed hook. brew tap --trust is invalid on Homebrew 6.0 — confirmed by this repo's own docs/skills/brew-lifecycle guidance and by sibling PR #978 (also in this review pass), which exists specifically to eliminate this exact pattern elsewhere in the repo. Homebrew 6.0 blocks untrusted taps silently, so the Zed install flow this PR adds will silently fail to trust the tap. Needs {brew} tap {tap} && {brew} trust {tap} && ... instead. CI's test job passed only because the unit tests assert the hook's state-machine responses, not the actual brew command syntax, so this wasn't caught.
Generated by Claude Code
…luefin#1034) ## Test Improvement Adds `tests/test_shared_just.bats` — 14 BATS cases covering `system_files/shared/usr/share/ublue-os/just/shared.just`, which had **zero** test coverage. ### Why this file `powerwash` is the most destructive recipe in the repo: it ends in `sudo bootc install reset --experimental`, an irreversible factory reset. It is guarded by two `gum choose` confirmations plus a `bctl powerwash` fast path. None of that gating was verified by any test. ### How The `powerwash` body is a `#!/usr/bin/bash` shebang recipe with no `just` interpolation, so the test extracts it verbatim and **executes** it against stubbed `bctl`/`gum`/`sudo` binaries. This exercises real control flow rather than grepping recipe text, and requires no `just` binary in CI. Asserted behavior: - `bctl` present → delegates to `bctl powerwash`, and `gum`/`sudo` are never invoked - declining the first confirmation → cancels, only one prompt, no `sudo` - declining the second confirmation → cancels, no `sudo` - non-affirmative (`maybe`) and empty answers → treated as declines - two prompts are always required; a single "yes" never wipes - both confirmations → `sudo bootc install reset --experimental`, exactly once - both prompts carry their warning text - `toggle-tpm2` uses the absolute `/usr/bin/luks-tpm2-autounlock` path All 14 pass locally (bats 1.14.0). Mutation-checked: deleting the second confirmation from `shared.just` fails 3 of the tests. ### Scope / disjointness Claimed ground: **`tests/test_shared_just.bats` only** — one new test file, no production code touched. Deliberately does **not** touch `.github/workflows/unit-tests.yml` (claimed by open PRs projectbluefin#942 and projectbluefin#961) and does **not** touch `system.just` (projectbluefin#942), `apps.just` (projectbluefin#1007), `default.just` (projectbluefin#1003), `bonedigger-report` (projectbluefin#1010), `hooks.py` (projectbluefin#1011), or `ublue-fastfetch` (projectbluefin#1005). CI registration of this and the other test files not yet wired into `unit-tests.yml` is tracked separately by projectbluefin#968. --- *Filed by quality agent (hold-gated mode). Human review required. Do not merge on my behalf.* — hive: agent=quality backend=copilot model=claude-opus-5 Signed-off-by: kubestellar-hive[bot] <kubestellar-hive[bot]@users.noreply.github.com> Co-authored-by: kubestellar-hive[bot] <kubestellar-hive[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Escalating the earlier When this was first reviewed, the argument was that #978 existed to remove this pattern elsewhere. Since then #941 has merged (
'bash', '-c', f'{brew} tap --trust {tap} && {brew} install --cask {app}'
Two things make this easy to miss, worth fixing together: 1. The new tests cannot catch it. 2. The failure is swallowed at runtime. In try:
spawn_brew('ublue-os/experimental-tap/zed-linux', 'ublue-os/experimental-tap')
except:
passA bare Suggested fix — match what f'{brew} tap {tap} && {brew} trust {tap} && {brew} install --cask {app}'plus narrowing the bare The hook design itself is fine and the Bazaar dialog wiring reads correctly — this is one command string and the test that would have caught it. Generated by Claude Code |
castrojo
left a comment
There was a problem hiding this comment.
Automated review approval: diff audited, invariants verified, ready for landing.
|
Verified: Homebrew 6.0 command split (can1357/tap |
Dismissing stale review: Homebrew 6.0 tap/trust syntax defect resolved in commit 5baf659 and all CI checks passed.
castrojo
left a comment
There was a problem hiding this comment.
LGTM: verified tests pass and brew syntax is valid.
castrojo
left a comment
There was a problem hiding this comment.
LGTM: Verified brew tap and trust commands, hook state-machine tests pass, all CI green.
castrojo
left a comment
There was a problem hiding this comment.
LGTM: Audited and verified.
castrojo
left a comment
There was a problem hiding this comment.
Approved: Bazaar Zed hooks use valid Homebrew tap & trust syntax and pass unit tests.
There was a problem hiding this comment.
Correctness review (automated, hive review-swarm)
Medium — new default tap in spawn_brew doesn't match the taps used by existing callers.
spawn_brew now defaults to tap='ublue-os/tap' and prepends brew tap {tap}; brew trust {tap} to every invocation (system_files/bluefin/etc/bazaar/hooks.py:29-35). But the existing VS Code handler installs ublue/tap/vscodium-linux and ublue/tap/visual-studio-code-linux (hooks.py:98, 100) — a ublue/tap prefix, not ublue-os/tap. So for those callers the new tap/trust commands tap and trust a repo the cask doesn't come from, and the tap the install actually resolves against remains untapped/untrusted. If the point of adding brew trust is to avoid Homebrew's untrusted-tap prompt, the code-handler path still hits it. Either derive the tap from the cask's prefix, pass the matching tap explicitly at both call sites, or leave the old callers' behavior unchanged. (The Zed path itself is consistent: hooks.py:134-135.)
Low — test gaps for the Zed state machine. tests/test_hooks.py:238-283 covers setup/teardown-dialog(download)/teardown/action, but not setup-dialog ('ok'), catch ('abort'), or the teardown-dialog cancel path ('abort') at hooks.py:120-129. The equivalent stages are the ones that abort/deny the Flatpak transaction, so regressions there would go unnoticed.
Verified: all 25 tests in tests/test_hooks.py pass at head 5baf659; bazaar.yaml:61-77 zed hook structure and dialog/option ids match the handler; brew trust <tap> is the correct Homebrew 6 trust command (test's --trust not in cmd assertion is right).
— hive: agent=reviewer backend=copilot model=claude-fable-5 copilot=1.0.78
There was a problem hiding this comment.
Security review (automated, security perspective)
1. MEDIUM — Silent, persistent tap trust grant without user consent
system_files/bluefin/etc/bazaar/hooks.py:33 now runs {brew} trust {tap} 2>/dev/null || true before every redirected install. For the Zed flow (hooks.py:134-135) this grants blanket, persistent trust to ublue-os/experimental-tap — all present and future code in an explicitly experimental tap — with stderr suppressed, in a detached terminal, based on a dialog (bazaar.yaml:61-66) that only says "Install Zed from the Universal Blue Homebrew tap" and never mentions a trust grant. This bypasses Homebrew's interactive untrusted-tap consent prompt, which would otherwise run fine in the spawned terminal where the user is already present. The PR body ("Tap experimental Universal Blue repository before install") does not disclose the trust grant. Suggest dropping the brew trust line (let brew prompt), or at minimum disclosing it in the dialog and not suppressing stderr.
2. LOW — Default parameter silently changes the existing VS Code/Codium hook, and trusts the wrong tap
spawn_brew(app, tap='ublue-os/tap') (hooks.py:29) means the pre-existing code hook (hooks.py:98, hooks.py:100) now also runs brew tap ublue-os/tap + brew trust ublue-os/tap — an undisclosed side effect of this PR. Worse, those callers install from the ublue/tap/... namespace (hooks.py:98), so the tap being trusted doesn't even match the tap being installed from, with all errors hidden by 2>/dev/null || true. If tap/trust is needed, pass the matching tap explicitly per-caller rather than via a default.
No shell injection: BAZAAR_TS_APPID and other env inputs are only used in equality checks (hooks.py:111-112); the bash -c string interpolates hardcoded constants only.
— hive: agent=reviewer backend=copilot model=claude-fable-5 copilot=1.0.78
There was a problem hiding this comment.
Correctness review
-
High — the redirected brew install targets a cask that doesn't exist.
hooks.py:134-135runsbrew install --cask ublue-os/experimental-tap/zed-linux, butublue-os/homebrew-experimental-taphas nozed-linuxcask — onlyzed-linux@preview.rb(verified against the tap'sCasks/listing). The stablezed-linuxcask lives inublue-os/tap(ublue-os/homebrew-tap/Casks/zed-linux.rb). Since the hook also returnsdenyat teardown (hooks.py:141), the Flatpak install is blocked and the brew install then fails, leaving the user with no Zed at all. Fix:spawn_brew('ublue-os/tap/zed-linux')— the new default tap parameter (hooks.py:29) already taps/trustsublue-os/tap, and the dialog text atbazaar.yaml:68("the Universal Blue Homebrew tap") already describes that tap, not the experimental one. -
Low — new trust logic doesn't cover the code hook it shares
spawn_brewwith.hooks.py:98,100install fromublue/tap/..., but the default now taps/trustsublue-os/tap(hooks.py:29,33).github.com/ublue/homebrew-tapreturns 404, so those install specs reference a nonexistent tap and remain untrusted regardless. Pre-existing, but worth fixing while touchingspawn_brew. -
Info — tests encode the bug.
tests/test_hooks.pytest_action_spawns_brew_with_tap_and_trustasserts the experimental-tap command string, so the suite passes (25/25 locally) while the real install would fail. Assertions should track the corrected cask path.
— hive: agent=reviewer backend=copilot model=claude-fable-5 copilot=1.0.78
There was a problem hiding this comment.
Security review (automated, perspective: security)
1. Medium — silent whole-tap brew trust bypasses Homebrew 6 tap-trust consent
system_files/bluefin/etc/bazaar/hooks.py:33 runs brew trust {tap} 2>/dev/null || true non-interactively. Homebrew 6 introduced tap trust precisely so third-party taps can't execute code without explicit user consent; whole-tap trust covers all current and future formulae, casks, and external commands in the tap. The Zed path (hooks.py:134-135) grants this to ublue-os/experimental-tap, while the user-facing dialog (system_files/bluefin/etc/bazaar/bazaar.yaml:64-67) says only "Install Zed from the Universal Blue Homebrew tap for the supported Bluefin integration" — no mention of an experimental tap or of granting it code-execution trust. Suggest scoping trust to the one cask (brew trust --cask ublue-os/experimental-tap/zed-linux) and having the dialog state what will be trusted.
2. Low — trust side-effect silently added to existing VS Code/Codium flow
The new default parameter tap='ublue-os/tap' (hooks.py:29) means the pre-existing code handler (hooks.py:98-100) now also runs brew tap + brew trust ublue-os/tap on every install — a trust expansion outside this PR's stated Zed scope. It's also inconsistent: those calls install ublue/tap/... casks but tap/trust ublue-os/tap.
3. Info — 2>/dev/null || true on both tap and trust (hooks.py:33) hides failures, so a failed trust silently degrades to a failed install with no user-visible reason.
No injection risk found: spawn_brew arguments are hardcoded literals; Bazaar env vars are only compared, never interpolated into the shell string.
— hive: agent=reviewer backend=copilot model=claude-fable-5 copilot=1.0.78
There was a problem hiding this comment.
Intent-alignment review (Zed redirect hook)
-
Scope creep with a likely-wrong tap name —
system_files/bluefin/etc/bazaar/hooks.py:29-34:spawn_brewgains a defaulttap='ublue-os/tap', so every existing caller now runsbrew tap ublue-os/tap; brew trust ublue-os/tapbefore install. But the existing VS Code/VSCodium hook installs casks from a different tap —ublue/tap/vscodium-linux/ublue/tap/visual-studio-code-linux(hooks.py:98-100). The newly tapped/trustedublue-os/tapdoesn't cover those casks, so this change modifies unrelated hook behavior without actually trusting the tap those installs use. Either scope the tap/trust logic to the Zed path only, or fix the tap names consistently. -
Dialog text vs. actual source —
system_files/bluefin/etc/bazaar/bazaar.yaml:66-68tells the user Zed comes from "the Universal Blue Homebrew tap", but the action installs fromublue-os/experimental-tap(hooks.py:134-135). If the experimental tap is intentional (per the PR body), the dialog should say so — users are being redirected off a working Flatpak onto an experimental channel without knowing.
Otherwise the diff matches the stated goal (Bazaar hook for dev.zed.Zed, per projectbluefin/dakota#609) with no other unrelated changes.
— hive: agent=reviewer backend=copilot model=claude-fable-5 copilot=1.0.78
There was a problem hiding this comment.
Review (style/maintainability perspective)
-
spawn_brewchange silently alters the existing code/codium hooks —system_files/bluefin/etc/bazaar/hooks.py:29-34. The new tap/trust preamble runs for every caller, so the pre-existinghandle_code()path (hooks.py:99-102, installingublue/tap/vscodium-linux/ublue/tap/visual-studio-code-linux) now also executesbrew tap ublue-os/tap; brew trust ublue-os/tapvia the default parameter. Note the mismatch: the default tap isublue-os/tapwhile those casks are prefixedublue/tap/…, so the tapped repo doesn't correspond to the casks being installed. For a Zed-scoped fix, consider only emitting the tap/trust commands when a tap is explicitly passed (or make the existing callers pass their actual tap), so unrelated hooks' behavior is untouched. -
Errors from
brew tap/brew trustare fully suppressed —hooks.py:33(2>/dev/null || true). If tappingublue-os/experimental-tapfails, the follow-upbrew install --cask ublue-os/experimental-tap/zed-linuxfails with no diagnostic of why. Suppressing stderr on the tap step will make this hard to debug in the field;|| truealone is enough to keep the chain going. -
Minor:
tests/test_hooks.py:281asserts"--trust" not in cmd— pinning a negative implementation detail is brittle and will confuse future editors (it reads like a leftover from an alternativebrew tap --trustapproach). Safe to drop.
Perspective: style/maintainability. Verdict: changes_requested.
— hive: agent=reviewer backend=copilot model=claude-fable-5 copilot=1.0.78
There was a problem hiding this comment.
docs-currency review
One doc that must change with this behavior:
docs/skills/brew-lifecycle/references/placement-rules.md:83-88— the "Tap trust call sites" table enumerates everybrew tap+brew trustlocation, but this PR adds a new one:hooks.pyspawn_brew(system_files/bluefin/etc/bazaar/hooks.py:29-34), which now taps/trustsublue-os/tapby default andublue-os/experimental-tapfor the Zed path. The table currently lists onlybazaar-hook spawn_brew. Please add row(s) forhooks.py spawn_brewso the call-site inventory stays accurate.
Minor, informational: docs/skills/bazaar.md:47 describes usr/libexec/bazaar-hook as the "in-image hook script used by Bazaar runtime path", and its spawn_brew (system_files/bluefin/usr/libexec/bazaar-hook:30-36) got no Zed handler — fine since bazaar.yaml:59 wires the new zed hook to hooks.py, but worth confirming the two scripts are intentionally diverging.
PR body claims (tap before install, regression coverage) match the diff.
— hive: agent=reviewer backend=copilot model=claude-fable-5 copilot=1.0.78
There was a problem hiding this comment.
Correctness review
-
High — wrong cask name for the experimental tap; the redirect will fail after aborting the user's Flatpak install.
hooks.py:132-133(spawn_brew('ublue-os/experimental-tap/zed-linux', 'ublue-os/experimental-tap')):ublue-os/homebrew-experimental-tapcontains onlyCasks/zed-linux@preview.rb— there is nozed-linuxcask there. Plainzed-linuxexists in the main tap (ublue-os/homebrew-tap/Casks/zed-linux.rb). As written, the user cancels their Flatpak install, clicks "Download from Homebrew", andbrew install --caskfails with "no available cask", leaving them with nothing. Either installublue-os/tap/zed-linux(default tap arg already matches) orzed-linux@previewif preview is intended. The test attests/test_hooks.py:273-284asserts the broken cask path, so it locks in the bug rather than catching it. -
Medium — spawn_brew's new default tap doesn't match what the existing code path installs.
hooks.py:29-35now taps/trustsublue-os/tapby default, buthandle_code(hooks.py:99-102) still installsublue/tap/vscodium-linux/ublue/tap/visual-studio-code-linux.github.com/ublue/homebrew-tapis a 404, so the tap+trust ofublue-os/tapneither helps nor fixes that path — brew will still try (and fail) to auto-tap the nonexistentublue/tap. If the intent of adding tap/trust tospawn_brewwas to make these installs work under Homebrew 6 tap-trust, the code-path cask references need to move toublue-os/tap/...too.
Not verified: actual Bazaar dialog/stage sequencing at runtime (yaml hook wiring mirrors the existing code hook and looks consistent).
— hive: agent=reviewer backend=copilot model=claude-fable-5
There was a problem hiding this comment.
Correctness review
High — the redirect installs from the wrong tap, so the action fails. hooks.py:134-135 runs spawn_brew('ublue-os/experimental-tap/zed-linux', 'ublue-os/experimental-tap'), but the zed-linux cask lives in the main tap (ublue-os/homebrew-tap/Casks/zed-linux.rb), not in ublue-os/homebrew-experimental-tap (its Casks/ has no zed entry). The user's flatpak install is aborted, then brew install --cask ublue-os/experimental-tap/zed-linux errors in the spawned terminal — the user ends up with no Zed at all. Should be spawn_brew('ublue-os/tap/zed-linux') (the new default tap already matches). The dialog body also says "Universal Blue Homebrew tap" (bazaar.yaml:68), i.e. the main tap, contradicting the code. The tests (tests/test_hooks.py:280-281) assert the wrong tap name, so they pass while encoding the bug.
Low — new default tap doesn't match existing callers. spawn_brew now taps/trusts ublue-os/tap by default (hooks.py:29), but the VS Code/Codium paths install from ublue/tap/... (hooks.py:98,100), a tap that 404s on GitHub. The added trust step therefore doesn't cover the tap those installs actually reference (pre-existing breakage, but this PR touched spawn_brew and the mismatch is now baked into the default).
Not verified: actual Bazaar hook stage sequencing at runtime; review is static + tap-content checks only.
— hive: agent=reviewer backend=copilot model=claude-fable-5 copilot=1.0.78
There was a problem hiding this comment.
Security review — 2 findings
-
Medium — trust widened for unrelated hooks via default arg.
spawn_brewnow runsbrew tap <tap>andbrew trust <tap>for every caller (system_files/bluefin/etc/bazaar/hooks.py:29-33). The defaulttap='ublue-os/tap'means the pre-existing JetBrains and VS Code hooks — which previously ran onlybrew install --cask— now also persistently markublue-os/tapas trusted as a silent side effect, beyond this PR's stated scope of redirecting Zed. If tap/trust is only needed for the Zed path, scope it there instead of changing behavior for allspawn_brewcallers. -
Low — persistent trust of an experimental tap, with errors suppressed. The Zed action trusts
ublue-os/experimental-tap(hooks.py:130-131) viabrew trust ... 2>/dev/null || true(hooks.py:33).brew trustis a persistent, system-wide grant covering all future formulae from that tap, not justzed-linux, and the2>/dev/null || trueplus the bareexcept Exception: pass(hooks.py:132-133) mean any failure or unexpected outcome is invisible to the user. Consider whether a one-click Bazaar dialog should silently confer standing trust in an experimental tap.
No injection: the interpolated app/tap values in the bash -c string (hooks.py:33) are hardcoded constants, not user- or env-derived.
— hive: agent=reviewer backend=copilot model=claude-fable-5 copilot=1.0.78
*⚠️ change: make the review perspective cap a per-PR budget Each perspective the review swarm dispatches becomes its own review comment. The cap that was supposed to limit that only applied within a single dispatch cycle: perspectives it skipped stayed "missing" and were dispatched on the next cycle instead. A capped hive therefore still worked through all five perspectives, one per cadence interval, and the maintainer still received five separate comments -- just spread over an hour rather than arriving together. Observed on a bluefin spoke: projectbluefin/common#1011 collected six reviews in seventy-seven minutes, and fsdk-containers#225 was reviewed at 04:30 and again at 04:45 with no restart in between. Count the perspectives a head SHA has already been given, so the cap is a lifetime budget for that commit. "Max perspectives per PR" now means what it says, and it is the control over how much review traffic one pull request attracts. This trades coverage for restraint, so it stays opt-in: the default is still no cap, and a hive that wants every perspective on every PR keeps getting it. A force-push clears the pending entries for the old head SHA, so genuinely new code earns a fresh budget. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Andrew Anderson <andy@clubanderson.com> * 🌱 chore: match changelog fragment to PR number Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Andrew Anderson <andy@clubanderson.com> --------- Signed-off-by: Andrew Anderson <andy@clubanderson.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: scanner[bot] <scanner[bot]@users.noreply.github.com>
Replace invalid 'brew tap --trust' syntax with separate 'brew tap' and 'brew trust' calls matching Homebrew 6.0 semantics and the repository standard. Narrow bare except to except Exception and add test assertion verifying brew command string.
5baf659 to
786ad5c
Compare
- Update hooks.py and bazaar-hook to install ublue-os/tap/zed-linux from ublue-os/tap - Align code hook callers to use matching ublue-os/tap - Add full stage tests for Zed hook in test_hooks.py and test_bazaar_hook.py - Document Zed hook and dual hook maintenance rule in docs/skills/bazaar.md Assisted-by: Gemini via GitHub Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
786ad5c to
d68baa5
Compare
Danathar
left a comment
There was a problem hiding this comment.
Approving as the second reviewer. What this does: when someone tries to install Zed from Flatpak in Bazaar, it offers the supported Homebrew cask instead, the same way it already does for VS Code and JetBrains.
What I checked:
- The earlier review objections are addressed: it now uses the Homebrew 6 two-step form (
brew tapthenbrew trust), not the invalidbrew tap --trust; and it installsublue-os/tap/zed-linux, which I confirmed exists in ublue-os/homebrew-tap (the earlier draft pointed at a cask that didn't). - It also fixes an existing bug:
maincurrently callsspawn_brew('ublue/tap/vscodium-linux')—ublue/tapis the wrong tap name. This PR corrects both VS Code entries toublue-os/tap. - Both entry points (
hooks.pyandlibexec/bazaar-hook) carry the same handler, and the docs now say they must stay in sync. - On the
brew trustconcern raised in review: the user has explicitly clicked "Download from Homebrew" in the dialog, andublue-os/tapis the image's own vendor tap, already marked trusted in the shipped Brewfiles. That's consent, not a silent grant. - CI is green including the composed E2E suite.
Fix
Add Bazaar hook for dev.zed.Zed so users can choose supported Zed Homebrew cask installation instead of an unsupported Flatpak. Tap and trust Universal Blue repository (
ublue-os/tap) before install, synchronize bothhooks.pyandbazaar-hookentrypoints, and add state-machine regression coverage.Refs projectbluefin/dakota#609
Filed by scanner agent (ACMM L5 — hold-gated mode). Hold-gated: human review required.
— hive: agent=scanner backend=copilot model=gpt-5.6-luna