Skip to content

fix: make bd setup claude project-local by default and auto-setup in bd init#2972

Merged
steveyegge merged 1 commit into
gastownhall:mainfrom
maphew:fix/2935-claude-project-local-default
Apr 3, 2026
Merged

fix: make bd setup claude project-local by default and auto-setup in bd init#2972
steveyegge merged 1 commit into
gastownhall:mainfrom
maphew:fix/2935-claude-project-local-default

Conversation

@maphew

@maphew maphew commented Apr 2, 2026

Copy link
Copy Markdown
Collaborator

Fixes #2935

Problem

  • bd setup claude (without flags) installed globally to ~/.claude/settings.json — surprising for project setup
  • bd setup --project claude wrote to .claude/settings.local.json — but users wanted .claude/settings.json (shared/team file) so all collaborators get the prime hooks
  • bd init didn't set up Claude hooks at all — users expected it to do everything needed

Changes

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.json

bd init auto-configures Claude

  • bd init now automatically runs Claude hook setup (project-local) unless --stealth or --skip-agents is set
  • Claude settings (.claude/settings.json, CLAUDE.md) are auto-staged in git

Legacy migration

  • Installing to project automatically migrates beads hooks from legacy .claude/settings.local.json
  • bd setup claude --check detects legacy installs with a migration hint
  • bd setup claude --remove cleans both .claude/settings.json and legacy .claude/settings.local.json

Files changed

  • cmd/bd/setup/claude.go — Core behavior change, legacy migration, new InstallClaudeProject export
  • cmd/bd/setup.go — Wire --global flag for claude recipe, update flag descriptions
  • cmd/bd/init.go — Auto-run Claude setup, stage Claude files in git
  • cmd/bd/setup/claude_test.go — Updated tests for new semantics

…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 hilmes left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.jsonsettings.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 JSONjson.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.json hooks → verifying migration message and hook removal from legacy
  • No test for remove with existing settings.local.json hooks → 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.

@steveyegge steveyegge merged commit 223895b into gastownhall:main Apr 3, 2026
31 checks passed
@maphew maphew deleted the fix/2935-claude-project-local-default branch April 3, 2026 03:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Can't easily initialize project with Claude settings

3 participants