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
- 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
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
- 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
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
- error getting git root: %s
- error getting files in git repo: %s
- error storing context: %v
- error initializing git repo: %v
- error removing lock file: %v
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/b294175240ebbe77.
Report an issue: GitHub.