{"record":{"id":"b294175240ebbe77","repo":"plandex-ai/plandex","slug":"error-removing-lock-file-v-b29417","errorCode":null,"errorMessage":"error removing lock file: %v","messagePattern":"error removing lock file: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/server/db/locks.go","lineNumber":464,"sourceCode":"\n\t\t\t\t\tif rowsAffected == 0 {\n\t\t\t\t\t\tlog.Printf(\"[Lock][Heartbeat] %s | %s | Lock not found: %s | stopping heartbeat loop\\n\", planId, params.Reason, newLock.Id)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tlog.Printf(\"[Lock][Heartbeat] %s | %s | Lock found: %s | continuing heartbeat loop\\n\", planId, params.Reason, newLock.Id)\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\t}()\n\n\t// check if git lock file exists\n\t// remove it if so\n\terr = gitRemoveIndexLockFileIfExists(getPlanDir(orgId, planId))\n\tif err != nil {\n\t\tlog.Printf(\"[Lock] %s | %s | Error removing lock file: %v\", planId, params.Reason, err)\n\t\treturn newLock.Id, fmt.Errorf(\"error removing lock file: %v\", err)\n\t}\n\n\tif branch != \"\" {\n\t\t// checkout the branch\n\t\terr = gitCheckoutBranch(getPlanDir(orgId, planId), branch)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"[Lock] %s | %s | Error checking out branch: %v\", planId, params.Reason, err)\n\t\t\treturn newLock.Id, fmt.Errorf(\"error checking out branch: %v\", err)\n\t\t}\n\t\tlog.Printf(\"[Lock] %s | %s | Checked out branch\", planId, params.Reason)\n\t}\n\n\treturn newLock.Id, nil\n}\n\nfunc deleteRepoLockDB(id, planId, reason string, numRetry int) error {\n\tstart := time.Now()\n\tgoroutineID := getGoroutineID()","sourceCodeStart":446,"sourceCodeEnd":482,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/db/locks.go#L446-L482","documentation":"After the DB lock is committed, lockRepoDB cleans stale git index lock files (.git/index.lock) in the plan directory via gitRemoveIndexLockFileIfExists; on failure it returns 'error removing lock file'. The DB lock IS held at this point (the lock id is returned alongside the error), but the git working dir may still be locked.","triggerScenarios":"gitRemoveIndexLockFileIfExists fails because the plan directory does not exist or is not a git repo, filesystem permissions deny deletion, or the file is held/remounted read-only on the host.","commonSituations":"Fresh worker node where getPlanDir was never cloned; NFS/EFS volume remounted read-only; container running as non-root user after a chown; plan directory deleted by a cleanup job mid-acquire.","solutions":["Check the wrapped cause and that getPlanDir(orgId, planId) exists and is a writable git repository","Fix filesystem permissions/ownership on the plan directory for the process user","Re-clone or re-initialize the plan directory if it is missing or corrupt","Note the lock id is still returned — release the lock (deleteRepoLockDB) if you abort because of this error"],"exampleFix":"// before\nid, err := lockRepoDB(ctx, orgId, planId, reason)\nif err != nil { return err }\n// after\nid, err := lockRepoDB(ctx, orgId, planId, reason)\nif err != nil {\n    if strings.Contains(err.Error(), \"error removing lock file\") && id != \"\" {\n        deleteRepoLockDB(id, planId, \"abort after git lock cleanup failure\", 0)\n    }\n    return err\n}","handlingStrategy":"validation","validationCode":"planDir := getPlanDir(orgId, planId)\nif fi, err := os.Stat(filepath.Join(planDir, \".git\")); err != nil || !fi.IsDir() {\n    return fmt.Errorf(\"plan dir %s is not a git repo\", planDir)\n}\nif err := syscall.Access(planDir, os.O_RDWR); err != nil {\n    return fmt.Errorf(\"plan dir %s not writable: %w\", planDir, err)\n}","typeGuard":"func planDirReady(dir string) bool {\n    fi, err := os.Stat(filepath.Join(dir, \".git\"))\n    return err == nil && fi.IsDir()\n}","tryCatchPattern":"id, err := lockRepoDB(ctx, orgId, planId, reason)\nif err != nil {\n    if strings.Contains(err.Error(), \"error removing lock file\") && id != \"\" {\n        _ = deleteRepoLockDB(id, planId, \"git lock cleanup failed\", 0)\n    }\n    return err\n}","preventionTips":["Ensure the plan dir is cloned/initialized before locking","Run the process as a user with write access to the plan directory","Avoid read-only mounts (NFS remounts) for plan working dirs","Remember the DB lock id is returned with the error — release it on abort"],"tags":["git","filesystem","locking"],"backgroundTag":"git-index-lock","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"}