{"record":{"id":"0ff10c2869a28e48","repo":"plandex-ai/plandex","slug":"error-removing-all-locks-v","errorCode":null,"errorMessage":"error removing all locks: %v","messagePattern":"error removing all locks: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/server/db/locks.go","lineNumber":653,"sourceCode":"\t\t} else {\n\t\t\tif locksVerboseLogging {\n\t\t\t\tlog.Println(\"transaction rolled back\")\n\t\t\t}\n\t\t}\n\t}()\n\n\t// Delete all active locks\n\tquery := \"DELETE FROM repo_locks WHERE id = ANY($1)\"\n\tids := make([]string, 0, len(activeLockIds))\n\tfor id := range activeLockIds {\n\t\tids = append(ids, id)\n\t}\n\t_, err = tx.Exec(query, pq.Array(ids))\n\tif err != nil {\n\t\tif err == sql.ErrNoRows {\n\t\t\tlog.Println(\"No active locks to cleanup\")\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"error removing all locks: %v\", err)\n\t\t}\n\t}\n\n\t// Commit the transaction\n\tif err = tx.Commit(); err != nil {\n\t\treturn fmt.Errorf(\"error committing transaction: %v\", err)\n\t}\n\n\tactiveLockIdsMu.Lock()\n\tactiveLockIds = make(map[string]bool)\n\tactiveLockIdsMu.Unlock()\n\n\tlog.Println(\"Successfully cleaned up all repo locks\")\n\treturn nil\n}\n","sourceCodeStart":635,"sourceCodeEnd":669,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/db/locks.go#L635-L669","documentation":"Inside CleanupActiveLocks, the batch DELETE of all active lock rows (using pq.Array(ids)) failed with an error other than sql.ErrNoRows; it is wrapped as 'error removing all locks'. The transaction aborts before commit, so stale locks remain in repo_locks.","triggerScenarios":"The DELETE ... WHERE id = ANY($1) statement errors: connection lost mid-statement, syntax/type mismatch on the array parameter, deadlock with a concurrent lock operation, or statement timeout on a huge id list.","commonSituations":"Very large activeLockIds map producing a slow delete; pq.Array misuse after driver upgrade; deadlock because other workers hold row locks on repo_locks during shutdown; DB failover mid-cleanup.","solutions":["Check the wrapped driver error for deadlock/timeout codes (40001/40P01) and retry cleanup","Verify the ids slice is non-empty and well-formed before the query","Keep the batch small (chunk ids) to avoid long-running deletes","Ensure no other workers are contending on repo_locks during cleanup","Confirm pq (lib/pq) import and driver version match the array-parameter usage"],"exampleFix":"// before\n_, err = tx.Exec(query, pq.Array(ids))\nif err != nil { return err }\n// after\n_, err = tx.Exec(query, pq.Array(ids))\nif err != nil {\n    if isDeadlockError(err) {\n        return CleanupActiveLocks(ctx) // safe: tx rolled back, retry whole cleanup\n    }\n    return fmt.Errorf(\"cleanup: %w\", err)\n}","handlingStrategy":"retry","validationCode":"if len(ids) == 0 {\n    return nil // nothing to clean\n}\nif err := Conn.PingContext(ctx); err != nil {\n    return fmt.Errorf(\"db unavailable: %w\", err)\n}","typeGuard":"func isDeleteAllFailure(err error) bool {\n    return err != nil && !errors.Is(err, sql.ErrNoRows) && strings.Contains(err.Error(), \"error removing all locks\")\n}","tryCatchPattern":"if err := CleanupActiveLocks(ctx); err != nil {\n    if isDeleteAllFailure(err) {\n        time.Sleep(time.Second)\n        return CleanupActiveLocks(ctx) // tx rolled back; full retry is safe\n    }\n    return err\n}","preventionTips":["Chunk large id lists to keep deletes short","Resolve contention — don't run cleanup while workers actively lock/unlock","Validate pq.Array usage and driver compatibility","Retry whole cleanup on failure since the tx aborts atomically"],"tags":["database","postgres","delete","cleanup"],"backgroundTag":"database-delete-failed","analyzedSha":"e2d772072efadbe41d2946d97d79be55532dbab5","analyzedAt":"2026-09-05T20:56:53.631Z","contentChangedAt":"2026-09-05T20:56:53.631Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}