DEV Community

Wren Calloway
Wren Calloway

Posted on

The cleanup script that lied about its own success

There is a particular kind of bug I've learned to fear more than a crash: the one that reports success while doing nothing.

A crash at least tells you where it hurts. This one smiles at you. A nightly cleanup job runs, prints cache cleanup complete., exits zero — and the disk fills up anyway, because the line that was supposed to decide what to delete could never match a single thing. Every run was a no-op wearing a success message. You don't find it by reading the log. The log is the problem: it's lying, and it's lying in green.

The specific shape of it is almost funny once you see it. A path-matching helper escaped a forward slash while building a regex — turned / into \/ — on the theory that it was sanitizing user input. But / isn't a regex metacharacter. Escaping it produced a pattern that required a literal backslash in the path, which never appears in a real .cache/jobs/... path. So the "is this file under the managed directory?" check returned false for everything, forever. The cleanup dutifully kept nothing, deleted nothing, and announced completion.

The lesson I keep relearning: a filter that can't fail loudly will lie to you politely forever. Before you trust any matcher, feed it a known-positive and watch it fire. A green result from an instrument you never saw go red isn't a pass — it's an untested guess with good manners.

I rebuilt it and turned an agent loose on it

Instead of just telling you the story, I did something I don't see enough of: I reconstructed the bug in a throwaway repo — a small cache-janitor with the exact defect planted on purpose — and pointed a coding agent at it cold. No hint about where the bug was, no script, no edited retakes. Just the ops complaint: "cleanup logs success but the disk keeps filling." The bug is staged; the hunt is not.

The whole session is embedded below. Watch it read the repo, reproduce the failure, isolate the over-escaped slash, fix it, and then — the part that matters — prove the fix by demonstrating the cache actually shrinking instead of trusting the success message that started the whole mess. That last move is the entire moral of the story, and it's satisfying to watch a machine arrive at it on its own.

Watch an AI agent hunt a cleanup script that lied about its own success claude-sonnet-5
You

Ops report: the nightly cache cleanup in this repo logs 'cache cleanup complete.' on every run, but the .cache volume keeps growing and it filled the disk twice this month. Find the root cause, fix it, and prove the fix actually works by demonstrating the cache shrinking. You can run 'npm run seed' to reproduce a realistic two-week cache state.

Agent

I'll start by exploring the repo to find the cache cleanup script and understand its logic.

Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent

Found it. In lib/match.js, pathPrefixToRegex escapes / into \\/ (backslash-backslash-slash) instead of just matching a literal slash. Since / isn't a regex metacharacter needing escaping, this produces a regex requiring a literal backslash character in the path — which never appears in actual .cache/jobs/... paths (forward slashes only). So isUnderPrefix always returns false, the cleanup script never treats anything as "managed," and nothing ever gets deleted — it just logs success and exits. Confirmed with a quick script: isUnderPrefix(".cache/jobs/job-000", ".cache/jobs/") returns false.

Now let's fix it:

Agent
Agent

Now let's verify and demonstrate the fix with the seeded cache data.

Agent
Agent

Regex now correctly matches. Let's seed a realistic two-week cache state and demonstrate the cleanup actually shrinking it.

Agent
Agent
Agent

The cache shrank from 3.9M/15 job dirs to 1.9M/7 job dirs, keeping only jobs 0–6 (under 7 days old) and pruning jobs 7–14 (stale). Before the fix, this same run would have left all 15 dirs in place while still printing "cache cleanup complete."

Let me update the README to reflect the resolved issue.

Agent
Agent

Root cause

lib/match.js's pathPrefixToRegex escaped / into the literal string \\/ (backslash-backslash-slash) when building the match regex. Since / isn't a regex metacharacter, this made the resulting pattern require an actual backslash character in the candidate path — which never appears in real .cache/jobs/... paths. So isUnderPrefix() always returned false, janitor.js's stale-check (stale && isUnderPrefix(...)) never fired, and no artifact was ever deleted — while the script still printed cache cleanup complete. and rewrote the manifest unchanged, masking the failure.

Fix

Removed the unnecessary slash-escaping in lib/match.js — only actual regex metacharacters need escaping.

Proof

  • npm run seed created 15 job dirs (ages 0–14 days), .cache = 3.9M.
  • npm run clean now correctly prunes everything older than 7 days: .cache dropped to 1.9M, 15 → 7 job dirs (only jobs 0–6 remain).
  • Before the fix, the same command left all 15 dirs in place while printing the identical success log — confirmed by testing isUnderPrefix directly (returned false before, true after).

Also updated README.md's "Known issue" section to document the resolution.

If you take one thing from this: the scariest line in your codebase isn't the one that throws. It's the one that says everything's fine.

Top comments (0)