plandex-ai/plandex · error
operation %s failed after %d attempts: %v
Error message
operation %s failed after %d attempts: %v
What it means
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.
Source
Thrown at app/server/db/git.go:683
err = operation()
if err == nil {
return nil
}
// Check if error is retryable
if strings.Contains(err.Error(), "index.lock") || strings.Contains(err.Error(), "cannot lock ref") {
log.Printf("Git lock file error detected for %s, will retry: %v\n", label, err)
err = gitRemoveIndexLockFileIfExists(repoDir)
if err != nil {
log.Printf("error removing lock files: %v", err)
}
continue
}
// Non-retryable error
return err
}
return fmt.Errorf("operation %s failed after %d attempts: %v", label, maxGitRetries, err)
}
// LogGitRepoState prints out useful debug info about the current git repository:
// - The currently checked-out branch
// - The last few commits
// - The status (untracked changes, etc.)
// - A directory listing of refs/heads
// - A directory listing of .git/ (to spot any leftover lock files or HEAD files)
func (repo *GitRepo) LogGitRepoState() {
repoDir := getPlanDir(repo.orgId, repo.planId)
log.Println("[DEBUG] --- Git Repo State ---")
// 1. Current branch
out, err := exec.Command("git", "-C", repoDir, "branch", "--show-current").CombinedOutput()
if err != nil {
log.Printf("[DEBUG] error running `git branch --show-current`: %v, output: %s", err, string(out))
} else {View on GitHub (pinned to e2d772072e)
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.
Example fix
// before: passing an unvalidated user-supplied sha
err := db.GitRewindToSha(app, userSha)
// after: validate the sha resolves to a commit first
if _, err := repo.ResolveRevision(plumbing.Revision(userSha)); err != nil {
return fmt.Errorf("invalid sha %q: %w", userSha, err)
}
err := db.GitRewindToSha(app, userSha) Defensive patterns
Strategy: retry
Validate before calling
if _, err := os.Stat(filepath.Join(repoPath, ".git", "index.lock")); err == nil {
return fmt.Errorf("git repo locked: %s/.git/index.lock exists", repoPath)
}
if _, err := exec.LookPath("git"); err != nil {
return fmt.Errorf("git binary not found on PATH")
} Try / catch
if err := db.GitAddAndCommit(app, msg); err != nil {
var gerr *GitError
if errors.As(err, &gerr) && strings.Contains(gerr.Err.Error(), "index.lock") {
os.Remove(filepath.Join(app.RepoPath, ".git", "index.lock"))
err = db.GitAddAndCommit(app, msg)
}
if err != nil {
logger.Error("git write failed", "err", err)
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- error committing convo message: %v
- panic in gitRemoveIndexLockFileIfExists: %v %s
- error removing lock files: %v
- error getting git root: %s
- error getting git status: %s
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/c11e6c05b1796c3a.
Report an issue: GitHub.