fix: NixOS symlink compatibility for mtime and permission checks (#459)
* 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>
This commit is contained in:
@@ -289,7 +289,11 @@ func CheckStaleness(ctx context.Context, store storage.Storage, dbPath string) (
|
||||
dbDir := filepath.Dir(dbPath)
|
||||
jsonlPath := utils.FindJSONLInDir(dbDir)
|
||||
|
||||
stat, err := os.Stat(jsonlPath)
|
||||
// Use Lstat to get the symlink's own mtime, not the target's.
|
||||
// This is critical for NixOS and other systems where JSONL may be symlinked.
|
||||
// Using Stat would follow symlinks and return the target's mtime, which can
|
||||
// cause false staleness detection when symlinks are recreated (e.g., by home-manager).
|
||||
stat, err := os.Lstat(jsonlPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// JSONL doesn't exist - expected for new repo
|
||||
|
||||
Reference in New Issue
Block a user