{"record":{"id":"35e08ba98a5c058c","repo":"charmbracelet/crush","slug":"file-lock-is-held-by-another-process","errorCode":null,"errorMessage":"file lock is held by another process","messagePattern":"file lock is held by another process","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/lock/lock.go","lineNumber":30,"sourceCode":"// The lock file at path is created if it does not exist. It is never\n// unlinked — flock is keyed by inode, not path, and unlinking could\n// create a window where two processes lock different inodes at the\n// same path.\n//\n// This is the canonical file-locking helper for Crush. Callers should\n// prefer it over rolling their own platform-specific code.\npackage lock\n\nimport (\n\t\"context\"\n\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}","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/lock/lock.go#L12-L48","documentation":"lock.ErrContended is returned by lock.TryFile when an advisory file lock is already held by another process. Unlike lock.File, which blocks until the lock is free or the context is cancelled, TryFile is the non-blocking variant: if the lock is contended it fails fast with this sentinel so callers can decide to skip or retry.","triggerScenarios":"Two crush processes starting simultaneously and both calling acquireDataDirLock/TryFile on the same data-dir lock file; TryFile against a lock held by a still-running previous instance.","commonSituations":"Launching a second crush instance while one is already running against the same project data dir; leftover processes after a crash still holding the fd; shared home/data directories across containers pointing at the same lock file.","solutions":["Exit the other running crush process that holds the data-dir lock, then retry.","Use lock.File (blocking, context-bounded) instead of TryFile if waiting is acceptable.","Point each concurrent instance at a different data directory, or verify no orphaned process still holds the lock (lsof on the lock file)."],"exampleFix":"// before\nrelease, err := lock.TryFile(path)\nif err != nil {\n    return err // hard fail on contention\n}\n// after\nrelease, err := lock.TryFile(path)\nif errors.Is(err, lock.ErrContended) {\n    return nil // another instance owns the lock; skip gracefully\n}\nif err != nil {\n    return err\n}","handlingStrategy":"type-guard","validationCode":"// Check whether another crush instance is running before acquiring\nif _, err := os.Stat(lockPath); err == nil {\n    if holders := processesHolding(lockPath); len(holders) > 0 { /* another instance alive */ }\n}","typeGuard":"func isLockContended(err error) bool { return errors.Is(err, lock.ErrContended) }","tryCatchPattern":"release, err := lock.TryFile(path)\nif errors.Is(err, lock.ErrContended) {\n    return nil // or retry with backoff / fall back to lock.File\n}\nif err != nil {\n    return err\n}\ndefer release()","preventionTips":["Run only one crush instance per data directory, or isolate data dirs per instance.","Check for orphaned processes holding the lock (lsof the lock file) after crashes.","Use errors.Is with ErrContended to branch, and lock.File with a deadline when waiting is acceptable."],"tags":["go","file-lock","concurrency","single-instance"],"backgroundTag":"file-lock-contention","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}