blue/.blue/docs/rfcs/0060-reliable-binary-installation.accepted.md
Eric Garcia c745d11870 feat: RFC 0060 reliable binary installation
- install.sh: Add xattr/codesign fix after cp on macOS
- install.sh: Add portable timeout verification using perl
- INSTALL.md: Recommend cargo install --path as primary method
- INSTALL.md: Document macOS signature issue and fix
- blue doctor: Detect com.apple.provenance xattr with fix hint
- blue doctor: Verify code signature with codesign --verify
- blue doctor: 3-second liveness timeout for hanging binaries

Fixes dyld hang at _dyld_start when copied binaries have stale
signatures after Homebrew library updates.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 21:01:19 -05:00

135 lines
4.7 KiB
Markdown

# RFC 0060: Reliable Binary Installation
| | |
|---|---|
| **Status** | Accepted |
| **Date** | 2026-02-06 |
| **Relates To** | RFC 0052 (CLI Hook Management), RFC 0049 (Synchronous Guard) |
---
## Summary
Fix the binary installation flow so it doesn't produce binaries that hang on macOS. Add `cargo install --path` support as the primary install method, and post-copy re-signing as a fallback safety net.
## Problem Statement
### The Bug
When the Blue binary is copied to `~/.cargo/bin/` (or `/usr/local/bin/`), macOS preserves stale extended attributes (`com.apple.provenance`) and the adhoc code signature from the original build. If Homebrew updates a dynamically linked library (e.g., `openssl@3`) between when the binary was built and when it's run, `dyld` hangs indefinitely at `_dyld_start` during signature verification. The process never reaches `main()`.
### Observed Behavior
```
$ blue init
[hangs forever — no output, 0 bytes written to stdout/stderr]
$ sample $PID
890 _dyld_start (in dyld) + 0 # 100% of samples stuck here
```
- `kill -9` cannot terminate the process (state: `UE` — uninterruptible + exiting)
- The same binary works fine when run from `target/release/blue` directly
- The same binary works fine when copied to `/tmp/` without extended attributes
### Root Cause
`cp` on macOS preserves extended attributes by default. The combination of:
1. `com.apple.provenance` xattr (marks binary as "downloaded/copied")
2. Stale adhoc linker signature from original `cargo build`
3. Updated dylib versions on disk (Homebrew `openssl@3`)
...causes `dyld` to enter a signature verification path that deadlocks.
### Evidence
| Test | Result |
|------|--------|
| `target/release/blue init` | Works (0.1s) |
| `cp` to `~/.cargo/bin/blue` then run | Hangs at `_dyld_start` |
| `cp` to `/tmp/blue-copy` then run | Works (no provenance xattr) |
| Symlink to `target/release/blue` | Works |
| `xattr -cr` + `codesign --force --sign -` | Works |
## Proposal
### 1. Support `cargo install` as primary method
Add workspace metadata so `cargo install --path apps/blue-cli` works correctly. This lets Cargo handle the build-and-install atomically, producing a freshly signed binary.
**INSTALL.md** becomes:
```bash
# Build and install (recommended)
cargo install --path apps/blue-cli
# Then configure for Claude Code
blue install
```
### 2. Post-copy re-signing in `install.sh`
For users who prefer `install.sh` or `cp`:
```bash
cp "$BINARY" "$INSTALL_DIR/blue"
# Fix macOS code signature after copy
if [[ "$OSTYPE" == "darwin"* ]]; then
xattr -cr "$INSTALL_DIR/blue"
codesign --force --sign - "$INSTALL_DIR/blue"
fi
```
### 3. Post-copy re-signing in `blue install` (Rust)
The `handle_install_command` currently doesn't copy the binary anywhere — it only sets up hooks, skills, and MCP config. But the `SessionStart` hook adds `target/release/` to PATH, which is fragile. Instead:
- Add an optional `--binary` flag to `blue install` that copies and re-signs the binary to `~/.cargo/bin/`
- Or detect if running from `target/release/` and warn the user
### 4. `blue doctor` validation
Add a check to `blue doctor` that detects:
- Stale code signatures on the installed binary
- Mismatched binary vs source versions
- Presence of `com.apple.provenance` xattr on the binary
## Implementation Plan
### Phase 1: Fix `install.sh` (immediate)
1. Add `xattr -cr` + `codesign --force --sign -` after every `cp` of the binary
2. Add a verification step that actually runs `blue --version` with a timeout
### Phase 2: Support `cargo install --path`
1. Verify `cargo install --path apps/blue-cli` works with the current workspace layout
2. Update `INSTALL.md` to recommend `cargo install` as the primary method
3. Update `install.sh` to use `cargo install` instead of `cp` when possible
### Phase 3: Harden `blue doctor`
1. Add macOS signature check: `codesign --verify` on the installed binary
2. Add xattr check: warn if `com.apple.provenance` is present
3. Add timeout-based liveness check: run `blue --version` with a 3s timeout
## Files Changed
| File | Change |
|------|--------|
| `install.sh` | Add post-copy re-signing for macOS |
| `apps/blue-cli/src/main.rs` | Add doctor checks for signature issues |
| `INSTALL.md` | Recommend `cargo install --path` as primary method |
## Test Plan
- [x] `install.sh` produces a working binary on macOS after Homebrew openssl update
- [x] `cargo install --path apps/blue-cli` produces a working binary
- [x] `blue doctor` detects provenance xattr on binary (warns with fix hint)
- [x] `blue doctor` passes on a freshly installed binary
- [x] Install flow works on Linux (no-op for codesign steps via `#[cfg(target_os = "macos")]`)
---
*"Right then. Let's get to it."*
-- Blue