{"record":{"id":"d78a8e6b56db131e","repo":"gastownhall/beads","slug":"truncating-lock-file-w","errorCode":null,"errorMessage":"truncating lock file: %w","messagePattern":"truncating lock file: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/linear/synclock.go","lineNumber":121,"sourceCode":"\treturn metadataErr\n}\n\n// SyncLockHeldError is returned when the lock is held by another process.\ntype SyncLockHeldError struct {\n\tInfo *SyncLockInfo\n}\n\nfunc (e *SyncLockHeldError) Error() string {\n\tif e.Info != nil {\n\t\treturn fmt.Sprintf(\"another bd linear sync is already running (PID %d, started %s)\",\n\t\t\te.Info.PID, e.Info.Started.Format(time.RFC3339))\n\t}\n\treturn \"another bd linear sync is already running\"\n}\n\nfunc writeLockInfo(f *os.File) error {\n\tif err := f.Truncate(0); err != nil {\n\t\treturn fmt.Errorf(\"truncating lock file: %w\", err)\n\t}\n\tif _, err := f.Seek(0, 0); err != nil {\n\t\treturn err\n\t}\n\tinfo := fmt.Sprintf(\"pid=%d\\nstarted=%s\\n\", os.Getpid(), time.Now().UTC().Format(time.RFC3339))\n\t_, err := f.WriteString(info)\n\treturn err\n}\n\nfunc readLockInfo(path string) *SyncLockInfo {\n\tdata, err := os.ReadFile(path) // #nosec G304 -- path is constructed from beadsDir, not user input\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn parseLockInfo(string(data))\n}\n\nfunc parseLockInfo(content string) *SyncLockInfo {","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/linear/synclock.go#L103-L139","documentation":"writeLockInfo refreshes the lock file contents with owner metadata, starting by truncating the file to zero. This error wraps a Truncate(0) failure on the held lock file, and propagates up through publishSyncLockInfo into AcquireSyncLock's \"writing lock info\" error.","triggerScenarios":"AcquireSyncLock succeeds on the flock but the subsequent f.Truncate(0) on the open lock file fails — typically I/O errors, disk full (with some filesystems), or the underlying file being replaced/deleted while held.","commonSituations":"Volume running out of space; lock file removed and recreated by another tool between open and truncate; NFS attribute-cache inconsistency; read-only remount after lock acquisition.","solutions":["Check and free disk space on the filesystem holding the beads directory","Ensure no external tooling deletes/recreates files in the beads directory during sync","Verify the beads directory is on a local filesystem with reliable file operations","Retry the sync once the filesystem condition is fixed; the lock is released cleanly on this failure path"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Check usable space before starting sync (statfs on Go 1.21+)\nvar st syscall.Statfs_t\nif err := syscall.Statfs(beadsDir, &st); err == nil {\n    freeBytes := int64(st.Bavail) * int64(st.Bsize)\n    if freeBytes < 1<<20 { return fmt.Errorf(\"less than 1MB free on %s\", beadsDir) }\n}","typeGuard":null,"tryCatchPattern":"lock, err := linear.AcquireSyncLock(beadsDir, true)\nif err != nil {\n    var pe *fs.PathError\n    if errors.As(err, &pe) {\n        return fmt.Errorf(\"lock file I/O problem at %s: %w\", pe.Path, err)\n    }\n    return err\n}","preventionTips":["Alert on low disk space for volumes hosting beads directories","Do not delete/replace the lock file while a holder has it open","Avoid NFS for the beads directory (attribute-cache inconsistencies)","Fix filesystem conditions and retry; the failed acquisition releases the flock cleanly"],"tags":["filesystem","disk-full","io","go"],"backgroundTag":"disk-full","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}