{"record":{"id":"a6340d9e1a9a11c9","repo":"charmbracelet/crush","slug":"open-lock-file-q-w","errorCode":null,"errorMessage":"open lock file %q: %w","messagePattern":"open lock file %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/lock/lock.go","lineNumber":41,"sourceCode":"\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n)\n\n// ErrContended is returned by TryFile when the lock is already held by\n// another process.\nvar ErrContended = errors.New(\"file lock is held by another process\")\n\n// File acquires an exclusive advisory lock on the file at path, blocking\n// until the lock is acquired or ctx is cancelled. It returns a release\n// function that drops the lock and closes the underlying file descriptor.\n//\n// Pass a context with a deadline (e.g. context.WithTimeout) to bound the\n// wait. Pass context.Background() to block indefinitely.\nfunc File(ctx context.Context, path string) (func(), error) {\n\tf, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0o600)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"open lock file %q: %w\", path, err)\n\t}\n\n\trelease, err := lockFile(ctx, f)\n\tif err != nil {\n\t\tf.Close()\n\t\treturn nil, err\n\t}\n\n\treturn func() {\n\t\trelease()\n\t\tf.Close()\n\t}, nil\n}\n\n// TryFile is like File but returns ErrContended immediately if the lock\n// is already held by another process. Use this when you want to fail\n// fast rather than wait.\nfunc TryFile(path string) (func(), error) {","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/lock/lock.go#L23-L59","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure the parent directory of the lock path exists before calling lock.File (os.MkdirAll on the data dir).","Check ownership/permissions of the existing lock file (0600 owned by the same user) and remove a stale file with wrong ownership.","Verify path points to a file, not a directory, and the filesystem is writable.","If a stale lock from a crashed process has wrong permissions, delete it: rm <path> and retry."],"exampleFix":"// before\nrelease, err := lock.File(ctx, filepath.Join(dataDir, \"crush.lock\"))\n// after\nif err := os.MkdirAll(dataDir, 0o755); err != nil {\n    return fmt.Errorf(\"prepare data dir: %w\", err)\n}\nrelease, err := lock.File(ctx, filepath.Join(dataDir, \"crush.lock\"))\nif err != nil {\n    return fmt.Errorf(\"acquire data-dir lock: %w\", err)\n}","handlingStrategy":"validation","validationCode":"lockDir := filepath.Dir(path)\nif info, err := os.Stat(path); err == nil && info.IsDir() {\n    return fmt.Errorf(\"%s is a directory, expected lock file\", path)\n}\nif err := os.MkdirAll(lockDir, 0o755); err != nil {\n    return fmt.Errorf(\"cannot create lock dir: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"release, err := lock.File(ctx, path)\nif err != nil {\n    var perr *fs.PathError\n    if errors.As(err, &perr) {\n        return fmt.Errorf(\"lock open failed (%s): %w\", perr.Op, err)\n    }\n    return err\n}","preventionTips":["Always create the data directory before attempting to lock.","Run the app as the same user that owns the data directory.","Avoid placing lock files on read-only or network filesystems."],"tags":["filesystem","locking","permissions"],"backgroundTag":"lock-file-open-failed","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}