* 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>
Implements automatic fix capability for bd doctor with user confirmation
and security hardening.
Features:
- Organizes fix implementations under doctor/fix/ directory structure
- Shows all fixable issues and prompts for confirmation (Y/n) before applying
- Provides clear output about what was fixed and any errors encountered
- Re-runs diagnostics after fixes to show updated state
- Each fix is idempotent and safe to run multiple times
Automatic fixes implemented:
- Git hooks (runs bd hooks install)
- Daemon health issues (runs bd daemons killall)
- DB-JSONL sync problems (runs bd sync --import-only)
- File permissions (fixes .beads/ and database permissions)
- Database version mismatches (runs bd migrate)
- Schema compatibility issues (runs bd migrate)
- Gitignore updates (writes canonical template)
Security improvements:
- Prevents command injection by using os.Executable() instead of PATH lookup
- Prevents path traversal attacks with workspace validation
- Fixes race conditions by using cmd.Dir instead of os.Chdir()
- Corrects file permission logic (proper bit masking)
- Validates all operations run in beads workspaces only
Files changed:
- cmd/bd/doctor.go: Enhanced applyFixes() with confirmation and better UX
- cmd/bd/doctor/gitignore.go: Fixed permissions (0600 → 0644)
- cmd/bd/doctor/fix/common.go: Security helpers (getBdBinary, validateBeadsWorkspace)
- cmd/bd/doctor/fix/hooks.go: Git hooks fix
- cmd/bd/doctor/fix/daemon.go: Daemon health fix
- cmd/bd/doctor/fix/sync.go: DB-JSONL sync fix
- cmd/bd/doctor/fix/permissions.go: File permissions fix
- cmd/bd/doctor/fix/migrate.go: Database migration fixes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>