charmbracelet/crush · error
acquire config lock: %w
Error message
acquire config lock: %w
What it means
lockConfig takes a cross-process advisory flock (path+'.lock') with a bounded deadline (configLockDeadline). This error wraps a failure to acquire that lock within the deadline or an OS-level flock error — another process holds the config lock, or the lock file cannot be created/locked.
Source
Thrown at internal/config/store.go:287
// as soon as the file access is complete — no I/O should be performed
// while the lock is held.
func (s *ConfigStore) lockConfig(scope Scope) (func(), error) {
s.mu.Lock()
path, err := s.configPath(scope)
if err != nil {
s.mu.Unlock()
return nil, err
}
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
s.mu.Unlock()
return nil, fmt.Errorf("create config directory: %w", err)
}
ctx, cancel := context.WithTimeout(context.Background(), configLockDeadline)
defer cancel()
release, err := lock.File(ctx, path+".lock")
if err != nil {
s.mu.Unlock()
return nil, fmt.Errorf("acquire config lock: %w", err)
}
return func() {
release()
s.mu.Unlock()
}, nil
}
// atomicWrite handles the lock-read-transform-write-unlock cycle for
// config file mutations. The fn callback receives the current file
// contents (raw bytes, or {} if the file is missing) and must return the
// new contents. fn must be pure — no I/O, no network calls.
func (s *ConfigStore) atomicWrite(scope Scope, fn func(current []byte) ([]byte, error)) error {
unlock, err := s.lockConfig(scope)
if err != nil {
return err
}
defer unlock()
View on GitHub (pinned to 7944b8e522)
Solutions
- Wait a moment and retry — the deadline means another process briefly held the lock
- Check for other running crush processes (pgrep) and stop the conflicting one
- Move config to a local filesystem if it resides on NFS/SMB without working flock
- Remove a genuinely stale .lock file only after confirming no process holds it
Example fix
// before
err := store.SetConfigField(scope, key, val) // hard failure on lock contention
// after
for i := 0; i < 3; i++ {
err := store.SetConfigField(scope, key, val)
if err == nil || !strings.Contains(err.Error(), "acquire config lock") {
break
}
time.Sleep(500 * time.Millisecond) // retry on transient lock contention
} Defensive patterns
Strategy: retry
Validate before calling
if _, err := os.Stat(configPath + ".lock"); err == nil {
// lock exists; check for a live holder before retrying
}
if lsofHoldsLock(configPath+".lock") {
return errors.New("another crush process is writing config; wait and retry")
} Type guard
func isLockTimeout(err error) bool {
return err != nil && strings.Contains(err.Error(), "acquire config lock") && errors.Is(err, context.DeadlineExceeded)
} Try / catch
var lastErr error
for i := 0; i < 5; i++ {
err := store.SetConfigField(scope, key, val)
if err == nil || !isLockTimeout(err) {
return err
}
lastErr = err
time.Sleep(time.Duration(1<<i) * 100 * time.Millisecond)
}
return lastErr Prevention
- Avoid running multiple crush instances that mutate config simultaneously
- Keep the config on a local filesystem with working flock support (not NFS/SMB)
- Kill -9'd processes release flock on exit, but check for wedged holders with lsof/fuser
- Keep writes short; never do network I/O while holding the config lock
When it happens
Trigger: Running two crush instances (or a crashed process leaking a stale lock) that concurrently call atomicWrite; the lock file lives on a filesystem without flock support (some network mounts); the deadline expires while waiting for a peer's long-held lock.
Common situations: Two crush sessions editing config simultaneously; an editor/script with a long-running config operation; NFS/SMB mounts where flock fails; leftover .lock file from a killed process (usually harmless — flock releases on fd close, but broken fs semantics can stall).
Related errors
- open lock file %q: %w
- flock: %w
- LockFileEx: %w
- server is hosting live workspaces
- file lock is held by another process
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/e03c12530e51ee81.
Report an issue: GitHub.