- Mark unused ctx parameter with underscore in getRepoRootForWorktree
- Replace exec.Command("test", "-d") with os.Stat for directory check
- Handle file.Close() errors properly in compact.go and migrate_tombstones.go
- Explicitly ignore cleanup errors with _ assignment
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update default.nix vendorHash to match current go.mod dependencies
- Fix NewSQLiteStorage calls to include required context.Context parameter
- Rename duplicate runCmd to runGitCmd in routing_integration_test.go
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CloseIssue was storing the reason only in the events table, not in the
issues.close_reason column. This caused `bd show --json` to return an
empty close_reason even when one was provided.
- Update CloseIssue in queries.go and transaction.go to set close_reason
- Clear close_reason when reopening issues (in manageClosedAt)
- Add tests for close_reason in storage and CLI JSON output
- Document the dual-storage of close_reason (issues + events tables)
Adds a new command that displays a thank you page listing all human
contributors to the beads project. Features:
- Static list of contributors (compiled into binary)
- Top 20 featured contributors displayed in columns
- Additional contributors in wrapped list
- Styled output using lipgloss (colored box, sections)
- Dynamic width based on content
- JSON output support (--json flag)
- Excludes bots and AI agents by email pattern
- Fix bd-tnsq: executeDelete now sets closed_at=NULL when creating
tombstones, satisfying the CHECK constraint that requires
closed_at IS NULL when status != 'closed'
- Fix bd-08ea: cleanup command now also prunes expired tombstones
(older than 30 days) after converting closed issues to tombstones
- Add regression test for batch deletion of closed issues
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixes#543, #544, #545, #546 (no-db mode regressions)
Memory backend fixes:
- GetReadyWork now properly excludes issues with open blocks dependencies
- GetBlockedIssues now includes issues with status=blocked (even with 0 blockers)
- LoadFromIssues initializes hierarchical child counters from existing IDs
so repeated --parent creates bd-xxx.1, bd-xxx.2, etc.
JSONL path discovery:
- findJSONLPath works in no-db mode when dbPath is empty
- Honors BEADS_JSONL environment variable override
- Falls back to locating .beads directory
Based on PR #547 by @joelklabo - cherry-picked core fixes.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
When multiple polecats run bd sync simultaneously, they race to push to
the shared beads-sync branch. Previously this caused non-fast-forward
errors that blocked all polecats.
Now pushFromWorktree:
- Detects non-fast-forward errors from git push output
- On conflict: fetches remote, rebases local commits on top, retries
- Uses exponential backoff for transient failures (up to 5 retries)
- Aborts rebase cleanly if it fails to leave worktree in good state
Fixes gt-zqor.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements single-shot mode improvements for Windows and Docker scenarios:
- Add --lock-timeout global flag (default 30s, 0 = fail immediately)
- Add config file support: lock-timeout: 100ms
- Parameterize SQLite busy_timeout via NewWithTimeout() function
- In --sandbox mode: default lock-timeout to 100ms
- In --sandbox mode: skip FlushManager creation (no background goroutines)
This addresses bd.exe hanging on Windows and locking conflicts when
using beads across host + Docker containers.
Closes: bd-59er, bd-r4od, bd-dh8a
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds comprehensive Git worktree support for beads issue tracking:
Core changes:
- New internal/git/gitdir.go package for worktree detection
- GetGitDir() returns proper .git location (main repo, not worktree)
- Updated all hooks to use git.GetGitDir() instead of local helper
- BeadsDir() now prioritizes main repository's .beads directory
Features:
- Hooks auto-install in main repo when run from worktree
- Shared .beads directory across all worktrees
- Config option no-install-hooks to disable auto-install
- New bd worktree subcommand for diagnostics
Documentation:
- New docs/WORKTREES.md with setup instructions
- Updated CHANGELOG.md and AGENT_INSTRUCTIONS.md
Testing:
- Updated tests to use exported git.GetGitDir()
- Added worktree detection tests
Co-authored-by: Claude <noreply@anthropic.com>
Closes: #478
Fix daemon auto-sync delete mutation not reflected in sync branch
When deleting an issue with `bd delete <id> --force`, the daemon auto-sync now properly removes the deleted issue from the sync branch.
**Problem:** The merge logic saw fewer local issues (due to deletion) and would re-add the deleted issue.
**Solution:** Add `ForceOverwrite` option to bypass merge logic when mutations occur. Mutation-triggered exports are authoritative and should overwrite, not merge.
Reviewed-by: stevey
The importer was not seeing tombstones when building the dbByID map,
causing it to treat tombstone IDs as "new" issues. This led to UNIQUE
constraint violations during INSERT.
- Include tombstones in SearchIssues call (IncludeTombstones: true)
- Skip tombstones when matching by ID instead of trying to update them
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When CreateTombstone was called on a closed issue, the CHECK constraint
(status = closed) = (closed_at IS NOT NULL) was violated because
closed_at was not cleared. Now setting closed_at = NULL in the UPDATE.
Added regression test for creating tombstone from closed issue.
Fixes: bd-fi05
Generated with Claude Code
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The deleted_at column was defined as TEXT in the schema but code was
trying to scan into sql.NullTime. The ncruces/go-sqlite3 driver only
auto-converts TEXT to time.Time for columns declared as DATETIME/DATE/
TIME/TIMESTAMP. For TEXT columns, it returns raw strings which
sql.NullTime.Scan() cannot handle.
Added parseNullableTimeString() helper that manually parses time strings
and changed all deletedAt variables from sql.NullTime to sql.NullString.
Fixes import failure: "sql: Scan error on column index 22, name
deleted_at: unsupported Scan, storing driver.Value type string into
type *time.Time"
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(daemon): check for stale startlock before waiting 5 seconds
When a previous daemon startup left behind a bd.sock.startlock file
(e.g., from a crashed process), the code was waiting 5 seconds before
checking if the lock was stale. This caused unnecessary delays on
every bd command when the daemon wasn't running.
Now checks if the PID in the startlock file is alive BEFORE waiting.
If the PID is dead or unreadable, the stale lock is cleaned up
immediately and lock acquisition is retried.
Fixes ~5s delay when startlock file exists from crashed process.
* perf: add benchmarks for large descriptions, bulk operations, and sync merge
Added three new performance benchmarks to identify bottlenecks in common operations:
1. BenchmarkLargeDescription - Tests handling of 100KB+ issue descriptions
- Measures string allocation/parsing overhead
- Result: 3.3ms/op, 874KB/op allocation
2. BenchmarkBulkCloseIssues - Tests closing 100 issues sequentially
- Measures batch write performance
- Result: 1.9s total, shows write amplification
3. BenchmarkSyncMerge - Tests JSONL merge cycle with creates/updates
- Simulates real sync operations (10 creates + 10 updates per iteration)
- Result: 29ms/op, identifies sync bottlenecks
Added BENCHMARKS.md documentation describing:
- How to run benchmarks with various options
- All available benchmark categories
- Performance targets on M2 Pro hardware
- Dataset caching strategy
- CPU profiling integration
- Optimization workflow
This completes performance testing coverage for previously unmeasured scenarios.
* docs: clarify daemon lock acquisition logic in comments
Improve comments to clarify that acquireStartLock does both:
1. Immediately check for stale locks from crashed processes (avoids 5s delay)
2. If PID is alive, properly wait for legitimate daemon startup (5s timeout)
No code changes - only clarified comment documentation for maintainability.
---------
Co-authored-by: Steve Yegge <steve.yegge@gmail.com>
* feat(config): add no-install-hooks config to disable git hook installation
Add `no-install-hooks` boolean config that prevents git hook installation
during `bd init`. This can be set via:
- Environment variable: BD_NO_INSTALL_HOOKS=1
- Global config: ~/.config/bd/config.yaml with `no-install-hooks: true`
- Local config: .beads/config.yaml with `no-install-hooks: true`
The existing `--skip-hooks` flag continues to work and takes precedence.
Default behavior unchanged: hooks install by default.
* docs: add no-install-hooks to configuration documentation
- Add no-install-hooks to Supported Settings table in CONFIG.md
- Add example in config file section
- Add "Disabling Hook Installation" section to GIT_INTEGRATION.md
with examples for flag, env var, and config file methods
Implements the bd reset command for GitHub issue #479:
- CLI command with flags: --hard, --force, --backup, --dry-run, --skip-init, --verbose
- Impact summary showing issues/tombstones to be deleted
- Confirmation prompt (skippable with --force)
- Colored output for better UX
- Unit tests for reset.go and git.go
- Fix: use --force flag in git rm to handle staged files
Part of epic bd-aydr.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fix sync bug where newly created issues were incorrectly tombstoned during bd sync.
The root cause was git-history-backfill finding issues in local commits on the sync branch, then tombstoning them when they weren't in the merged JSONL. The fix protects issues from the left snapshot (local export) from git-history-backfill.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Enable daemon RPC support for delete operations to trigger auto-sync.
This PR adds delete operation support to the RPC daemon, ensuring that delete operations emit mutation events and trigger auto-sync like other mutating operations.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
- Add bd migrate-tombstones command (bd-8f9) to convert legacy
deletions.jsonl entries to inline tombstones in issues.jsonl
- Supports --dry-run to preview changes
- Supports --verbose for detailed progress
- Archives deletions.jsonl with .migrated suffix after migration
- Update bd compact to prune expired tombstones (bd-okh)
- All compact modes now prune tombstones older than 30-day TTL
- Reports count of pruned tombstones in output
- Add resurrection merge test (bd-bob)
- Tests scenario where base is tombstone but both left/right resurrect
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changed Priority from hardcoded 2 to 0 (unset) to distinguish legacy tombstones
from user-set values. IssueType remains TypeTask as empty fails validation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Track legacy deletions.jsonl entries converted to tombstones during import:
- Add Result.ConvertedToTombstone counter
- Add Result.ConvertedTombstoneIDs for the converted IDs
- Update test to verify the new counter
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add TestIsExpiredTombstone with edge cases for merge package
- Add TestImportIssues_LegacyDeletionsConvertedToTombstones for importer
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update import/export to handle tombstones for deletion sync propagation:
Exporter:
- Include tombstones in JSONL output by setting IncludeTombstones: true
- Both single-repo and multi-repo exports now include tombstones
Importer:
- Tombstones from JSONL are imported as-is (they're issues with status=tombstone)
- Legacy deletions.jsonl entries are converted to tombstones via convertDeletionToTombstone()
- Non-tombstone issues in deletions manifest are still skipped (backward compat)
- purgeDeletedIssues() now creates tombstones instead of hard-deleting
This is Phase 2 of the tombstone implementation (bd-dli design), enabling
inline soft-delete tracking for cross-clone deletion synchronization.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- bd-ki14: Preserve tombstones when other side implicitly deleted
In merge3WayWithTTL(), implicit deletion cases now check if the
remaining side is a tombstone and preserve it instead of dropping.
- bd-ig5: Remove duplicate constants from merge package
StatusTombstone, DefaultTombstoneTTL, and ClockSkewGrace now
reference the types package to avoid duplication.
- bd-6x5: Handle empty DeletedAt in mergeTombstones()
Added explicit handling for edge cases where one or both tombstones
have empty DeletedAt fields with deterministic behavior.
- bd-1sn: Copy tombstone fields in mergeIssue() safety fallback
When status becomes tombstone via mergeStatus safety fallback,
tombstone fields are now copied from the appropriate side.
Added comprehensive tests for all fixed edge cases.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 1 of tombstone migration: bd delete now creates tombstones instead
of hard-deleting issues.
Key changes:
- Add CreateTombstone() method to SQLiteStorage for soft-delete
- Modify executeDelete() to create tombstones instead of removing rows
- Add IsExpired() method with 30-day default TTL and clock skew grace
- Fix deleted_at schema from TEXT to DATETIME for proper time scanning
- Update delete.go to call CreateTombstone (single issue path)
- Still writes to deletions.jsonl for backward compatibility (dual-write)
- Dependencies are removed when creating tombstones
- Tombstones are excluded from normal searches (bd-1bu)
TTL constants:
- DefaultTombstoneTTL: 30 days
- MinTombstoneTTL: 7 days (safety floor)
- ClockSkewGrace: 1 hour
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add partial index on deleted_at for efficient TTL queries
- Exclude tombstones from SearchIssues by default (new IncludeTombstones filter)
- Report tombstone count separately in GetStatistics
- Display tombstone count in bd stats output
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add validation in ValidateWithCustomStatuses() requiring deleted_at for tombstones
- Add validation that non-tombstones cannot have deleted_at set
- Block direct status update to tombstone in validateStatusWithCustom()
- Users must use 'bd delete' instead of 'bd update --status=tombstone'
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
When syncing JSONL to worktree, if the worktree has more issues than
local, merge them instead of blindly overwriting. This prevents fresh
clones from accidentally deleting remote issues when they sync with
fewer issues than the sync branch.
Root cause of GitHub #464: A fresh clone with sync-branch configured
would start with an empty database (since JSONL is on sync-branch, not
HEAD). When syncing, the local 1-issue JSONL would overwrite the
remotes 10-issue JSONL, and the subsequent 3-way merge would see this
as local deleted 9 issues causing deletion to win.
The fix compares issue counts and triggers a merge when local has fewer
issues than the worktree (remote). Uses 3-way merge with empty base to
combine both sets of issues.
Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: use os.Lstat for symlink-safe mtime and permission checks
On NixOS and other systems using symlinks heavily (e.g., home-manager),
os.Stat follows symlinks and returns the target's metadata. This causes:
1. False staleness detection when JSONL is symlinked - mtime of target
changes unpredictably when symlinks are recreated
2. os.Chmod failing or changing wrong file's permissions when target
is in read-only location (e.g., /nix/store)
3. os.Chtimes modifying target's times instead of the symlink itself
Changes:
- autoimport.go: Use Lstat for JSONL mtime in CheckStaleness()
- import.go: Use Lstat in TouchDatabaseFile() for JSONL mtime
- export.go: Skip chmod for symlinked files
- multirepo.go: Use Lstat for JSONL mtime cache
- multirepo_export.go: Use Lstat for mtime, skip chmod for symlinks
- doctor/fix/permissions.go: Skip permission fixes for symlinked paths
These changes are safe cross-platform:
- On systems without symlinks, Lstat behaves identically to Stat
- Symlink permission bits are ignored on Unix anyway
- The extra Lstat syscall overhead is negligible
Fixes symlink-related data loss on NixOS. See GitHub issue #379.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* test: add symlink behavior tests for NixOS compatibility
Add tests that verify symlink handling behavior:
- TestCheckStaleness_SymlinkedJSONL: verifies mtime detection uses
symlink's own mtime (os.Lstat), not target's mtime (os.Stat)
- TestPermissions_SkipsSymlinkedBeadsDir: verifies chmod is skipped
for symlinked .beads directories
- TestPermissions_SkipsSymlinkedDatabase: verifies chmod is skipped
for symlinked database files while still fixing .beads dir perms
Also adds devShell to flake.nix for local development with go, gopls,
golangci-lint, and sqlite tools.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Replaces manual working directory save/restore patterns
with Go's built-in `t.Chdir()` helper across 23 test files.
The manual pattern involved calling `os.Getwd()` to save
the original directory, using `defer os.Chdir(origWd)` for
restoration, and manually handling errors during directory
changes. This boilerplate has been replaced with single
`t.Chdir(path)` calls that handle cleanup automatically.
The `t.Chdir()` method automatically restores the working
directory when the test completes, eliminating the need for
manual defer statements and error handling.
Total:
~75 instances replaced (assuming Claude's math is right)
Co-authored-by: Claude <noreply@anthropic.com>
Reduces the default retention period for deletion manifest entries.
Shorter TTL limits blast radius of stale deletions that can poison
beads installations when agents get out of sync.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Fix gosec G204/G304 warnings by adding exclusions for safe subprocess
launches and file reads in doctor.go, jira.go, migrate_sync.go, and
syncbranch/worktree.go
- Fix misspell: "cancelled" -> "canceled" in sync.go
- Fix unparam: mark unused ctx params in jira.go placeholder functions
- Fix errcheck: explicitly ignore fmt.Sscanf return in doctor.go and
use closure pattern for deferred os.RemoveAll in worktree.go
- Increase Windows test timeout from 10m to 20m to prevent CI timeouts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The estimated_minutes field existed in the Issue schema but wasn't exposed
via CLI. This adds:
- --estimate / -e flag to bd create (e.g., bd create "Task" --estimate 120)
- --estimate / -e flag to bd update (e.g., bd update bd-xyz --estimate 60)
- EstimatedMinutes field to RPC CreateArgs and UpdateArgs
- Server-side handling in handleCreate and updatesFromArgs
- Validation for non-negative values
The value is specified in minutes and is useful for planning and
prioritization. The vscode-beads extension already has an Estimate column
that can now be populated.
Fixes#443🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add --dry-run flag to preview fixes without applying changes
- Handle corrupted/empty/null-byte registry files gracefully
- Treat corrupted registry as empty instead of failing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Root cause: metadata.json is tracked in git and contains last_bd_version.
When git operations (pull, checkout, merge) reset metadata.json to the
committed version, the upgrade notification would fire repeatedly.
Fix: Store the last used bd version in .beads/.local_version which is
gitignored, so git operations don't affect version tracking.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
isLikelyHash() required at least one digit to distinguish hashes from
English words, but base36 hashes can be all-letters by chance.
This caused ExtractIssuePrefix("xa-adt-bat") to return "xa" instead
of "xa-adt", breaking import for 20 issues in xa-adapt.
Fix: Accept all-letter suffixes for 3-char only, keep digit requirement
for 4+ chars where word collision probability is low enough (~0.2%).
Rationale:
- 3-char: 36³ = 46K hashes, ~1000 common words = ~2% collision
- 4-char: 36⁴ = 1.6M hashes, ~3000 words = ~0.2% collision
- 5+ char: collision rate negligible
Also updated CONFIG.md to clarify mass delete threshold requires >5 issues (bd-in6).
The string(rune('0'+i)) pattern produces incorrect characters when i >= 10.
Changed to strconv.Itoa(i) for reliable conversion.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- bd-dtm: Changed stderr printing to use SafetyWarnings in worktree.go
- bd-ciu: Fixed non-deterministic output order in formatVanishedIssues
- bd-dmd: Removed duplicate safety check message in sync.go
- bd-k2n: PushSyncBranch now recreates worktree if cleaned up
- bd-c5m: Fixed string(rune()) in tests to use strconv.Itoa
- bd-8uk: Added test for SafetyWarnings population
- bd-1kf: Fixed mergePriority to handle negative priorities
- bd-xo9: Documented sync.require_confirmation_on_mass_delete config
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add auto-push functionality to PullFromSyncBranch for true one-command sync:
- After successful content merge, auto-push to remote by default
- Safety check: warn (but dont block) if >50% issues vanished AND >5 existed
- Vanished = removed from JSONL entirely, NOT status=closed
Changes:
- Add push parameter to PullFromSyncBranch function
- Add Pushed field to PullResult struct
- Add countIssuesInContent helper for safety check
- Add test for countIssuesInContent function
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
When multiple clones commit to beads-sync branch and histories diverge,
git merge would fail. This replaces git's commit-level merge with a
content-based merge that extracts JSONL from base/local/remote and
merges at the semantic level.
Key changes:
- Add divergence detection using git rev-list --left-right
- Extract JSONL content from specific commits for 3-way merge
- Reset to remote's history then commit merged content on top
- Pre-emptive fetch before commit to reduce divergence likelihood
- Deletions.jsonl merged by union (keeps all deletions)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
After adding close_reason column to issues table, two functions still
called GetCloseReason() to fetch from events table after already
scanning the column. Removed the redundant code.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add migration 017_close_reason_column.go to create the column
- Update all INSERT statements to include close_reason
- Update all SELECT statements to include close_reason
- Update doctor.go to check for close_reason in schema validation
- Remove workaround code that batch-loaded close reasons from events table
- Fix migrations_test.go to include close_reason in test table schema
This fixes sync loops where close_reason values were silently dropped
because the DB lacked the column despite the struct having the field.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>