gastownhall/beads · error

failed to create lock file: %w

Error message

failed to create lock file: %w

What it means

acquireBootstrapLock creates a bootstrap lock file (mode 0600) with os.OpenFile before trying to flock it. This error means the lock file itself could not be created/opened — the process never even got to the locking stage. It wraps the underlying OS error.

Source

Thrown at internal/storage/dolt/bootstrap.go:230

// Uses non-blocking flock with polling to respect the timeout deadline.
// Detects and cleans up stale lock files from crashed processes.
func acquireBootstrapLock(lockPath string, timeout time.Duration) (*os.File, error) {
	// Check for stale lock file before attempting to acquire.
	// If the lock file is very old, the holding process likely crashed
	// without cleanup. Remove it so we can proceed.
	if info, err := os.Stat(lockPath); err == nil {
		age := time.Since(info.ModTime())
		if age > staleLockAge {
			fmt.Fprintf(os.Stderr, "Bootstrap: removing stale lock file (age: %s)\n", age.Round(time.Second))
			_ = os.Remove(lockPath) // Best effort cleanup of lock file
		}
	}

	// Create lock file
	// #nosec G304 - controlled path
	f, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0600)
	if err != nil {
		return nil, fmt.Errorf("failed to create lock file: %w", err)
	}

	// Try to acquire lock with non-blocking flock and polling.
	deadline := time.Now().Add(timeout)
	for {
		err := lockfile.FlockExclusiveNonBlocking(f)
		if err == nil {
			// Lock acquired - update modification time for stale detection
			return f, nil
		}

		if !lockfile.IsLocked(err) {
			// Unexpected error (not contention)
			_ = f.Close() // Best effort cleanup on error path
			return nil, fmt.Errorf("failed to acquire bootstrap lock: %w", err)
		}

		if time.Now().After(deadline) {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check that the lock file's parent directory exists and is writable (e.g. `ls -la .beads/` and `touch .beads/test` as the same user)
  2. Fix permissions: `chmod u+w .beads` or run as a user with write access to the repository
  3. Check for read-only filesystem or full disk (`mount`, `df -h`) and free space or remount read-write

Example fix

// before
bd bootstrap
// error: failed to create lock file: open .beads/bootstrap.lock: permission denied
// after
mkdir -p .beads && chmod u+w .beads
bd bootstrap
Defensive patterns

Strategy: validation

Validate before calling

// ensure lock dir exists and is writable before bootstrap
info, err := os.Stat(beadsDir)
if err != nil || !info.IsDir() { os.MkdirAll(beadsDir, 0o755) }
if f, err := os.CreateTemp(beadsDir, ".writetest"); err != nil {
    // directory not writable: fix permissions before bootstrap
} else { f.Close(); os.Remove(f.Name()) }

Prevention

When it happens

Trigger: os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0600) fails: the `.beads` directory does not exist, the filesystem is read-only, permission denied on the lock path, disk full, or the path is invalid (e.g. wrong working directory).

Common situations: Running `bd bootstrap` in a directory where `.beads` has not been created or is not writable; running under a user without write permission to the repo; a read-only container/mount or full disk.

Related errors


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