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

  1. 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).
  2. 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.
  3. For branch errors, check the branch's existence first and make create/delete idempotent in your caller.
  4. 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.
  5. Verify the git binary is installed and on PATH in the server environment.
  6. 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

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


AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05). Data as JSON: /api/errors/c11e6c05b1796c3a. Report an issue: GitHub.