{"record":{"id":"bb1582cdbda1d471","repo":"twpayne/chezmoi","slug":"open-s-w","errorCode":null,"errorMessage":"open %s: %w","messagePattern":"open (.+?): %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/chezmoi/boltpersistentstate.go","lineNumber":238,"sourceCode":"\t\t}\n\t\treturn b.Put(key, value)\n\t})\n}\n\n// open opens b's database if it is not already open, creating it if needed.\nfunc (b *BoltPersistentState) open() error {\n\tif b.db != nil {\n\t\treturn nil\n\t}\n\tif err := MkdirAll(b.system, b.path.Dir(), fs.ModePerm); err != nil {\n\t\treturn err\n\t}\n\tswitch db, err := bbolt.Open(b.path.String(), 0o600, &b.options); {\n\tcase errors.Is(err, syscall.EINVAL):\n\t\t// Assume that any EINVAL error is because flock(2) failed.\n\t\treturn fmt.Errorf(\"open %s: failed to acquire lock: %w\", b.path, err)\n\tcase err != nil:\n\t\treturn fmt.Errorf(\"open %s: %w\", b.path, err)\n\tdefault:\n\t\tb.empty = false\n\t\tb.db = db\n\t\treturn nil\n\t}\n}\n","sourceCodeStart":220,"sourceCodeEnd":245,"githubUrl":"https://github.com/twpayne/chezmoi/blob/f901167e4685db90da56d6a2a19df642cb3e0247/internal/chezmoi/boltpersistentstate.go#L220-L245","documentation":"chezmoi's persistent state is a bbolt database file. When bbolt.Open fails with EINVAL, the code assumes flock(2) failed (the file is already locked by another process); other errors are wrapped with the state file path so the developer knows which file could not be opened. This prevents silent corruption from two processes writing the state concurrently.","triggerScenarios":"Calling CopyTo, Data, Delete, DeleteBucket, ForEach, or Get on BoltPersistentState while another chezmoi process (or a stale process) holds the flock on the state file, or when the file/disk rejects the open (e.g. bad path on unsupported filesystem).","commonSituations":"Two chezmoi runs in parallel (cron + manual run, IDE integration + shell), a hung previous run still holding the lock, state file on a network filesystem that does not support flock, or a corrupted/unwritable state path.","solutions":["Find and wait for/kill the other chezmoi process holding the lock (lsof/fuser on the state file, typically ~/.config/chezmoi/chezmoistate.boltdb)","Remove a stale lock by ensuring no chezmoi process is running, then retry","Move the state file off network filesystems that lack flock support","If the file is corrupt and no process holds it, back it up and delete it so chezmoi recreates it"],"exampleFix":"// before: parallel runs collide\nchezmoi apply & chezmoi apply &\n\n// after: serialize runs\nflock /tmp/chezmoi.lock -c 'chezmoi apply'","handlingStrategy":"retry","validationCode":"// before operating on state, check no other holder\nout, _ := exec.Command(\"fuser\", statePath.String()).Output()\nif len(bytes.TrimSpace(out)) > 0 { /* another process holds the lock */ }","typeGuard":null,"tryCatchPattern":"if err := ps.Delete(key); err != nil {\n    if strings.Contains(err.Error(), \"failed to acquire lock\") {\n        time.Sleep(retryBackoff) // then retry\n    }\n}","preventionTips":["Never run two chezmoi commands concurrently; use flock or a job scheduler to serialize","Check for stuck chezmoi processes with pgrep before long operations","Keep the state file on a local filesystem with flock support"],"tags":["go","file-lock","boltdb","concurrency"],"backgroundTag":"file-lock-contention","analyzedSha":"f901167e4685db90da56d6a2a19df642cb3e0247","analyzedAt":"2026-09-01T18:16:41.508Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T01:17:15.007Z"}