Extracts duplicated path canonicalization logic (filepath.Abs + EvalSymlinks)
into a reusable helper function utils.CanonicalizePath() in internal/utils/path.go.
Changes:
- Add internal/utils/path.go with CanonicalizePath() function
- Add comprehensive tests in internal/utils/path_test.go
- Replace inline canonicalization in beads.go:131-140
- Replace inline canonicalization in cmd/bd/main.go:446-454
- Replace inline canonicalization in cmd/bd/nodb.go:25-33
The new helper maintains identical behavior:
1. Converts path to absolute form via filepath.Abs
2. Resolves symlinks via filepath.EvalSymlinks
3. Falls back gracefully on errors (returns absPath if EvalSymlinks fails,
returns original path if Abs fails)
Fixes bd-efe8
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add BEADS_DIR as a replacement for BEADS_DB to point to the .beads
directory instead of the database file directly.
Rationale:
- With --no-db mode, there's no .db file to point to
- The .beads directory is the logical unit (contains config.yaml, db
files, jsonl files)
- More intuitive: point to the beads directory not the database file
Implementation:
- Add BEADS_DIR environment variable support to FindDatabasePath()
- Priority order: BEADS_DIR > BEADS_DB > auto-discovery
- Maintain backward compatibility with BEADS_DB (now deprecated)
- Update --no-db mode to respect BEADS_DIR
- Update MCP integration (config.py, bd_client.py)
- Update documentation to show BEADS_DIR as preferred method
Testing:
- Backward compatibility: BEADS_DB still works
- BEADS_DIR works with regular database mode
- BEADS_DIR works with --no-db mode
- Priority: BEADS_DIR takes precedence over BEADS_DB
Follow-up issues for refactoring:
- bd-efe8: Refactor path canonicalization into helper function
- bd-c362: Extract database search logic into helper function
Closes bd-e16b
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Renamed config.json to metadata.json to clarify purpose (database metadata)
- Fixed config.yaml/config.json conflict by making Viper explicitly load only config.yaml
- Added automatic migration from config.json to metadata.json on first read
- Fixed jsonOutput variable shadowing across 22 command files
- Updated bd init to create both metadata.json and config.yaml template
- Fixed 5 failing JSON output tests
- All tests passing
Resolves config file confusion and makes config.yaml work correctly.
Closes#178 (global flags), addresses config issues from #193
Amp-Thread-ID: https://ampcode.com/threads/T-e6ac8192-e18f-4ed7-83bc-4a5986718bb7
Co-authored-by: Amp <amp@ampcode.com>
- Add nolint:gosec comments for safe file operations
- G304: File reads from validated/secure paths
- G306/G302: JSONL/error files need 0644 for sharing/debugging
- G204: Subprocess launches with validated arguments
- G104: Deferred file close errors are non-critical
- G115: Safe integer conversions in backoff
- G201: SQL placeholders for IN clause expansion
All warnings are for intentional behavior that is safe in context.
Amp-Thread-ID: https://ampcode.com/threads/T-d78f2780-4709-497f-97b0-035ca8c809e1
Co-authored-by: Amp <amp@ampcode.com>
- Remove broken import special case that created vc.db instead of using found database
- Add git to nativeBuildInputs in default.nix for tests
- Fix path comparison bug (symlink resolution caused prefix mismatch)
When daemon detects multiple .db files (after filtering .backup and vc.db),
it now writes detailed error to .beads/daemon-error file before exiting.
The error file is checked and displayed when:
- Daemon discovery fails to connect
- Auto-start fails to yield a running daemon
- User runs 'bd daemons list'
This makes the error immediately visible without requiring users to check
daemon logs.
Changes:
- cmd/bd/daemon.go: Write daemon-error file on multiple .db detection
- internal/daemon/discovery.go: Read and surface daemon-error in DaemonInfo.Error
- cmd/bd/main.go: Display daemon-error when auto-start fails
Amp-Thread-ID: https://ampcode.com/threads/T-1005a8d1-7a5a-4844-ad2d-2b8a6145825f
Co-authored-by: Amp <amp@ampcode.com>
* Add bd doctor command for installation health checks
Implements a comprehensive health check command similar to claude doctor
that validates beads installation and provides actionable recommendations.
Features:
- Installation check (.beads/ directory exists)
- Database version verification (compares with CLI version)
- ID format detection (hash-based vs sequential)
- CLI version check (fetches latest from GitHub)
- Storage type detection (SQLite vs JSONL-only mode)
- Tree-style output with color-coded warnings
- JSON output for scripting (--json flag)
- Actionable fix recommendations for each issue
Implementation improvements:
- Status constants instead of magic strings
- Semantic version comparison (fixes 0.10.0 vs 0.9.9 edge case)
- Documented defer pattern for intentional error ignore
- Comprehensive test coverage including version comparison edge cases
- Clean integration using slices.Contains for command list
Usage:
bd doctor # Check current directory
bd doctor /path/to/repo # Check specific repository
bd doctor --json # Machine-readable output
* Simplify bd doctor documentation in README
Reduce verbose health check section to 2 lines as requested.
* Fix bd doctor to handle JSONL-only mode for ID format check
When no SQLite database exists (JSONL-only mode), skip the ID format
check instead of showing an error. This prevents the confusing
'Unable to query issues' error when the installation is actually fine.
Skip auto-import when sync command is run with --dry-run flag to prevent
database modifications during dry-run mode. Previously, autoImportIfNewer()
would run in PersistentPreRun hook and modify the database even in dry-run,
causing the JSONL file to become dirty.
Amp-Thread-ID: https://ampcode.com/threads/T-1bc29344-0c59-4127-855d-860d1579ba0b
Co-authored-by: Amp <amp@ampcode.com>
This allows users to set --no-db mode persistently via:
1. .beads/config.yaml file (no-db: true)
2. BD_NO_DB environment variable
3. --no-db command-line flag (highest precedence)
Changes:
- Add no-db to config defaults in internal/config/config.go
- Wire no-db flag to read from config in cmd/bd/main.go
- Create example .beads/config.yaml with documentation
The configuration precedence is:
CLI flag > Environment variable > Config file > Default
This makes no-db mode repository-scoped and automatically
respected by all bd commands and the beads-mcp service.
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
Implement --no-db mode to avoid SQLite database corruption in scenarios
where the same .beads directory is accessed from multiple processes
(e.g., host + container, multiple containers).
Changes:
- Add in-memory storage backend (internal/storage/memory/memory.go)
- Implements full Storage interface using in-memory data structures
- Thread-safe with mutex protection for concurrent access
- Supports all core operations: issues, dependencies, labels, comments
- Add JSONL persistence layer (cmd/bd/nodb.go)
- initializeNoDbMode(): Load .beads/issues.jsonl on startup
- writeIssuesToJSONL(): Atomic write-back after each command
- detectPrefix(): Smart prefix detection with fallback hierarchy
1. .beads/nodb_prefix.txt (explicit config)
2. Common prefix from existing issues
3. Current directory name (fallback)
- Integrate --no-db flag into command flow (cmd/bd/main.go)
- Add global --no-db flag to all commands
- PersistentPreRun: Initialize memory storage from JSONL
- PersistentPostRun: Write memory back to JSONL atomically
- Skip daemon and SQLite initialization in --no-db mode
- Extract common writeJSONLAtomic() helper to eliminate duplication
- Update bd init for --no-db mode (cmd/bd/init.go)
- Create .beads/nodb_prefix.txt instead of SQLite database
- Create empty issues.jsonl file
- Display --no-db specific initialization message
Code Quality:
- Refactored atomic JSONL writes into shared writeJSONLAtomic() helper
- Used by both flushToJSONL (SQLite mode) and writeIssuesToJSONL (--no-db mode)
- Eliminates ~90 lines of code duplication
- Ensures consistent atomic write behavior across modes
Usage:
bd --no-db init -p myproject
bd --no-db create "Fix bug" --priority 1
bd --no-db list
bd --no-db update myproject-1 --status in_progress
Benefits:
- No SQLite corruption from concurrent access
- Container-safe: perfect for multi-mount scenarios
- Git-friendly: direct JSONL diffs work seamlessly
- Simple: no daemon, no WAL files, just JSONL
Test Results (go test ./...):
- ✓ github.com/steveyegge/beads: PASS
- ✗ github.com/steveyegge/beads/cmd/bd: 1 pre-existing failure (TestAutoFlushErrorHandling)
- ✓ github.com/steveyegge/beads/internal/compact: PASS
- ✗ github.com/steveyegge/beads/internal/rpc: 1 pre-existing failure (TestMemoryPressureDetection)
- ✓ github.com/steveyegge/beads/internal/storage/sqlite: PASS
- ✓ github.com/steveyegge/beads/internal/types: PASS
- ⚠ github.com/steveyegge/beads/internal/storage/memory: no tests yet
All test failures are pre-existing and unrelated to --no-db implementation.
The new --no-db mode has been manually tested and verified working.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
via [Happy](https://happy.engineering)
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
- bd-159: Track last_import_time in metadata after auto-import
- bd-160: Add staleness check to daemon before serving requests
- Detects when JSONL mtime > last import time
- Currently logs warning (needs bd-166 for actual import trigger)
- Add lastImportTime field to RPC Server struct with getter/setter
- Add checkAndAutoImportIfStale() method to detect stale JSONL
This is part of bd-158 epic to fix daemon showing stale data after git pull.
Amp-Thread-ID: https://ampcode.com/threads/T-b554e049-aff8-4a24-8bf3-3305483b7f5a
Co-authored-by: Amp <amp@ampcode.com>
- Implement FindAllDatabases() to scan hierarchy for all .beads directories
- Add DatabaseInfo struct with path, beads dir, and issue count
- Add warnMultipleDatabases() with formatted warning display
- Show active database with ▶ marker and issue counts
- Add comprehensive tests for multi-database detection
- Document warning and solutions in TROUBLESHOOTING.md
- Prevent confusion and database pollution from accidental duplicates
Amp-Thread-ID: https://ampcode.com/threads/T-4941975f-2686-40d0-bc12-aabf38a05890
Co-authored-by: Amp <amp@ampcode.com>
Fixed all unchecked error returns in production code:
- os.Remove() calls in cleanup paths
- cmd.Wait() in goroutines
- fmt.Fprintf() writes
- Type assertions with proper ok checks
Reduces linter issues from 99 to 88. CI should now pass linting.
Implements automatic daemon version detection and restart when client
and daemon versions are incompatible. Eliminates need for manual
'bd daemon --stop' after upgrades.
Changes:
- Check daemon version during health check in PersistentPreRun
- Auto-restart mismatched daemon or fall back to direct mode
- Check version when starting daemon, auto-stop old daemon if incompatible
- Robust restart logic: sets working dir, cleans stale sockets, reaps processes
- Uses waitForSocketReadiness helper for reliable startup detection
- Updated AGENTS.md with version management documentation
Closes bd-89
Amp-Thread-ID: https://ampcode.com/threads/T-231a3701-c9c8-49e4-a1b0-e67c94e5c365
Co-authored-by: Amp <amp@ampcode.com>
Fixes#114 and #122 by adding --description/-d flag to bd update CLI
and description parameter to MCP update_issue tool.
Changes:
- CLI: Added --description flag to updateCmd
- RPC: Added Description field to UpdateArgs
- Daemon: Updated updatesFromArgs to handle description
- MCP: Added description to update_issue, UpdateIssueParams, and clients
- Storage: description already supported in allowedUpdateFields
Tested in both daemon and direct modes.
- Implement robust worktree detection using git-dir vs git-common-dir comparison
- Add prominent warning when daemon mode is active in a worktree
- Warn in 3 places: initial connection, auto-start, and daemon start command
- Show shared database path and clarify BEADS_AUTO_START_DAEMON behavior
- Document limitations and solutions in README.md and AGENTS.md
- Add comprehensive tests for detection and path truncation
Fixes#55
Amp-Thread-ID: https://ampcode.com/threads/T-254eb9e3-1a42-42d7-afdf-b7ca2d2dcb8b
Co-authored-by: Amp <amp@ampcode.com>
- Update bd update to use --acceptance instead of --acceptance-criteria
- Update MCP client to use --acceptance flag
- Simplify SKILL.md documentation now that both commands use same flag
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
- Remove ~/.beads/bd.sock fallback in getSocketPath()
- Always return local socket path (.beads/bd.sock)
- Add migration warning if old global socket exists
- Update AGENTS.md to remove global daemon references
- Document breaking change in CHANGELOG.md
- Fix test isolation: tests now use temp .beads dir and chdir
Prevents cross-project daemon connections and database pollution.
Each project must use its own local daemon.
Amp-Thread-ID: https://ampcode.com/threads/T-c4454192-39c6-4c67-96a9-675cbfc4db92
Co-authored-by: Amp <amp@ampcode.com>
- Detects prefix mismatches during import
- Shows warning with mismatched prefixes and counts
- Added --rename-on-import flag to automatically fix prefixes
- Auto-import is lenient about prefixes (doesn't enforce)
- All tests passing
Previously, 'bd import' would fail if the database didn't exist, even when
importing into a directory with a .beads/ folder. This made it impossible
to recreate a database from JSONL after schema changes or database removal.
Changes:
- Allow import command to create database when .beads/ directory exists
- Prefer local .beads/vc.db over ancestor databases for import operations
- Enable the workflow: mv .beads/vc.db backup && bd import .beads/issues.jsonl
Fixes#180
Amp-Thread-ID: https://ampcode.com/threads/T-7842ea55-6ce4-4162-9ad8-299124a15946
Co-authored-by: Amp <amp@ampcode.com>
Changes to cmd/bd/main.go:
- Add BEADS_NO_DAEMON env var to explicitly disable daemon (single-user mode)
- Keep BEADS_AUTO_START_DAEMON for backward compatibility
- Lower global daemon threshold from 4+ repos to 2+ repos
- Document always-daemon mode as default behavior
Also create bd-161 epic for label enhancements with child tasks.
Amp-Thread-ID: https://ampcode.com/threads/T-675a2db5-b1b3-480d-a108-b003d8139d08
Co-authored-by: Amp <amp@ampcode.com>
Implemented bd-150: Improve daemon fallback visibility and user feedback
- Added DaemonStatus struct to track connection state
- Enhanced BD_DEBUG logging with detailed diagnostics and timing
- Added BD_VERBOSE mode with actionable warnings when falling back
- Implemented health checks before using daemon
- Clear fallback reasons: connect_failed, health_failed, auto_start_disabled, auto_start_failed, flag_no_daemon
- Updated documentation
Implemented bd-151: Add version compatibility checks for daemon RPC protocol
- Added ClientVersion field to RPC Request struct
- Client sends version (0.9.10) in all requests
- Server validates version compatibility using semver:
- Major version must match
- Daemon >= client for backward compatibility
- Clear error messages with directional hints (upgrade daemon vs upgrade client)
- Added ClientVersion and Compatible fields to HealthResponse
- Implemented 'bd version --daemon' command to check compatibility
- Fixed batch operations to propagate ClientVersion for proper checks
- Updated documentation with version compatibility section
Code review improvements:
- Propagate ClientVersion in batch sub-requests
- Directional error messages based on which side is older
- Made ServerVersion a var for future unification
Amp-Thread-ID: https://ampcode.com/threads/T-b5fe36b8-c065-44a9-a55b-582573671609
Co-authored-by: Amp <amp@ampcode.com>
- Refactored autoImportIfNewer() to use shared importIssuesCore()
- Removed ~200 lines of duplicated import logic from main.go
- Manual and auto-import now use identical collision detection/resolution
- Added auto-export scheduling after successful import (prevents JSONL drift)
- Optimized remapping notification (O(n) instead of O(n²), sorted output)
- Removed obsolete test functions for deleted helper functions
- Use bytes.NewReader instead of string conversion for better performance
Benefits:
- Future bug fixes only need to be made once
- Guaranteed consistency between manual and auto-import
- JSONL stays in sync with database after auto-import
- Clearer, more consistent user feedback
Amp-Thread-ID: https://ampcode.com/threads/T-1925a48d-ca8a-4b54-b4e7-de3ec755d25a
Co-authored-by: Amp <amp@ampcode.com>
- Implements bd stale command to show issues with execution_state where executor is dead/stopped
- Adds --release flag to automatically release orphaned issues
- Adds --threshold flag to customize heartbeat staleness threshold (default: 300s/5min)
- Handles missing executor instances (LEFT JOIN) for cases where executor was deleted
- Adds QueryContext and BeginTx helper methods to SQLiteStorage for advanced queries
- Fixes ExternalRef comparison bug in import_shared.go (pointer vs string)
- Removes unused imports in import.go
Resolves vc-124
- Implement shouldUseGlobalDaemon() with multi-repo detection
- Auto-detect 4+ beads repos and prefer global daemon
- Support BEADS_PREFER_GLOBAL_DAEMON env var for explicit control
- Add 'bd daemon --migrate-to-global' migration helper
- Update auto-start logic to use global daemon when appropriate
- Update documentation in AGENTS.md and README.md
Amp-Thread-ID: https://ampcode.com/threads/T-9af9372d-f3f3-4698-920d-e5ad1486d849
Co-authored-by: Amp <amp@ampcode.com>
Improvements based on oracle code review:
- Move socket cleanup AFTER lock acquisition (prevents unlinking live sockets)
- Add PID liveness check before removing stale socket
- Add stale lock detection with retry mechanism
- Tighten directory permissions to 0700 for security
- Improve socket readiness probing with shorter timeouts
- Make removeOldSocket() ignore ENOENT errors
Fixes race condition where socket could be removed during daemon startup window,
potentially orphaning a running daemon process.
Amp-Thread-ID: https://ampcode.com/threads/T-63542c60-b5b9-4a34-9f22-415d9d7e8223
Co-authored-by: Amp <amp@ampcode.com>
- bd-170: Implement hybrid sorting for ready work (recent 48h first, then oldest)
- bd-87: Use safer null-byte placeholders in ID remapping
- bd-92: Make auto-flush debounce configurable via BEADS_FLUSH_DEBOUNCE
- bd-171: Fix nil pointer dereference in renumber command
- Delete spurious test issues (bd-7, bd-130-134)
- Renumber database from 171 down to 144 issues
The show command was crashing with a nil pointer dereference when
accessing issue.CompactionLevel. This occurred due to two issues in
the daemon mode response handling:
1. The IssueDetails struct incorrectly embedded *types.Issue as a
pointer, causing the JSON unmarshaling to leave it nil. Changed
to embed types.Issue directly.
2. Missing null check for non-existent issues. The daemon returns
null when an issue is not found, which wasn't handled properly.
Added explicit null checking before parsing the response to provide
a clear error message when issues don't exist.
Fixes panic when running: bd show <non-existent-id>
- Auto-starts daemon on first bd command (unless --no-daemon or BEADS_AUTO_START_DAEMON=false)
- Exponential backoff on failures: 5s, 10s, 20s, 40s, 80s, 120s (max)
- Lockfile prevents race conditions when multiple commands start daemon simultaneously
- Stdio redirected to /dev/null to prevent daemon output in foreground
- Uses os.Executable() for security (prevents PATH hijacking)
- Socket readiness verified with actual connection test
- Accepts multiple falsy values: false, 0, no, off (case-insensitive)
- Working directory set to database directory for local daemon context
- Comprehensive test coverage including backoff math and concurrent starts
Fixes:
- Closes bd-1 (won't fix - compaction keeps DBs small)
- Closes bd-124 (daemon auto-start implemented)
Documentation updated in README.md and AGENTS.md
Amp-Thread-ID: https://ampcode.com/threads/T-b10fe866-ab85-417f-9c4c-5d1f044c5796
Co-authored-by: Amp <amp@ampcode.com>
Implements bd-121: Global daemon with system-wide socket
Changes:
- Add --global flag to daemon command
- Use ~/.beads/bd.sock when --global is set
- Skip git repo validation for global daemon
- Update daemon discovery to check ~/.beads/ as fallback
- Both Go CLI and Python MCP client check global socket
- Update all tests to pass global parameter
Benefits:
- Single daemon serves all repos on system
- No per-repo daemon management needed
- Better resource usage for users with many repos
- Automatic fallback when local daemon not running
Usage:
bd daemon --global # Start global daemon
bd daemon --status --global # Check global status
bd daemon --stop --global # Stop global daemon
Related: bd-73 (multi-repo epic)
Amp-Thread-ID: https://ampcode.com/threads/T-ea606216-b886-4af0-bba8-56d000362d01
Co-authored-by: Amp <amp@ampcode.com>