Skip to content

Cache admin deletion#709

Merged
kentcdodds merged 5 commits intomainfrom
cursor/cache-admin-deletion-143b
Feb 25, 2026
Merged

Cache admin deletion#709
kentcdodds merged 5 commits intomainfrom
cursor/cache-admin-deletion-143b

Conversation

@kentcdodds
Copy link
Owner

@kentcdodds kentcdodds commented Feb 25, 2026

Add a "Delete all matching cache values" button to /cache/admin to enable bulk deletion of filtered cache entries.


Open in Web Open in Cursor 


Note

Medium Risk
Adds a destructive bulk-delete path over caches; mistakes in filtering/limits or primary-instance enforcement could lead to unintended cache invalidation across the system.

Overview
Adds a bulk deletion action to /cache/admin that removes all currently filtered cache keys from both the SQLite-backed cache and in-memory LRU cache.

Introduces input hardening around the limit param (sanitized and capped at 10_000) and enforces that bulk deletes can only run on the primary LiteFS instance. The UI now shows a conditional “delete all N matching cache values” button with a double-confirmation flow and in-progress status.

Written by Cursor Bugbot for commit 869f83a. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

  • New Features

    • Added bulk delete to remove all cache entries matching specified criteria in one action.
    • New "Delete All Matching" button in Cache Admin showing total matching values, with double-confirm safety prompts.
    • Preserves existing single-key delete flow.
  • Improvements

    • UI and loader updated to compute and display matching counts before deletion.
    • Safeguards ensure bulk deletion runs only on the primary instance.

cursoragent and others added 3 commits February 25, 2026 20:23
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@cursor
Copy link

cursor bot commented Feb 25, 2026

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cdacab3 and 869f83a.

📒 Files selected for processing (1)
  • app/routes/cache.admin.tsx

📝 Walkthrough

Walkthrough

Adds a bulk-delete flow to the Cache Admin route: introduces a delete-all-matching intent, helper to compute matching cache keys, loader changes to compute matching counts, server-side bulk deletion across SQLite and LRU caches with primary-instance validation, and a UI button + confirmation to trigger the bulk delete.

Changes

Cohort / File(s) Summary
Cache Admin Bulk Delete
app/routes/cache.admin.tsx
Adds deleteAllMatchingIntent handling in the action to perform bulk deletion across SQLite and LRU caches; adds getMatchingCacheKeys helper used by loader and action; loader now returns matchingCacheValuesCount; adds DeleteAllMatchingCacheValuesButton UI, confirmation workflow, and primary-instance validation; retains single-key delete flow.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant UI as UI Component
    participant Server as Server Action
    participant SQLite as SQLite Cache
    participant LRU as LRU Cache

    User->>UI: Click "Delete All Matching"
    UI->>UI: Show confirmation prompts
    User->>UI: Confirm
    UI->>Server: POST (intent=delete-all-matching, query, limit)
    Server->>Server: Validate instance & params, compute matching keys
    Server->>SQLite: Delete matching keys
    SQLite-->>Server: Deletion result
    Server->>LRU: Delete matching keys
    LRU-->>Server: Deletion result
    Server-->>UI: Return success and updated counts
    UI->>User: Display result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I dug through keys both old and new,
Matched them, counted, then bid adieu,
I nudged the primary, checked twice with care,
One hop, one button — gone into air! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Cache admin deletion' directly aligns with the main change: adding bulk deletion functionality to the cache admin interface, which is the primary objective of the PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch cursor/cache-admin-deletion-143b

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@kentcdodds kentcdodds marked this pull request as ready for review February 25, 2026 20:39
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
app/routes/cache.admin.tsx (1)

122-123: Use matchingCacheValuesCount consistently in the results badge.

The count is recomputed inline at Line 155 even though it’s already derived at Lines 122-123. Reusing the variable avoids drift.

Small cleanup
 						<div className="absolute top-0 right-2 flex h-full w-14 items-center justify-between text-lg font-medium text-slate-500">
 							<span title="Total results shown">
-								{data.cacheKeys.sqlite.length + data.cacheKeys.lru.length}
+								{matchingCacheValuesCount}
 							</span>
 						</div>

Also applies to: 155-156

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/routes/cache.admin.tsx` around lines 122 - 123, The code computes the
total count twice; reuse the existing matchingCacheValuesCount variable (which
equals data.cacheKeys.sqlite.length + data.cacheKeys.lru.length) wherever the
inline sum is re-calculated (e.g., the results badge rendering) instead of
recomputing data.cacheKeys.sqlite.length + data.cacheKeys.lru.length inline;
update all occurrences (including the second instance noted) to reference
matchingCacheValuesCount so the badge and any other UI elements stay consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/routes/cache.admin.tsx`:
- Around line 40-51: Sanitize and clamp the incoming limit inside
getMatchingCacheKeys: coerce to an integer (Math.floor), treat NaN or values <=
0 as a minimum of 1, and cap at a sensible MAX_LIMIT (e.g., 1000) to prevent
unbounded or invalid operations; then pass the sanitized value to
searchCacheKeys and getAllCacheKeys. Add a local MAX_LIMIT constant (or
file-level constant) and use something like sanitizedLimit = Math.min(MAX_LIMIT,
Math.max(1, Math.floor(limit) || 1)) before calling searchCacheKeys(query,
sanitizedLimit) or getAllCacheKeys(sanitizedLimit).
- Around line 73-77: Bulk deletes currently call cache.delete for each key and
can trigger fire-and-forget cross-replica writes; to prevent returning success
before replica operations complete, add a server-side guard to require the
primary instance for bulk delete: after resolving currentInstance via
getInstanceInfo (the existing currentInstance) and after
ensureInstance(instance) confirm that instance === currentInstance and if not
return an error/invariantResponse rejecting bulk delete; update the route
handling the bulk-delete loop (where cache.delete is iterated) to perform this
check and only proceed with the loop when the requested instance equals
currentInstance.

---

Nitpick comments:
In `@app/routes/cache.admin.tsx`:
- Around line 122-123: The code computes the total count twice; reuse the
existing matchingCacheValuesCount variable (which equals
data.cacheKeys.sqlite.length + data.cacheKeys.lru.length) wherever the inline
sum is re-calculated (e.g., the results badge rendering) instead of recomputing
data.cacheKeys.sqlite.length + data.cacheKeys.lru.length inline; update all
occurrences (including the second instance noted) to reference
matchingCacheValuesCount so the badge and any other UI elements stay consistent.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bd91621 and 2385b94.

📒 Files selected for processing (1)
  • app/routes/cache.admin.tsx

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
app/routes/cache.admin.tsx (2)

38-59: Sanitization helpers look correct — past issue fully addressed.

sanitizeCacheKeysLimit correctly handles all degenerate inputs (NaN, 0, negative, non-integer, and above-max). getMatchingCacheKeys consistently applies the sanitized limit for both lookup paths. The previous review concern is resolved here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/routes/cache.admin.tsx` around lines 38 - 59, sanitizeCacheKeysLimit and
getMatchingCacheKeys are correct and the previous sanitization bug is resolved;
no code changes needed—keep sanitizeCacheKeysLimit (handles non-finite, zero,
negative, non-integer, and above-max values) and ensure getMatchingCacheKeys
continues to call sanitizeCacheKeysLimit before invoking searchCacheKeys or
getAllCacheKeys.

88-92: Primary-instance guard correctly addresses the fire-and-forget bulk-delete concern.

Because currentIsPrimary is asserted before the deletion loop, all cache.delete calls take the primary branch (preparedDelete.run) with no cross-replica fire-and-forget risk.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/routes/cache.admin.tsx` around lines 88 - 92, The primary-instance guard
is correctly placed: ensure getInstanceInfo() is called and
invariantResponse(currentIsPrimary, `Bulk delete must run on the primary
instance (${primaryInstance})`) remains before any cache.delete calls so that
cache.delete uses the primary branch (preparedDelete.run) and avoids
cross-replica fire-and-forget behavior; confirm the code paths using
preparedDelete.run and cache.delete are unchanged and no deletion loop executes
before this check.
🧹 Nitpick comments (1)
app/routes/cache.admin.tsx (1)

81-88: Redundant getInstanceInfo() call — merge into one.

getInstanceInfo() is awaited on Line 81 and again on Line 88. All needed fields can be destructured from a single call.

♻️ Proposed refactor
-	const { currentInstance } = await getInstanceInfo()
 	const instance = formData.get('instance') ?? currentInstance
 	invariantResponse(typeof instance === 'string', 'instance must be a string')
 	await ensureInstance(instance)

 	const intent = formData.get('intent')
 	if (intent === deleteAllMatchingIntent) {
-		const { currentIsPrimary, primaryInstance } = await getInstanceInfo()
+	const { currentInstance, currentIsPrimary, primaryInstance } = await getInstanceInfo()
+	const instance = formData.get('instance') ?? currentInstance
+	invariantResponse(typeof instance === 'string', 'instance must be a string')
+	await ensureInstance(instance)
+
+	const intent = formData.get('intent')
+	if (intent === deleteAllMatchingIntent) {
 		invariantResponse(
 			currentIsPrimary,
 			`Bulk delete must run on the primary instance (${primaryInstance})`,
 		)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/routes/cache.admin.tsx` around lines 81 - 88, The code calls
getInstanceInfo() twice; merge into a single call and destructure all needed
fields (currentInstance, currentIsPrimary, primaryInstance) once at the top,
then use those variables for the instance default and the intent check; remove
the second await of getInstanceInfo() and ensure
invariantResponse/ensureInstance still use the single destructured
currentInstance and instance variable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@app/routes/cache.admin.tsx`:
- Around line 38-59: sanitizeCacheKeysLimit and getMatchingCacheKeys are correct
and the previous sanitization bug is resolved; no code changes needed—keep
sanitizeCacheKeysLimit (handles non-finite, zero, negative, non-integer, and
above-max values) and ensure getMatchingCacheKeys continues to call
sanitizeCacheKeysLimit before invoking searchCacheKeys or getAllCacheKeys.
- Around line 88-92: The primary-instance guard is correctly placed: ensure
getInstanceInfo() is called and invariantResponse(currentIsPrimary, `Bulk delete
must run on the primary instance (${primaryInstance})`) remains before any
cache.delete calls so that cache.delete uses the primary branch
(preparedDelete.run) and avoids cross-replica fire-and-forget behavior; confirm
the code paths using preparedDelete.run and cache.delete are unchanged and no
deletion loop executes before this check.

---

Nitpick comments:
In `@app/routes/cache.admin.tsx`:
- Around line 81-88: The code calls getInstanceInfo() twice; merge into a single
call and destructure all needed fields (currentInstance, currentIsPrimary,
primaryInstance) once at the top, then use those variables for the instance
default and the intent check; remove the second await of getInstanceInfo() and
ensure invariantResponse/ensureInstance still use the single destructured
currentInstance and instance variable.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2385b94 and cdacab3.

📒 Files selected for processing (1)
  • app/routes/cache.admin.tsx

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@kentcdodds kentcdodds merged commit 11b80b6 into main Feb 25, 2026
6 of 7 checks passed
@kentcdodds kentcdodds deleted the cursor/cache-admin-deletion-143b branch February 25, 2026 20:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants