charmbracelet/crush · error

open lock file %q: %w

Error message

open lock file %q: %w

What it means

lock.File opens (creating if necessary) the lock file with os.OpenFile(O_RDWR|O_CREATE, 0600) before attempting to flock it. This error wraps a failure of that open, meaning the lock could not even be created or opened for reading/writing. It is returned before any locking is attempted.

Source

Thrown at internal/lock/lock.go:41

	"errors"
	"fmt"
	"os"
)

// ErrContended is returned by TryFile when the lock is already held by
// another process.
var ErrContended = errors.New("file lock is held by another process")

// File acquires an exclusive advisory lock on the file at path, blocking
// until the lock is acquired or ctx is cancelled. It returns a release
// function that drops the lock and closes the underlying file descriptor.
//
// Pass a context with a deadline (e.g. context.WithTimeout) to bound the
// wait. Pass context.Background() to block indefinitely.
func File(ctx context.Context, path string) (func(), error) {
	f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0o600)
	if err != nil {
		return nil, fmt.Errorf("open lock file %q: %w", path, err)
	}

	release, err := lockFile(ctx, f)
	if err != nil {
		f.Close()
		return nil, err
	}

	return func() {
		release()
		f.Close()
	}, nil
}

// TryFile is like File but returns ErrContended immediately if the lock
// is already held by another process. Use this when you want to fail
// fast rather than wait.
func TryFile(path string) (func(), error) {

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Ensure the parent directory of the lock path exists before calling lock.File (os.MkdirAll on the data dir).
  2. Check ownership/permissions of the existing lock file (0600 owned by the same user) and remove a stale file with wrong ownership.
  3. Verify path points to a file, not a directory, and the filesystem is writable.
  4. If a stale lock from a crashed process has wrong permissions, delete it: rm <path> and retry.

Example fix

// before
release, err := lock.File(ctx, filepath.Join(dataDir, "crush.lock"))
// after
if err := os.MkdirAll(dataDir, 0o755); err != nil {
    return fmt.Errorf("prepare data dir: %w", err)
}
release, err := lock.File(ctx, filepath.Join(dataDir, "crush.lock"))
if err != nil {
    return fmt.Errorf("acquire data-dir lock: %w", err)
}
Defensive patterns

Strategy: validation

Validate before calling

lockDir := filepath.Dir(path)
if info, err := os.Stat(path); err == nil && info.IsDir() {
    return fmt.Errorf("%s is a directory, expected lock file", path)
}
if err := os.MkdirAll(lockDir, 0o755); err != nil {
    return fmt.Errorf("cannot create lock dir: %w", err)
}

Try / catch

release, err := lock.File(ctx, path)
if err != nil {
    var perr *fs.PathError
    if errors.As(err, &perr) {
        return fmt.Errorf("lock open failed (%s): %w", perr.Op, err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling lock.File(ctx, path) where the parent directory of path does not exist, the path is a directory, or the process lacks write permission on an existing lock file or its directory.

Common situations: Data directory deleted while the app runs or never created before locking; running with a different (unprivileged) user than the one that created the lock file; a directory placed where the lock file is expected; read-only filesystem.

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/a6340d9e1a9a11c9. Report an issue: GitHub.