gastownhall/beads · error

truncating lock file

Error message

truncating lock file: %w

What it means

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.

Solutions

  1. Check and free disk space on the filesystem holding the beads directory
  2. Ensure no external tooling deletes/recreates files in the beads directory during sync
  3. Verify the beads directory is on a local filesystem with reliable file operations
  4. Retry the sync once the filesystem condition is fixed; the lock is released cleanly on this failure path
Defensive patterns

Strategy: validation

Validate before calling

// Check usable space before starting sync (statfs on Go 1.21+)
var st syscall.Statfs_t
if err := syscall.Statfs(beadsDir, &st); err == nil {
    freeBytes := int64(st.Bavail) * int64(st.Bsize)
    if freeBytes < 1<<20 { return fmt.Errorf("less than 1MB free on %s", beadsDir) }
}

Try / catch

lock, err := linear.AcquireSyncLock(beadsDir, true)
if err != nil {
    var pe *fs.PathError
    if errors.As(err, &pe) {
        return fmt.Errorf("lock file I/O problem at %s: %w", pe.Path, err)
    }
    return err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/d78a8e6b56db131e. Report an issue: GitHub.

Appendix: source

Thrown at internal/linear/synclock.go:121

	return metadataErr
}

// SyncLockHeldError is returned when the lock is held by another process.
type SyncLockHeldError struct {
	Info *SyncLockInfo
}

func (e *SyncLockHeldError) Error() string {
	if e.Info != nil {
		return fmt.Sprintf("another bd linear sync is already running (PID %d, started %s)",
			e.Info.PID, e.Info.Started.Format(time.RFC3339))
	}
	return "another bd linear sync is already running"
}

func writeLockInfo(f *os.File) error {
	if err := f.Truncate(0); err != nil {
		return fmt.Errorf("truncating lock file: %w", err)
	}
	if _, err := f.Seek(0, 0); err != nil {
		return err
	}
	info := fmt.Sprintf("pid=%d\nstarted=%s\n", os.Getpid(), time.Now().UTC().Format(time.RFC3339))
	_, err := f.WriteString(info)
	return err
}

func readLockInfo(path string) *SyncLockInfo {
	data, err := os.ReadFile(path) // #nosec G304 -- path is constructed from beadsDir, not user input
	if err != nil {
		return nil
	}
	return parseLockInfo(string(data))
}

func parseLockInfo(content string) *SyncLockInfo {

View on GitHub (pinned to 71377f2769)