{"record":{"id":"94501c435d6ef149","repo":"plandex-ai/plandex","slug":"error-removing-lock-files-v","errorCode":null,"errorMessage":"error removing lock files: %v","messagePattern":"error removing lock files: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/server/db/git.go","lineNumber":641,"sourceCode":"\t\t\t}()\n\t\t\tif err := removeLockFile(path); err != nil {\n\t\t\t\terrCh <- err\n\t\t\t\treturn\n\t\t\t}\n\t\t\terrCh <- nil\n\t\t}(path)\n\t}\n\n\terrs := []error{}\n\tfor i := 0; i < len(paths); i++ {\n\t\terr := <-errCh\n\t\tif err != nil {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\n\tif len(errs) > 0 {\n\t\treturn fmt.Errorf(\"error removing lock files: %v\", errs)\n\t}\n\n\treturn nil\n}\n\nfunc setGitConfig(repoDir, key, value string) error {\n\tres, err := exec.Command(\"git\", \"-C\", repoDir, \"config\", key, value).CombinedOutput()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error setting git config %s to %s for dir: %s, err: %v, output: %s\", key, value, repoDir, err, string(res))\n\t}\n\treturn nil\n}\n\nfunc gitWriteOperation(operation func() error, repoDir, label string) error {\n\tlog.Printf(\"[Git] gitWriteOperation - label: %s\", label)\n\tvar err error\n\tfor attempt := 0; attempt < maxGitRetries; attempt++ {\n\t\tif attempt > 0 {","sourceCodeStart":623,"sourceCodeEnd":659,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/db/git.go#L623-L659","documentation":"gitRemoveIndexLockFileIfExists removes up to three git lock files (.git/index.lock, .git/refs/heads/HEAD.lock, .git/HEAD.lock) concurrently and collects every per-goroutine error. If any removal failed (see errors 375/376/377), it aggregates them into this single wrapped error. Callers are gitAdd, gitCommit, gitCheckoutBranch, gitWriteOperation, and lockRepoDB — so a failed lock cleanup blocks the subsequent git write operation.","triggerScenarios":"Any gitAdd/gitCommit/gitCheckoutBranch/gitWriteOperation/lockRepoDB call where at least one lock-file goroutine returned an error: removal failure (permissions), stat failure (traversal denied), >10 removal attempts without success, or a recovered panic.","commonSituations":"Lock files left behind by a crashed git process combined with wrong .git ownership; repo on a full or failing disk; another long-running git process re-creating locks while this function retries (11 failed attempts); multiple app instances writing the same repo concurrently.","solutions":["Read the aggregated inner errors to see which path failed and why (permission vs retry-exhausted vs panic).","Fix the root cause from the inner error: chmod/chown .git, free disk space, or kill the competing git process.","Ensure only one writer operates on the repo — use lockRepoDB/advisory locking so concurrent app instances don't fight over locks.","If a stale lock persists, remove it manually once the owning process is confirmed dead, then retry the operation."],"exampleFix":"// before\nif err := lockRepoDB(); err != nil {\n    return err // opaque aggregate\n}\n// after: log and verify before writing\nif err := lockRepoDB(); err != nil {\n    log.Printf(\"lock cleanup failed: %v; checking for live git processes\", err)\n    // exec.Command(\"pgrep\", \"git\")... abort if a git process is active\n    return fmt.Errorf(\"git write aborted, lock cleanup failed: %w\", err)\n}","handlingStrategy":"retry","validationCode":"for _, p := range []string{\"index.lock\", \"HEAD.lock\", filepath.Join(\"refs\", \"heads\", \"HEAD.lock\")} {\n    lp := filepath.Join(repoDir, \".git\", p)\n    if _, err := os.Stat(lp); err == nil {\n        if syscall.Access(filepath.Dir(lp), syscall.W_OK) != nil {\n            return fmt.Errorf(\"pre-check: cannot remove %s (permissions)\", lp)\n        }\n    }\n}","typeGuard":"func isLockCleanupErr(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"error removing lock files:\")\n}","tryCatchPattern":"var lastErr error\nfor i := 0; i < 3; i++ {\n    if err := gitWriteOperation(op, repoDir, \"commit\"); err != nil {\n        if isLockCleanupErr(err) {\n            time.Sleep(time.Duration(1<<i) * 200 * time.Millisecond)\n            lastErr = err\n            continue\n        }\n        return err\n    }\n    return nil\n}\nreturn lastErr","preventionTips":["Serialize all git writes on a repo through lockRepoDB so only one process manipulates locks.","Alert on this error: it usually means a crashed git process left stale locks that need manual cleanup.","Confirm no cron/CI job runs raw git commands on the same repo concurrently.","Monitor disk space; full disks cause cascading lock removal failures."],"tags":["go","git","concurrency","lock-file","error-aggregation"],"backgroundTag":"git-index-lock-stuck","analyzedSha":"e2d772072efadbe41d2946d97d79be55532dbab5","analyzedAt":"2026-09-05T20:56:53.631Z","contentChangedAt":"2026-09-05T20:56:53.631Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}