plandex-ai/plandex · error

error removing lock file: %v

Error message

error removing lock file: %v

What it means

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.

Source

Thrown at app/server/db/locks.go:464

					if rowsAffected == 0 {
						log.Printf("[Lock][Heartbeat] %s | %s | Lock not found: %s | stopping heartbeat loop\n", planId, params.Reason, newLock.Id)
						return
					}

					log.Printf("[Lock][Heartbeat] %s | %s | Lock found: %s | continuing heartbeat loop\n", planId, params.Reason, newLock.Id)
				}
			}

		}
	}()

	// check if git lock file exists
	// remove it if so
	err = gitRemoveIndexLockFileIfExists(getPlanDir(orgId, planId))
	if err != nil {
		log.Printf("[Lock] %s | %s | Error removing lock file: %v", planId, params.Reason, err)
		return newLock.Id, fmt.Errorf("error removing lock file: %v", err)
	}

	if branch != "" {
		// checkout the branch
		err = gitCheckoutBranch(getPlanDir(orgId, planId), branch)
		if err != nil {
			log.Printf("[Lock] %s | %s | Error checking out branch: %v", planId, params.Reason, err)
			return newLock.Id, fmt.Errorf("error checking out branch: %v", err)
		}
		log.Printf("[Lock] %s | %s | Checked out branch", planId, params.Reason)
	}

	return newLock.Id, nil
}

func deleteRepoLockDB(id, planId, reason string, numRetry int) error {
	start := time.Now()
	goroutineID := getGoroutineID()

View on GitHub (pinned to e2d772072e)

Solutions

  1. Check the wrapped cause and that getPlanDir(orgId, planId) exists and is a writable git repository
  2. Fix filesystem permissions/ownership on the plan directory for the process user
  3. Re-clone or re-initialize the plan directory if it is missing or corrupt
  4. Note the lock id is still returned — release the lock (deleteRepoLockDB) if you abort because of this error

Example fix

// before
id, err := lockRepoDB(ctx, orgId, planId, reason)
if err != nil { return err }
// after
id, err := lockRepoDB(ctx, orgId, planId, reason)
if err != nil {
    if strings.Contains(err.Error(), "error removing lock file") && id != "" {
        deleteRepoLockDB(id, planId, "abort after git lock cleanup failure", 0)
    }
    return err
}
Defensive patterns

Strategy: validation

Validate before calling

planDir := getPlanDir(orgId, planId)
if fi, err := os.Stat(filepath.Join(planDir, ".git")); err != nil || !fi.IsDir() {
    return fmt.Errorf("plan dir %s is not a git repo", planDir)
}
if err := syscall.Access(planDir, os.O_RDWR); err != nil {
    return fmt.Errorf("plan dir %s not writable: %w", planDir, err)
}

Type guard

func planDirReady(dir string) bool {
    fi, err := os.Stat(filepath.Join(dir, ".git"))
    return err == nil && fi.IsDir()
}

Try / catch

id, err := lockRepoDB(ctx, orgId, planId, reason)
if err != nil {
    if strings.Contains(err.Error(), "error removing lock file") && id != "" {
        _ = deleteRepoLockDB(id, planId, "git lock cleanup failed", 0)
    }
    return err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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