{"record":{"id":"c11e6c05b1796c3a","repo":"plandex-ai/plandex","slug":"operation-s-failed-after-d-attempts-v","errorCode":null,"errorMessage":"operation %s failed after %d attempts: %v","messagePattern":"operation (.+?) failed after (.+?) attempts: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/server/db/git.go","lineNumber":683,"sourceCode":"\t\terr = operation()\n\t\tif err == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\t// Check if error is retryable\n\t\tif strings.Contains(err.Error(), \"index.lock\") || strings.Contains(err.Error(), \"cannot lock ref\") {\n\t\t\tlog.Printf(\"Git lock file error detected for %s, will retry: %v\\n\", label, err)\n\t\t\terr = gitRemoveIndexLockFileIfExists(repoDir)\n\t\t\tif err != nil {\n\t\t\t\tlog.Printf(\"error removing lock files: %v\", err)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// Non-retryable error\n\t\treturn err\n\t}\n\treturn fmt.Errorf(\"operation %s failed after %d attempts: %v\", label, maxGitRetries, err)\n}\n\n// LogGitRepoState prints out useful debug info about the current git repository:\n//   - The currently checked-out branch\n//   - The last few commits\n//   - The status (untracked changes, etc.)\n//   - A directory listing of refs/heads\n//   - A directory listing of .git/ (to spot any leftover lock files or HEAD files)\nfunc (repo *GitRepo) LogGitRepoState() {\n\trepoDir := getPlanDir(repo.orgId, repo.planId)\n\n\tlog.Println(\"[DEBUG] --- Git Repo State ---\")\n\n\t// 1. Current branch\n\tout, err := exec.Command(\"git\", \"-C\", repoDir, \"branch\", \"--show-current\").CombinedOutput()\n\tif err != nil {\n\t\tlog.Printf(\"[DEBUG] error running `git branch --show-current`: %v, output: %s\", err, string(out))\n\t} else {","sourceCodeStart":665,"sourceCodeEnd":701,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/db/git.go#L665-L701","documentation":"gitWriteOperation wraps a write action against the on-disk git repository used by apps (add/commit, reset, checkout, branch create/delete). It retries transient git failures up to maxGitRetries; if every retry fails with a non-retryable error, or retries are exhausted, it surfaces this wrapped error naming the operation label, attempt count, and underlying cause. It indicates the local git repo is in a state the requested write cannot succeed against.","triggerScenarios":"Any of GitAddAndCommit, GitRewindToSha, GitResetToSha, GitCheckoutSha, GitCreateBranch, or GitDeleteBranch repeatedly failing: git index.lock held by another process, invalid/unknown sha passed to reset/checkout/rewind, branch already exists or does not exist, merge conflicts / dirty worktree blocking checkout, or git binary missing on PATH.","commonSituations":"Concurrent deploys or two server processes writing the same app repo (stale .git/index.lock), a client requesting rewind to a garbage/truncated sha, deleting a branch that a prior failed run already removed, container images without git installed, or repos left mid-merge after a crash.","solutions":["Read the wrapped %v cause; if it mentions index.lock, remove the stale lock: rm <repo>/.git/index.lock (after confirming no git process is running).","If the sha is invalid, verify the commit exists (git log / git cat-file -t <sha>) before calling GitRewindToSha/GitResetToSha/GitCheckoutSha; reject bad shas at the handler layer.","For branch errors, check the branch's existence first and make create/delete idempotent in your caller.","If the worktree is dirty or mid-merge, reset the app repo to a clean state (git status, abort merge, discard local changes) and retry the operation.","Verify the git binary is installed and on PATH in the server environment.","Log GitRepoState (LogGitRepoState) to capture branch, recent commits, and status before escalating."],"exampleFix":"// before: passing an unvalidated user-supplied sha\nerr := db.GitRewindToSha(app, userSha)\n\n// after: validate the sha resolves to a commit first\nif _, err := repo.ResolveRevision(plumbing.Revision(userSha)); err != nil {\n    return fmt.Errorf(\"invalid sha %q: %w\", userSha, err)\n}\nerr := db.GitRewindToSha(app, userSha)","handlingStrategy":"retry","validationCode":"if _, err := os.Stat(filepath.Join(repoPath, \".git\", \"index.lock\")); err == nil {\n    return fmt.Errorf(\"git repo locked: %s/.git/index.lock exists\", repoPath)\n}\nif _, err := exec.LookPath(\"git\"); err != nil {\n    return fmt.Errorf(\"git binary not found on PATH\")\n}","typeGuard":null,"tryCatchPattern":"if err := db.GitAddAndCommit(app, msg); err != nil {\n    var gerr *GitError\n    if errors.As(err, &gerr) && strings.Contains(gerr.Err.Error(), \"index.lock\") {\n        os.Remove(filepath.Join(app.RepoPath, \".git\", \"index.lock\"))\n        err = db.GitAddAndCommit(app, msg)\n    }\n    if err != nil {\n        logger.Error(\"git write failed\", \"err\", err)\n    }\n}","preventionTips":["Run only one server process per app repository to avoid concurrent git writes.","Validate shas and branch names at the API/handler layer before invoking git helpers.","Make branch create/delete calls idempotent in your caller.","Confirm git is installed in your container image and on PATH.","Monitor and alert on repeated occurrences; call LogGitRepoState for diagnostics."],"tags":["git","retry-exhausted","repository-state","concurrency"],"backgroundTag":"git-operation-failed-after-retries","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"}