fix(launchd): render every interpreter from the keg, and stop copying the hotlane daemon - #757
Conversation
… the hotlane daemon The M4 runs eight launchd jobs out of `~/Gits/brainlayer/.venv` or the framework Python; the M1 runs a hotlane daemon dated Jul 24 while its keg says 1.5.12. Same root cause, two shapes: `install.sh` resolved what a plist EXECUTES from the authoring environment instead of from the keg. **Interpreters.** `PYTHON_BIN` defaulted to `command -v python3` and `BRAINLAYER_BIN` to `which brainlayer`. Both answer with whatever the installing shell's PATH happens to front -- on the M4 that is `/Library/Frameworks/Python.framework/.../python3` and, when installing from the checkout, `~/Gits/brainlayer/.venv/bin/brainlayer`. Neither is a path any release can move, and no `__build_sha__` describes what they run. In a keg both now default to the keg's own interpreter through the stable `opt/` symlink. An explicit `PYTHON_BIN`/`BRAINLAYER_BIN`/`BRAINLAYER_PYTHON` still wins, so `brainlayer setup` and every existing test are unaffected. **Hotlane's script path.** `install_hotlane_brainbar_daemon()` copied the daemon into `~/.local/lib/brainlayer/` and pointed the plist at the copy. `brew install` never runs that refresh, so the copy is the thing that went stale on the M1. The daemon already ships inside the keg (pyproject force-includes it into `brainlayer/launchd/`), so in a keg the plist now names the packaged file directly and `brew upgrade` moves it for free -- no intermediate copy to outrun. Outside a keg the copy survives unchanged, because in a checkout the checkout is the truth; `test_launchd_installer_still_copies_the_hotlane_daemon_outside_a_keg` pins that. `opt/`, never `Cellar/<version>/`: a versioned path pins a job to a keg the next upgrade deletes. That is what `stable_brainlayer_path` already exists for, so this reuses it rather than inventing a second convention (#749). The one place the raw versioned root is kept is the release-signature gate, which verifies the exact keg it was handed -- a one-shot check where naming the version is right. Scope, so the next reader does not re-litigate it: `WorkingDirectory` under a checkout is NOT fixed here. It does not decide which code runs. Tests. `test_launchd_installer_renders_every_interpreter_from_the_keg_not_the_authoring_shell` renders every service in a keg fixture whose PATH fronts checkout-`.venv` decoys, and asserts no `ProgramArguments` entry names the checkout, the framework Python, or a versioned Cellar path. It fails on HEAD and passes here; so does the hotlane pin test. The reason no existing keg test caught this is that all of them pass `PYTHON_BIN`/`BRAINLAYER_BIN` explicitly -- the defect lives in what `install.sh` picks when they are UNSET, which is how `brew install` runs it. Known limit, not fixed here: `brew upgrade` still does not RE-RENDER existing plists, so the M4's eight stale jobs need one `install.sh` run from the keg. After that they follow versions on their own, because the paths are `opt/`-stable. Co-Authored-By: brainlayerClaude running claude-opus-5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_72162c75-9f2a-44d8-b819-ca2d0e81e9c3) |
BrainLayer ratchetEvery Value below was measured by this run. A row this machine cannot measure says
🟢 GREEN measured, within budget · 🔴 RED measured, out of budget — a finding to clear before merge · ⚪ n/a not measurable on this machine, never guessed. No RED rows. Measured on Linux/x86_64 · checked-out HEAD |
|
|
Overall Grade |
Security Reliability Complexity Hygiene |
Code Review Summary
| Analyzer | Status | Updated (UTC) | Details |
|---|---|---|---|
| Python | Sep 4, 2026 11:16a.m. | Review ↗ | |
| Swift | Sep 4, 2026 11:16a.m. | Review ↗ | |
| JavaScript | Sep 4, 2026 11:16a.m. | Review ↗ | |
| Shell | Sep 4, 2026 11:16a.m. | Review ↗ | |
| Secrets | Sep 4, 2026 11:16a.m. | Review ↗ |
Important
AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.
|
Warning Review limit reachedNext included review available in 45 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Team Run ID: 📒 Files selected for processing (2)
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Team Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📜 Recent review details⏰ Context from checks skipped due to timeout. (4)
🧰 Additional context used🪛 ast-grep (0.45.2)tests/test_installable_build.py[error] 3341-3348: Command coming from incoming request (subprocess-from-request) [error] 3450-3470: Command coming from incoming request (subprocess-from-request) 🔇 Additional comments (2)
📝 WalkthroughWalkthroughThe launchd installer now detects Homebrew kegs, prefers keg-local Python and CLI paths, and pins the hotlane daemon to the packaged keg file. Tests cover keg installations with checkout decoys and preserve copying behavior for non-keg installations. ChangesHomebrew keg-aware launchd installation
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to Launchd services installed from a Homebrew keg now use stable keg paths for executables and the hotlane daemon, while checkout installations retain their copy behavior. The covered installation paths are ready to merge. Sequence Diagram(s)sequenceDiagram
participant Installer
participant HomebrewKeg
participant LaunchdPlist
participant UserLibrary
Installer->>HomebrewKeg: Detect keg and resolve Python and CLI
HomebrewKeg-->>Installer: Return keg-local paths
Installer->>LaunchdPlist: Render launchd jobs with keg paths
Installer->>HomebrewKeg: Locate packaged hotlane daemon
Installer->>LaunchdPlist: Reference packaged daemon
Installer-->>UserLibrary: Skip copy for keg installation
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| exit 1 | ||
| fi | ||
| "$BRAINLAYER_RELEASE_VERIFY" "$BRAINLAYER_KEG" | ||
| "$BRAINLAYER_RELEASE_VERIFY" "$BRAINLAYER_KEG_RAW" |
There was a problem hiding this comment.
🟡 Medium launchd/install.sh:198
The release-signature tests at tests/test_release_verify_signatures.py:171 and :193 still assert the old "$BRAINLAYER_RELEASE_VERIFY" "$BRAINLAYER_KEG" invocation, so the test suite fails against this script. Update those guards to assert the new BRAINLAYER_KEG_RAW contract.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @scripts/launchd/install.sh around line 198:
The release-signature tests at `tests/test_release_verify_signatures.py:171` and `:193` still assert the old `"$BRAINLAYER_RELEASE_VERIFY" "$BRAINLAYER_KEG"` invocation, so the test suite fails against this script. Update those guards to assert the new `BRAINLAYER_KEG_RAW` contract.
…losed on a keg with no interpreter Round 2 on #757. Three findings from the Cursor pass, all three real; verified each in the tree before acting. **CRITICAL — `install.sh remove` unlinked a Homebrew-owned file.** Keg-pinning changed what `HOTLANE_BRAINBAR_DST` MEANS -- from a copy this installer made into `~/.local/lib` to the keg's own shipped file -- while the remove path still ran `rm -f` on it. On a writable Homebrew prefix that deletes packaged content, and the next keg-pinned install then fails closed on its own missing-daemon guard until someone reinstalls the formula. `remove` now names the `~/.local/lib` copy literally, so a future change to what that variable means cannot silently make this destructive again. Every other `rm` in that block was already `$BRAINLAYER_LIB_DIR`-rooted; mine was the only one that had become keg-rooted. The lesson generalises and is worth writing down: **when you change what a path MEANS, re-read every site that ACTS on it.** `HOTLANE_BRAINBAR_DST` has 8 hits; I had checked the ones I edited, not the ones that merely used it. **MEDIUM — a detected keg with a missing interpreter fell through to PATH.** That is the same fail-open this whole change exists to close, reintroduced as a fallback inside the fix for it: a keg is present, so PATH's answer (framework python, a checkout `.venv`) is wrong by definition. With a keg detected and no explicit override, a missing keg interpreter is now an error, not a fallback. An explicit `PYTHON_BIN`/`BRAINLAYER_BIN` is still honoured. **Naming.** `BRAINLAYER_KEG` is again the keg exactly as found -- what the release-signature gate verifies, where naming the version is the precise thing to do -- and the `opt/`-normalised form is `BRAINLAYER_KEG_STABLE`, used for anything that may reach a plist. My round-1 rename broke `test_source_checkout_install_sh_skips_gate_without_keg`, which asserts on install.sh source text; it failed on CI `test (3.12)` and my scoped pre-push did not cover that file. Restoring the name leaves both existing guards untouched and still true, which is better evidence than editing them to match my code. Tests: `..._remove_never_unlinks_the_packaged_daemon_out_of_the_keg` (install, remove, assert the packaged daemon survives, then assert the keg is still installable -- the real failure mode is not a missing file but every later install failing closed) and `..._refuses_path_fallback_when_a_keg_has_no_usable_interpreter` (both missing interpreters, asserting the error and that nothing is rendered from a PATH guess first). **Both verified to FAIL on 47d4e82 and pass here**, restored-tree and re-run, not assumed. 226 passed across test_release_verify_signatures, test_installable_build, test_launchd_hygiene, test_fleet_watchdog, test_cli_launchd_mode_a, test_backup_daily. `ruff check` and `ruff format --check` clean. Co-Authored-By: brainlayerClaude running claude-opus-5 <noreply@anthropic.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_06c28af6-4dcc-424e-92b7-e20c50ec2b03) |
Round 2 — three findings, all three real, plus a regression of mine that CI caughtI verified each in my own tree before acting rather than deferring to the review. 🔴 CRITICAL — confirmed, and it was mineKeg-pinning changed what Every other The generalisable lesson, since this is the third such case this week: when you change what a path means, re-read every site that acts on it. MEDIUM — confirmedA detected keg with a missing HIGH — the tests now catch both
Both verified to FAIL on 47d4e82 and pass on 669fc5f — restored tree, re-ran, restored. Not assumed. A regression I caused, and the gate that missed itMy round-1 rename of Fixed by restoring Worth flagging for the lane: Numbers226 passed across On the deploy, since it follows from a finding in this PR
— brainlayerClaude (worker) · claude-code/claude-opus-5 |
All six sites: pyproject.toml, src/brainlayer/__init__.py, server.json (root + packages[0]), brain-bar/bundle/Info.plist (short, bundle, release). Casks/brainbar.rb stays 1.5.9: no Swift change this release, declared to scripts/brainlayer-version-check.sh via BRAINLAYER_VERSION_CHECK_CASK_LAG_REASON="no BrainBar release for 1.5.13". Why a release. #757 (64bd6e9) changed `scripts/launchd/**`, which pyproject force-includes into the wheel as `brainlayer/launchd/`. That content is exactly what the keg ships and exactly what the fix has to reach: the renderer that decides which interpreter every com.brainlayer.* plist executes, and the hotlane daemon the plist now names inside the keg instead of copying out of it. Reusing 1.5.12 would put new packaged content under an identical version number — the false-green this sprint exists to close. The running binary must answer a `__build_sha__` descending from 64bd6e9. What this release does NOT do, stated because the deploy depends on it. Shipping 1.5.13 fixes the RENDERER, not the machines. `brew upgrade` does not re-render existing plists — measured: the M4's eight stale jobs are dated Jun 26 x4, Jul 1, Aug 19 and Sep 2, and 1.5.12 moved none of them. Each Mac therefore needs a second step after the keg upgrade: re-render the affected labels from the keg, so the new keg-resolving renderer actually rewrites them. Co-authored-by: brainlayerClaude running claude-opus-5 <noreply@anthropic.com>
Size: S
What was wrong
Two shapes, one root cause:
install.shresolved what a plist EXECUTES from the authoring environment instead of from the keg.Measured on the two Macs, via
plistlibonProgramArguments(not a grep, see Scope below):M4 — 8 of 20
com.brainlayer.*jobs execute the source checkout or the framework Python:decay,health-check,p0-counter,repair-fts~/Gits/brainlayer/.venv/bin/brainlayermaintenance-nightly,maintenance-weekly~/Gits/brainlayer/.venv/bin/pythonthroughput-watchdog/Library/Frameworks/Python.framework/.../python3hotlane-brainbar~/Gits/brainlayer/scripts/hotlane_brainbar_daemon.pyNo release can move any of those, and no
__build_sha__describes what they run.M1 — clean on all eight, and stale in a different way. Its hotlane plist correctly names the keg's venv Python, but its script path is
~/.local/lib/brainlayer/hotlane_brainbar_daemon.py, dated Jul 24 and byte-different from the keg's 1.5.12 copy — proven withdiff, not inferred. The plist on disk cannot reveal that.The fix
Interpreters (all renderers).
PYTHON_BINdefaulted tocommand -v python3,BRAINLAYER_BINtowhich brainlayer— both answer with whatever the installing shell's PATH fronts. In a keg they now default to the keg's own interpreter, through the stableopt/symlink. An explicitPYTHON_BIN/BRAINLAYER_BIN/BRAINLAYER_PYTHONstill wins, sobrainlayer setupand every existing test are unaffected.Hotlane's script path.
install_hotlane_brainbar_daemon()copied the daemon to~/.local/lib/brainlayer/and pointed the plist at the copy.brew installnever runs that refresh — the copy IS the M1's staleness. The daemon already ships inside the keg (pyproject.toml:139force-includes it intobrainlayer/launchd/), so in a keg the plist now names the packaged file directly andbrew upgrademoves it for free. No intermediate copy remains to outrun. Outside a keg the copy survives unchanged, because in a checkout the checkout is the truth.opt/, neverCellar/<version>/: a versioned path pins a job to a keg the next upgrade deletes. This reuses the existingstable_brainlayer_path(#749) rather than inventing a second convention. The one place the raw versioned root is deliberately kept is the release-signature gate, which verifies the exact keg it was handed.Scope — stated so it is not re-litigated
WorkingDirectoryunder a checkout is NOT fixed here, on purpose. It does not decide which code runs. A grep that counts bothWorkingDirectoryandProgramArgumentsgives 12 dirty plists on the M4; the number that matters is 8.Also not fixed here (named follow-up). Six other files are copied into
~/.local/lib/brainlayer/and refreshed only byinstall.sh, never bybrew upgrade— the same staleness class as the hotlane copy:brainlayer-env-run.sh(arg0 of nearly every plist),throughput-watchdog.py,tier0-watchdog.sh,fleet-watchdog.sh, and the two templated ones,backup-daily.shandjsonl-backup.sh. The templated pair cannot be keg-pinned without a substitution redesign. Flagged for the lead to split with orc rather than sprawling this PR.Tests
test_launchd_installer_renders_every_interpreter_from_the_keg_not_the_authoring_shellrenders every service (install.sh all) inside a keg fixture whose PATH deliberately fronts checkout-.venvdecoys, then asserts noProgramArgumentsentry names the checkout, the framework Python, or a versioned Cellar path — plus a positive half, so it cannot pass by rendering nothing.test_launchd_installer_pins_hotlane_to_the_keg_copy_with_no_intermediate_copyasserts the packaged daemon path and that~/.local/lib/.../hotlane_brainbar_daemon.pyis not written at all in a keg.test_launchd_installer_still_copies_the_hotlane_daemon_outside_a_kegpins the dev path so keg-pinning does not take it away.Both new keg tests fail on HEAD and pass here — verified by restoring
git show HEAD:scripts/launchd/install.shand re-running, not assumed. The third passes on both, as a regression guard should.Why no existing keg test caught this: every one of them passes
PYTHON_BIN/BRAINLAYER_BINexplicitly. The defect lives in whatinstall.shpicks when they are unset — which is exactly howbrew installruns it.tests/test_installable_build.py95 passed;test_launchd_hygiene,test_fleet_watchdog,test_cli_launchd_mode_a,test_backup_daily,test_enrichment_controller213 passed.ruff checkandruff format --checkclean. Full scoped pre-push gate passed on push.Known limit — this fix alone does not clean the M4
brew upgradedoes not re-render existing plists. The M4's eight stale jobs need oneinstall.shrun from the keg (per-service, notall—com.brainlayer.watchstays=> disabledand R3 owns it). After that they follow versions on their own, because the paths areopt/-stable. That deploy ships with 1.5.13 and is proved per-Mac byps/lsofon the live pid, never by the plist on disk.🤖 Generated with Claude Code
Note
Medium Risk
Changes which binaries launchd jobs execute on installed Macs; behavior is fail-closed and heavily tested, but mis-detection could block installs or leave stale plists until re-run.
Overview
Fixes launchd plists that were baked with whatever Python/
brainlayerthe installing shell’s PATH returned (checkout.venv, framework Python) instead of the Homebrew keg, so upgrades could not move those jobs.install.shnow discovers the keg before defaultingPYTHON_BIN/BRAINLAYER_BIN, resolves them through the stableopt/brainlayersymlink when overrides are unset, and errors if a keg is detected but its interpreter/CLI is missing (no silent PATH fallback). For hotlane on a keg, the plist points at the packagedhotlane_brainbar_daemon.pyin the wheel—no copy to~/.local/lib—andremoveonly deletes that local copy, not Homebrew-owned files.Tests add a keg fixture with checkout PATH decoys and assert all
ProgramArgumentsuse the keg, hotlane is keg-pinned, dev checkout still copies locally, and remove/reinstall stay safe.Reviewed by Cursor Bugbot for commit 669fc5f. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Fix launchd installer to render interpreters from keg and stop copying hotlane daemon
brainlayerCLI from inside the keg, preferring them over PATH results. It exits with an error if a keg is present but either binary is missing, unless an explicit override is set.Macroscope summarized 669fc5f.
Summary by CodeRabbit