6.5 KiB
Row 68 (#1561): the bus host runs an explicit Claude Code binary, round 1
Sage, 2026-10-11. Brief:
docs/plans/2026-10-11_bus-claude-binary.md, "Bus host runs an explicit
Claude Code binary" (e1b90ed6). Mos's Q18 ask: the bus finds
/usr/bin/claude 2.1.259 (pacman claude-code), which refuses
claude-opus-5-5. Jason runs the native ~/.local/bin/claude (2.1.296).
Filbert and Dewey review. Sage commits after the gate; nothing is
committed, staged or pushed yet.
Base a86de1f1. build.patch applies there with git apply --index;
candidate-manifest.sha256 pins all 13 files after the patch.
Design
The binary is named, not found. MOSAIC_CLAUDE_BIN is an absolute path:
- The unit sets it.
scripts/bus-service.sh render|install --claude PATH; without--claude,command -v claudein the installing shell. The path is kept as given, not resolved:~/.local/bin/claudeis a symlink the native installer moves to each new version, so the unit follows updates without a reinstall. ENV_ALLOWpasses it to the session. It's a path, not a credential.adapters/claude/adapter.shruns$MOSAIC_CLAUDE_BINwhen it is set and nonempty, elseclaudeas before.claudeVersion, which fills the manifest'sclaudeVersion, asks the same binary, so the manifest records what actually ran.
Rejected: prepending ~/.local/bin to the unit's PATH. Every binary
there (pip and npm installs, personal scripts) would then shadow the
system's, for the host and for every session. Also rejected: a field in
the business file or system config. Which binary runs is a property of the
host install, like node's directory, which the unit already carries.
Refusals
| Where | Input | Result |
|---|---|---|
bus-service.sh |
--claude relative |
exit 1, "must be absolute", nothing written |
bus-service.sh |
not an executable file, or missing | exit 1, "not an executable file" |
bus-service.sh |
a character outside A-Za-z0-9/._+- (%, space, ...) |
exit 1, "cannot carry". systemd expands % and splits Environment= on spaces. |
bus-service.sh |
no --claude and no claude on PATH |
exit 0; the unit gets # MOSAIC_CLAUDE_BIN unset: ... and stderr says so. A pi-only host installs as before. |
bus-service.sh |
uninstall --claude, render --dir |
exit 4 |
| adapter | MOSAIC_CLAUDE_BIN relative |
exit 2 before claude runs |
| adapter | not an executable file, or missing | exit 2 before claude runs |
| adapter | unset or empty | claude from PATH, as today |
install now renders into a variable before mktemp, so a refused pin
leaves no temp file in the unit directory. Calling render inside
|| { ... } would have switched set -e off in the function and written
a unit with an empty line. claude_line failing is handled with an
explicit || exit 1.
install prints claude: <path> (<--version>) or the unset line.
status adds -p Environment to its systemctl show, which shows PATH
and the pin. The unit holds no secret.
Files
packages/cli/systemd/mosaic-bus.service.in: a comment and an@CLAUDE_ENV@line afterEnvironment=PATH=. The header names "the claude line below" rather than the token, because header tokens render into the comment (as@REPO@and@PATH@already do) and the claude substitution only matches its own line.scripts/bus-service.sh:claude_line,--claudefor render and install, the install print,statusshowsEnvironment, usage.adapters/claude/adapter.sh: theCLAUDEvariable and its checks.packages/seat/src/session.mjs:MOSAIC_CLAUDE_BINinENV_ALLOW.packages/cli/src/launcher.mjs:claudeVersionasksenv.MOSAIC_CLAUDE_BIN || "claude".- Docs:
packages/cli/README.md("Unit" and the manifest note),adapters/README.md(adapter list and variable table),packages/seat/README.md(sessionEnv),docs/TOOLS.md(usage line and one paragraph). The brief didn't listdocs/TOOLS.md. I added it because its usage line would otherwise be wrong, and it is my file. - Tests:
host.test.mjs, new test "bus-service.sh pins the claude binary...":- the pin found on
PATH, from--claude, and through a symlink kept unresolved - five refusals with exit 1 and empty stdout
- install printing the pin and the stand-in's version
- a refused install writing nothing
uninstall --claudeandrender --direxiting 4- no claude anywhere:
PATHis a directory holding only node, dirname and sed, and the unit gets the comment line
- the pin found on
- The existing render test also asserts no
@CLAUDE_ENV@. launcher.test.mjs:claudeVersionwith the pin, fromPATH, and with none (null).session.test.mjs:sessionEnvpassesMOSAIC_CLAUDE_BIN.claude-session.test.mjs, new test:- the pin wins over a
claudeonPATH - unset or empty uses
PATH - relative, missing and non-executable pins exit 2 without running anything
- the pin wins over a
Each new test fails against base: unpinned claudeVersion returns the
PATH stand-in's 1.0.0, the base adapter runs the PATH stand-in, base
sessionEnv drops the variable, and base render rejects --claude with
exit 4.
Gate (my run on the candidate)
| Suite | Result |
|---|---|
| packages/cli | 85/0 |
| packages/seat | 27/0 |
| packages/harness | 57/0, nothing skipped. The live claude tests ran against the 2.1.296 on my PATH. |
| scripts/test-*.sh | all nine exit 0: auth 15, conductor 17, config 24, discord 66, extension-package 18, foundation 44, queue 27, release 14, task 98 (Docker on) |
Logs: /tmp/sage-r68-out/ (not kept).
After landing (not in the candidate)
scripts/bus-service.sh installfrom my shell, which resolves/home/jwoltje/.local/bin/claude.installprints the pin and its version.- The opus probe: the adapter's flags with
MOSAIC_CLAUDE_BINandPATHtaken from the installed unit, as decision 89's probe did. - Report the path and version to Mos.
The running host (MainPID 1242258) keeps its old environment until it
restarts. That restart is the Q18 --pm start, which waits for Mos's
watched window. Nothing here restarts it.
Questions for reviewers
- Is keeping the symlink, not its target, right? The other choice pins an exact version and needs a reinstall per update. I chose to follow updates because that's what Jason's own sessions do.
- With no claude found,
installsucceeds with a comment rather than refusing, because a pi-only business needs no claude. Is that the right default, or shouldinstallrefuse unless the business's roles are all pi?