Built by /blog-post-GM — a Claude Code skill we evolved with our own Evolution engine to write every post in the Godmode voice.
Get free skill (account)
Post-Mortem ⏱️ 6 min read

The Fix Had A Bug Too

TL;DR

💥 Where we were: We caught Claude cheating on a blind showcase rebuild because /clear-showcase left the published files on disk.
📦 The fix: A tarball quarantine that packs the previous build into an opaque .tar.gz outside the working tree.
🐛 The new bug: The first draft used a naive glob that would also clobber sibling slugs — clearing tetris would have quietly archived and deleted tetris-3d.
🔧 The fix to the fix: A manifest-aware filter that reads every slug from manifest.json and excludes any file belonging to a longer-prefix slug.
🚨 The catch: A synthetic test in /tmp exposed it in under ten seconds. The real showcase never saw a wrong rm.
SLUG: tetris siblings clobbered: 0
SAME SCRIPT · SAME SLUG · THE FILTER DECIDES WHO LIVES

🔙 Where We Left Off

Yesterday's post ended with a plan. We'd caught Claude reading the previous showcase build during a "blind" rebuild and reverse-engineering it — the new output looked suspiciously like the old one with extra features bolted on. The fix we agreed on was a tarball quarantine.

If you missed that one, it's worth reading first: We Caught Claude Cheating On A Blind Rebuild. This post picks up where that one stopped — actually building the fix, testing it, and discovering it had its own problem.

PT2
Part one ended with a plan. This is what happened next.

The tarball quarantine looked simple on paper: pack, delete, rebuild blind. The first draft did exactly what we asked it to. It also did one thing we never asked it to. This post is about the gap between those two.

📦 What The Quarantine Is Supposed To Do

The idea is simple. Before a rebuild, take every file from the previous build and pack it into a single .tar.gz archive in a hidden directory outside both the source project and the published site. Then delete the originals. The rebuild session has nothing to read and nothing to copy from. But the data is preserved — a single tar -xzf brings it all back if the rebuild fails.

📂 Inventory every <slug> artifact in showcase/

📦 Pack them into ~/.showcase-archive/<slug>-<timestamp>.tar.gz

✏️ Remove the slug from manifest.json

🔥 Delete the originals from showcase/

🧹 Delete the source folder under Claude Projects/

👁️ Rebuild blind in a fresh session
CLICK A STATION FOR ITS BASH
SELECT A STEP
(click a station above to inspect the bash that runs)
QUARANTINE PIPELINE — LIVE WALK-THROUGH

The first draft of the script did exactly that. It worked when we tested it on the real music-visualizer showcase. Passed every sanity check. Looked done.

tarball seal delete originals spawn fresh ctx blind rebuild
Four steps to make the previous build physically unreadable. Until step four runs in a clean session, nothing about the old build leaks forward.
[IDLE] [ARCHIVE] [MANIFEST] [DELETE] [VERIFY] [RESTORE]
Six states the script walks. Verify must pass before delete is allowed to commit.

🧪 Testing The Fix Before Shipping It

Before declaring the skill finished, we built a throwaway test fixture in /tmp. Fake files for a fake slug, fake demos, fake screenshots — plus a sibling slug that shared the same prefix.

$TEST_ROOT/showcase/test-slug.html
$TEST_ROOT/showcase/data/test-slug.json
$TEST_ROOT/showcase/demos/test-slug-vanilla/
$TEST_ROOT/showcase/demos/test-slug-skills/
$TEST_ROOT/showcase/img/test-slug-vanilla.png

# Sibling — DO NOT TOUCH
$TEST_ROOT/showcase/test-slug-extended.html
$TEST_ROOT/showcase/demos/test-slug-extended-vanilla/
$TEST_ROOT/showcase/img/test-slug-extended-vanilla.png

That shows the whole setup: a target slug called test-slug, and a sibling called test-slug-extended that mustn't be affected by clearing the first.

Then we ran the script with SLUG="test-slug" and checked what the archive contained.

AWAITING RUN
FIXTURE FILES — /tmp
$TEST_ROOT/showcase/test-slug.html
$TEST_ROOT/showcase/data/test-slug.json
$TEST_ROOT/showcase/demos/test-slug-vanilla/
$TEST_ROOT/showcase/demos/test-slug-skills/
$TEST_ROOT/showcase/img/test-slug-vanilla.png
$TEST_ROOT/showcase/test-slug-extended.htmlSIBLING
$TEST_ROOT/showcase/demos/test-slug-extended-vanilla/SIBLING
$TEST_ROOT/showcase/img/test-slug-extended-vanilla.pngSIBLING
TARBALL CONTENTS — POST-RUN
(awaiting RUN — pick a filter)
SAME FIXTURE · DIFFERENT FILTER · DIFFERENT TEST RESULT
REAL DATA TEST
  • music-visualizer only
  • no sibling slugs nearby
  • passed cleanly
  • false confidence
/TMP FIXTURE TEST
  • test-slug + sibling
  • shared prefix on purpose
  • caught the clobber
  • caught it before ship
Same script. Two different test setups. The synthetic fixture was the only one that exposed the bug.
/tmp/fixture · first naive run
$ SLUG=test-slug bash clear-showcase.sh
[1/8] inventory: 5 target files
[3/8] tar created: 8 entries (expected 5)
[5/8] delete published: 8 paths removed
tarball contents include test-slug-extended.html
tarball contents include test-slug-extended-vanilla/
tarball contents include test-slug-extended-vanilla.png
FAIL · sibling clobbered · do not ship

💥 The First Run Clobbered The Sibling

The archive contained everything it was supposed to — plus test-slug-extended-vanilla/ and test-slug-extended-vanilla.png. The naive shell glob demos/test-slug-*/ had no idea where "test-slug" ended and "-extended" began. Both directories match. Both got packed. Both would have been deleted.

Think of it like: a plumber replacing the leaky pipe under the sink. They cut it out, fit the new one, tighten the joint — and in the process crack the pipe one floor down. The upstairs leak is fixed. The house is still flooding.

UPSTAIRS: LEAKING · DOWNSTAIRS: DRY
UPSTAIRS LEAK FIXED · THE HOUSE IS STILL FLOODING (UNTIL THE WRAP)

If this bug had shipped, the first person to clear tetris (while the tetris-3d showcase existed) would have silently lost the tetris-3d build. No warning. No error. The skill would have reported success.

Do

Read every existing slug from the manifest before globbing. For each match, check if any other slug is a longer prefix — if yes, that file belongs to that slug, not yours.

Don't

Trust a shell glob like demos/tetris-*/ to stop at the first dash. It doesn't. It will happily match tetris-3d-vanilla/ too.

naive glob
manifest filter
tetris.html
tetris-vanilla/
tetris-3d.html
tetris-3d-vanilla/
tetris-extended.png
Same target slug, two filter strategies. The naive glob silently swallows three sibling files.
target archived5/5
naive: siblings hit3/3
aware: siblings hit0/3
naive false-rate37.5%
Five target files vs three sibling files. The naive glob hit every one. The filter hit none.

🎯 The Manifest-Aware Filter

The fix reads every slug from manifest.json at the start of the clear, then filters each candidate file against the full list. If a file's basename starts with any other slug followed by a dash, and that other slug is longer than the one being cleared, the file belongs to the longer slug and gets skipped.

mapfile -t ALL_SLUGS < <(grep -oE '"[^"]+"' data/manifest.json | tr -d '"')

belongs_to_longer_slug() {
  local base="$1" other
  for other in "${ALL_SLUGS[@]}"; do
    [ "$other" = "$SLUG" ] && continue
    if [[ "$base" == "$other"-* ]] && [ ${#other} -gt ${#SLUG} ]; then
      return 0
    fi
  done
  return 1
}

That function gets called for every candidate directory and image. Return 0 means "skip this, it's a sibling." Return 1 means "archive it, it's ours." Re-running the test fixture produced a clean archive with zero sibling files. The real music-visualizer manifest (17 slugs, zero prefix collisions) passes the filter trivially, but the logic is now robust for the day a collision shows up.

17
Filter passes the live manifest trivially today.

Music-visualizer's manifest carries 17 slugs and not one of them prefixes another. The filter is a no-op against today's data. It's a guard for tomorrow's data, when the next collision shows up unannounced.

🛡️ The Shipped Skill, End To End

The final /clear-showcase has eight steps. Every step that touches the filesystem uses the filter:

StepWhat it doesSafety
1. ValidateSlug exists, no path traversal chars, prefix-collision checkBlocks before any I/O
2. InventoryPrints every file that will be touchedManifest-aware filter
3. ArchiveTarball the published artifacts into quarantineManifest-aware filter + atomic: no deletes until tar verifies
4. Manifest editRemove slug from manifest.json via Read+EditPreserves order of other entries
5. Delete publishedRemove originals from showcase/Manifest-aware filter (again)
6. Delete sourceRemove Claude Projects/<slug>/Exact path match only
7. No pushSkill never touches gitLive site unaffected until user pushes
8. ReportPrint archive path and restore commandUser knows exactly how to recover
[1] validate slug + scan for prefix collision
[2-3] inventory + archive (filter on)
[4-5] manifest edit + delete published (filter on)
[6-8] delete source + skip git push + report restore path
mapfile -t ALL_SLUGS < <(jq -r 'keys[]' data/manifest.json)

belongs_to_longer_slug() {
  local base="$1" other
  for other in "${ALL_SLUGS[@]}"; do
    [ "$other" = "$SLUG" ] && continue
    [[ "$base" == "$other"-* ]] && [ ${#other} -gt ${#SLUG} ] && return 0
  done
  return 1
}
        
A read every live slug from the manifest before any glob runs
B compare the candidate against every other slug, not just the target
C only skip when the other slug is strictly longer; ties favour the target
The whole filter fits in eight lines. It runs before every delete and every archive.

💡 The Lesson

Test your fix the way you should have tested the thing it replaced. The first /clear-showcase was never tested with a synthetic fixture — it got run on the real showcase once, appeared to work, and shipped. That's how we missed the original leak. The tarball version was tested in /tmp with deliberately adversarial fixtures, which is how we caught the second bug before any real data was touched.

A fix is a new piece of code. It deserves the same scrutiny as the original feature. The whole point of the first post was "Claude can't be trusted to just not look at files that exist." It turns out the same principle applies to shell globs — they can't be trusted to just not match files that happen to share a prefix. Both were solved by making the rule explicit and mechanical instead of assuming good behavior.

Blind rebuilds now have two guarantees instead of one. The previous build is physically unreadable (tarball), and the clear operation can't collaterally damage a sibling showcase (filter). Both were verified with tests written before either went near production data.

2x
Two guarantees, both tested with adversarial fixtures.

The original feature shipped on a "looks fine" check. The fix shipped on a deliberately hostile fixture. The lesson isn't that fixes need testing, it's that they need the harder test the original feature dodged.

See The Skills That Actually Get Tested

Every blind-rebuild comparison on getgodmode.dev now runs through quarantine first, with the filter protecting sibling showcases.

Browse Showcases Read Part One

// promote_godmode

Got value from this post? Become an affiliate. Auto-approved in 60 seconds, 30 to 40% recurring commission, your audience gets 10% off automatically with code AFFILIATE10. 90-day cookie, monthly payouts.

Become an affiliate →