plandex-ai/plandex · error
error iterating over repo locks: %v
Error message
error iterating over repo locks: %v
What it means
After iterating repoLockRows, lockRepoDB checks rows.Err() for deferred errors from the driver during iteration (connection drop mid-result-set, query cancellation, driver-level failure). If set, it logs and returns "error iterating over repo locks: %v", failing the lock attempt instead of acting on a partial lock list.
Source
Thrown at app/server/db/locks.go:226
now := time.Now()
for repoLockRows.Next() {
var lock repoLock
if err := repoLockRows.Scan(&lock.Id, &lock.OrgId, &lock.UserId, &lock.PlanId, &lock.PlanBuildId, &lock.Scope, &lock.Branch, &lock.LastHeartbeatAt, &lock.CreatedAt); err != nil {
return "", fmt.Errorf("error scanning repo lock: %v", err)
}
// ensure heartbeat hasn't timed out
if now.Sub(lock.LastHeartbeatAt) < lockHeartbeatTimeout {
locks = append(locks, &lock)
} else {
expiredLockIds = append(expiredLockIds, lock.Id)
expiredLockIdsSet[lock.Id] = true
}
}
if err := repoLockRows.Err(); err != nil {
log.Printf("[Lock][%d] error iterating over repo locks: %v | reason: %s", goroutineID, err, params.Reason)
return "", fmt.Errorf("error iterating over repo locks: %v", err)
}
log.Printf("[Lock][%d] %d locks found, %d expired | reason: %s", goroutineID, len(locks), len(expiredLockIds), params.Reason)
if len(expiredLockIds) > 0 {
log.Printf("[Lock][%d] %d expired locks found, deleting | reason: %s", goroutineID, len(expiredLockIds), params.Reason)
if locksVerboseLogging {
log.Printf("deleting expired locks: %v", expiredLockIds)
}
query := "DELETE FROM repo_locks WHERE id = ANY($1)"
_, err := tx.Exec(query, pq.Array(expiredLockIds))
if err != nil {
if isDeadlockError(err) {
log.Println("deadlock clearing expired locks, won't do anything")
} else {
log.Printf("[Lock][%d] error removing expired locks: %v | reason: %s", goroutineID, err, params.Reason)
return "", fmt.Errorf("error removing expired locks: %v", err)View on GitHub (pinned to e2d772072e)
Solutions
- Check the wrapped driver error — connection-reset style errors warrant a retry of the whole lock attempt (the layer already has retry/backoff).
- Verify context deadlines: a too-short ctx on LockRepoParams.Ctx will cancel mid-iteration.
- Check DB/server logs and statement_timeout / idle settings for what killed the query.
- Ensure TCP keepalives / pool health checks are enabled so stale connections are culled.
Example fix
// before ctx := context.Background() lockId, err := db.LockRepo(ctx, cancel, params) // no deadline, dies on stale conn // after ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) lockId, err := db.LockRepo(ctx, cancel, params) // fresh ctx + built-in retries
Defensive patterns
Strategy: retry
Validate before calling
if err := db.Conn.PingContext(ctx); err != nil {
return fmt.Errorf("database connection unhealthy before locking: %w", err)
} Type guard
func ctxUsable(ctx context.Context) bool {
return ctx != nil && ctx.Err() == nil
} Try / catch
lockId, err := db.LockRepo(ctx, cancel, params)
if err != nil && strings.Contains(err.Error(), "error iterating over repo locks") {
// mid-stream failure — retry; the lock layer's own backoff may also kick in
lockId, err = db.LockRepo(ctx, cancel, params)
} Prevention
- Enable DB TCP keepalives and pool connection lifetime limits to cull stale connections.
- Set statement_timeout/idle_in_transaction_session_timeout above the lock query's worst case.
- Give lock calls bounded contexts so cancellations are deliberate, not accidental.
When it happens
Trigger: Postgres connection lost while streaming rows; the request context cancelled during iteration; driver error surfaced only after the last row was fetched.
Common situations: Network instability between server and DB under load; long query killed by idle-in-transaction or statement timeout; LB/proxy dropping idle DB connections.
Related errors
- error starting transaction: %v
- error checking settings: %v
- error scanning repo lock: %v
- error removing expired locks: %v
- error inserting new lock: %v
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/01c0c733f3d550c3.
Report an issue: GitHub.