fix: make bd setup claude project-local by default and auto-setup in bd init#2972
Conversation
…bd init (gastownhall#2935) Changes: - bd setup claude now defaults to project-local (.claude/settings.json) instead of global (~/.claude/settings.json) - bd setup claude --global explicitly installs globally - bd init automatically runs Claude hook setup (project-local) - Legacy .claude/settings.local.json hooks are auto-migrated on install and cleaned on remove - bd setup claude --check detects legacy installs with migration hint - bd init stages .claude/settings.json and CLAUDE.md for git commit Closes gastownhall#2935 Amp-Thread-ID: https://ampcode.com/threads/T-019d4fc1-6587-75cb-bdcb-1912a98cb1c1 Co-authored-by: Amp <amp@ampcode.com>
hilmes
left a comment
There was a problem hiding this comment.
Review: fix: make bd setup claude project-local by default and auto-setup in bd init
Verdict: Correct behavioral change with clean polarity inversion. The settings.json vs settings.local.json distinction aligns with Claude Code's team-vs-personal semantics. Legacy migration is safe. One notable test coverage gap for the migration paths.
4 files, +126 −32. 1 commit. Author: maphew (co-authored with Amp).
Core change: Default scope inversion ✅
Old: bd setup claude → global (~/.claude/settings.json), --project → project (settings.local.json)
New: bd setup claude → project (.claude/settings.json), --global → global (~/.claude/settings.json)
The boolean parameter flips from project bool to global bool throughout the call chain. Every call site correctly updated:
| Call site | Old | New | Correct? |
|---|---|---|---|
setup.go InstallClaude |
setupProject |
setupGlobal |
✅ |
setup.go RemoveClaude |
setupProject |
setupGlobal |
✅ |
init.go InstallClaudeProject |
— (new) | installClaude(env, false, stealth) |
✅ |
| 9 test call sites | flipped | verified individually | ✅ |
Settings file semantics: Exactly right ✅
The move from settings.local.json → settings.json for the project default is semantically correct per Claude Code's design:
.claude/settings.json= shared/team (committed to repo, all collaborators get the hooks).claude/settings.local.json= personal (git-ignored, per-developer overrides)
bd prime hooks should be shared with the team. Writing to the committed file is the correct default.
bd init auto-setup ✅
Placement is right: after AGENTS.md generation, before git auto-stage. Guards are correct:
!stealth— stealth mode wants invisible setup!skipAgents— if agents are skipped, Claude hooks are irrelevant!isBareGitRepo()— bare repos have no working tree for.claude/- Error is non-fatal (warning only) — init continues even if Claude setup fails
Git staging of .claude/settings.json and CLAUDE.md in the auto-commit block is correct.
InstallClaudeProject export ✅
Clean thin wrapper: resolves claudeEnv, calls installClaude(env, false, stealth), returns error instead of calling os.Exit. This is the right pattern for use from bd init where failures should be non-fatal.
Legacy migration: Safe and thorough ✅
Install path: After writing to settings.json, if !global, checks settings.local.json for beads hooks → removes them → writes back → prints migration message. Preserves non-beads hooks in the legacy file. Does not delete the file.
Remove path: After cleaning settings.json, if !global, also cleans settings.local.json. Correct: bd setup claude --remove cleans both locations.
Check path: Priority order is settings.json → global → legacy, with migration hint for legacy-only installs. Correct.
Edge case: both files have hooks → install writes to new, removes from legacy. After migration, hooks only in settings.json. Clean.
Edge case: legacy file has non-beads hooks → only bd prime variants removed, other hooks preserved. Safe.
Edge case: legacy file is malformed JSON → json.Unmarshal fails silently, migration skipped. Correct — don't break install because a legacy file is corrupted.
🟡 --project flag becomes a silent no-op for Claude
The --project flag still exists (used by gemini/mux) but the Claude recipe now reads setupGlobal instead of setupProject. Running bd setup claude --project does the same thing as bd setup claude — project-local install, since that's the default. No error, no warning. The flag help text correctly removes claude from --project's description.
This is technically correct (you get what you asked for — a project install) but could surprise users who relied on --project semantically. Acceptable given --project was previously the non-default path; now it is the default.
🟡 No dedicated tests for legacy migration
The new migration code (install, remove, and check paths) has no dedicated test coverage:
- No test for install with existing
settings.local.jsonhooks → verifying migration message and hook removal from legacy - No test for remove with existing
settings.local.jsonhooks → verifying both files cleaned - No test for check with only legacy hooks → verifying migration hint output
The migration logic is straightforward (reuses existing removeHookCommand + hasBeadsHooks), so the risk is low, but given this is the primary upgrade path for existing users, a TestLegacyMigration table-driven test would add confidence.
🟢 Minor: hasBeadsHooks uses os.ReadFile directly
Pre-existing pattern, not introduced by this PR, but worth noting: hasBeadsHooks uses os.ReadFile while the migration block uses env.readFile. Works in tests because they use real temp directories, but creates a subtle inconsistency in the testability model.
Summary
| Component | Verdict | Notes |
|---|---|---|
| Boolean polarity inversion | ✅ Correct | All 9+ call sites verified |
| settings.json vs settings.local.json | ✅ Semantically right | Team-shared hooks belong in the committed file |
| bd init auto-setup | ✅ Well-guarded | Correct placement, non-fatal errors |
| Legacy migration | ✅ Safe | Handles edge cases, preserves non-beads hooks |
| Test updates | 🟡 Incomplete | Existing tests updated correctly, but no new migration tests |
| --project flag for Claude | 🟡 Silent no-op | Acceptable, help text updated |
Ship it. Migration test coverage can come in a follow-up.
Fixes #2935
Problem
bd setup claude(without flags) installed globally to~/.claude/settings.json— surprising for project setupbd setup --project claudewrote to.claude/settings.local.json— but users wanted.claude/settings.json(shared/team file) so all collaborators get the prime hooksbd initdidn't set up Claude hooks at all — users expected it to do everything neededChanges
Default scope flipped to project-local
bd setup claude→ now writes to.claude/settings.json(shared/team file, committed to repo)bd setup claude --global→ explicitly installs to~/.claude/settings.jsonbd initauto-configures Claudebd initnow automatically runs Claude hook setup (project-local) unless--stealthor--skip-agentsis set.claude/settings.json,CLAUDE.md) are auto-staged in gitLegacy migration
.claude/settings.local.jsonbd setup claude --checkdetects legacy installs with a migration hintbd setup claude --removecleans both.claude/settings.jsonand legacy.claude/settings.local.jsonFiles changed
cmd/bd/setup/claude.go— Core behavior change, legacy migration, newInstallClaudeProjectexportcmd/bd/setup.go— Wire--globalflag for claude recipe, update flag descriptionscmd/bd/init.go— Auto-run Claude setup, stage Claude files in gitcmd/bd/setup/claude_test.go— Updated tests for new semantics