gastownhall/beads · error

truncating lock file: %w

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.

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)

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.