Onboarding: ACP adapter install fails silently when a broken Node shadows Buzz's managed Node in the login shell
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
## Summary
During desktop onboarding ("Set up your agent harnesses"), installing the ACP
adapter for **Claude Code** and **Codex** both fail with a generic
"Installation failed" and **no diagnosable output anywhere** (UI or logs).
Root cause turned out to be a **broken Homebrew Node** on the machine that
`install_acp_runtime` invoked *instead of* Buzz's own managed Node, because the
adapter install runs through a **non-interactive login shell** (`zsh -l -c`).
Two separate robustness gaps combined to make this happen and to make it very
hard to diagnose.
## Environment
- macOS 15.7.3, Apple Silicon (arm64)
- Buzz desktop built from source (`dev` profile)
- Node situation on the machine (deliberately messy, but realistic):
- `nvm` node `v24.15.0` (initialized in `~/.zshrc`, i.e. interactive only)
- Homebrew node `v25.8.1` — **broken** (linked against
`/opt/homebrew/opt/simdjson/lib/libsimdjson.31.dylib`, but installed
`simdjson` is `4.6.4` which only ships `libsimdjson.33.dylib`)
- `/usr/local/bin/node` `v25.8.1` (official pkg)
## What happens
Both harness cards show `CLI detected; ACP adapter missing` → clicking
**INSTALL** → `Installation failed`. The managed Node runtime itself
(`~/Library/Application Support/Buzz/runtimes/node/v24.11.0/...`) is present and
healthy; the failure is in the `npm install -g @agentclientprotocol/...` step.
## Root cause
`install_shell_command` runs install commands via a **login shell**:
```rust
// desktop/src-tauri/src/commands/agent_discovery.rs
let mut cmd = std::process::Command::new(&shell);
cmd.args(["-l", "-c", command]);
// ...
// PATH is built with the managed node bin prepended, then the login-shell PATH:
let mut path_parts = Vec::new();
if let Some(managed_node_bin) = crate::managed_agents::buzz_managed_node_bin_dir() {
path_parts.push(managed_node_bin);
}
// ...
if let Some(ref path) = crate::managed_agents::login_shell_path() {
path_parts.extend(std::env::split_paths(path));
}
```
The problem: `zsh -l -c` sources `~/.zprofile` (which runs
`eval "$(/opt/homebrew/bin/brew shellenv)"`, **re-prepending** `/opt/homebrew/bin`
to PATH), but does **not** source `~/.zshrc` (where `nvm` is initialized). So
inside the install shell:
- The managed-node bin that Buzz prepended via `cmd.env("PATH", ...)` is pushed
behind `/opt/homebrew/bin` again by `brew shellenv`.
- `nvm`'s node is absent (only set up for interactive shells).
- `npm`/`node` therefore resolve to the **broken Homebrew node**, which crashes
on launch:
```
dyld[]: Library not loaded: /opt/homebrew/opt/simdjson/lib/libsimdjson.31.dylib
Referenced from: /opt/homebrew/Cellar/node/25.8.1_1/bin/node
Reason: tried: '.../libsimdjson.31.dylib' (no such file), ...
```
→ npm can't run → adapter install fails for **every** harness.
### Reproduction (exact command Buzz runs)
```bash
BASE="$HOME/Library/Application Support/Buzz"
NODEBIN="$BASE/runtimes/node/v24.11.0/darwin-arm64/bin"
PREFIX="$BASE/node-tools"
PATH="$NODEBIN:$PATH" zsh -l -c \
"npm install --global --prefix '$PREFIX' @agentclientprotocol/claude-agent-acp"
# dyld: Library not loaded: .../libsimdjson.31.dylib (broken homebrew node wins)
```
Running the same install **without** the login shell (managed node first on
PATH) succeeds, confirming the diagnosis.
## Two robustness gaps
1. **Managed Node is not actually guaranteed to win.** Even though the code
prepends `buzz_managed_node_bin_dir()`, running under `zsh -l -c` lets the
user's profile (`brew shellenv`, and potentially others) re-order PATH so an
unrelated/broken Node shadows the managed one. The install is meant to be
hermetic against the managed runtime but isn't.
2. **The real error is swallowed.** `run_install_command_with_retry` captures
stderr into `InstallStepResult` but never logs it (no `eprintln!`/tracing),
and the onboarding UI shows a fixed message
(`Installation failed` / "Installation couldn't be completed. Try again.")
rather than the step's stderr:
```tsx
// desktop/src/features/onboarding/ui/SetupStep.tsx (RuntimeCard)
```
There is no way for a user (or maintainer) to see the underlying dyld/npm
error without manually reconstructing and re-running the command.
## Suggested fixes
- **Make adapter installs hermetic against the managed runtime.** Invoke the
managed `node`/`npm` by **absolute path** (from `buzz_managed_node_bin_dir()` /
`buzz_managed_npm_bin_dir()`) instead of relying on PATH resolution inside a
login shell — or run install commands with a **non-login** shell using Buzz's
constructed PATH so the user's profile can't re-prepend a different Node.
- **Surface/log the real failure.** `eprintln!` (or tracing) the failing step's
`command` + `stderr` on failure, and/or show the step stderr in the
onboarding error tooltip (or a "copy details" affordance). This one line would
have turned a multi-hour investigation into a glance.
## Workaround (for anyone hitting this)
Fix or remove the broken Node so the login shell resolves a working one:
```bash
brew reinstall node # relink against current simdjson
# or, if you only use nvm:
brew uninstall --ignore-dependencies node
```
Or install the adapters directly with Buzz's managed node so discovery marks
them Ready:
```bash
BASE="$HOME/Library/Application Support/Buzz"
PATH="$BASE/runtimes/node/v24.11.0/darwin-arm64/bin:$PATH" \
npm install --global --prefix "$BASE/node-tools" \
@agentclientprotocol/claude-agent-acp @agentclientprotocol/codex-acp
```
Contributor guide
Research direction
Start with install_shell_command and run_install_command_with_retry in desktop/src-tauri/src/commands/agent_discovery.rs, then inspect RuntimeCard in desktop/src/features/onboarding/ui/SetupStep.tsx. Trace how the managed Node and install-step stderr are handled; done means adapter installation cannot be shadowed by the login shell and the underlying failure details are available to users or maintainers.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, rust, typescript, zsh
- Domain
- desktop, developer-experience, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100