A folder
of timestamps,
turned into a manifest.
Two hundred screenshots, all named CleanShot 2026-04-15 at 09.14.07.png. Run this skill: ten Haiku subagents read each one in parallel, write a six-to-eight word description, and rename the file in place — atomically, with the safety nets the author cost himself four files to learn.
You can't find a screenshot you took six months ago.
- CleanShot 2025-09-26 at 16.27.39.png
- CleanShot 2025-11-19 at 13.12.36.png
- CleanShot 2025-12-05 at 11.24.33.png
- CleanShot 2026-02-18 at 12.48.31.png
- CleanShot 2026-03-04 at 06.13.44.png
- CleanShot 2026-03-17 at 22.10.20.mp4
- CleanShot 2026-03-21 at 11.46.42.png
- CleanShot 2026-04-08 at 12.09.10.png
- …and 187 more
A timestamp tells you when a screenshot exists. It doesn't tell you what's in it. Spotlight indexes the pixels reluctantly; iCloud-synced folders less reliably still. The only way most people find an old screenshot is by remembering, roughly, what they were doing the week they took it — and scrolling.
The real cost isn't filesystem clutter. It's the screenshots you stopped taking, because past you knew future you wouldn't be able to surface them.
196 →files renamed in the first run that motivated this skill, in three minutes, with zero loss after the second pass. The first pass cost four files. That's why the safety rules below are written the way they are.
Four stages, in two minutes.
The skill does as little as possible, and validates as much as possible.
Subagents handle the work that benefits from parallelism (vision); Python
handles the work that benefits from being correct (filename mutation,
collision detection, the actual os.rename).
Prep.
Extract the first frame from every .mp4 and .pdf. Resize every image to 1568px max — Read's image cap is real. Build a manifest TSV.
ffmpeg · sips · /tmp/screenshot-rename/full-batch-NN
Describe.
Dispatch one Haiku subagent per batch, in parallel — ten at a time. Each agent reads its 19 images and writes 6–8 word descriptions to desc-full-NN.tsv.
model · "haiku" · ~$0.30 / 200 files
Plan.
Aggregate. Validate every line: 6+ words, alnum only, source exists, target doesn't, no plan-internal collisions. Build the full rename map in memory.
plan-full.tsv · zero-error policy
Execute.
One os.rename per row, with pre-existence check. Audit len(listdir) before and after — it must be equal. That equality is your only proof no overwrites happened.
before == after · ok / fail
Before a timestamp.
After, a sentence.
A real rename from the run that motivated this skill. The description was generated by Haiku in roughly two seconds.
The original timestamp survives unchanged. Sorting still works. The description sits between, set off by em-dashes.
Three additions, one mixed run.
A second batch of files surfaced cases the first run never hit: Apple's own Screenshot filenames, user-typed keyword prefixes, and folders where some files were already renamed. The pipeline now handles all three in a single pass — and the parser learned a new gotcha along the way.
CleanShot and Screenshot, in one run.
The parser now accepts both CleanShot 2026-MM-DD at HH.MM.SS.png and
Apple's Screenshot 2026-MM-DD at H.MM.SS PM.png. Mixed folders no
longer need two passes — the manifest builder picks up either prefix.
Hand-typed prefixes survive.
A file named jojo travel CleanShot 2026-...png carries user knowledge
the AI doesn't have. The parser strips the keyword phrase, title-cases it, and
prepends it to the AI description — so the new filename reads
Jojo Travel Flight Australia Melbourne Flightaware Map Route.
Re-running won't stack descriptions.
The parser now detects names already in the
App - Description - timestamp.ext form and excludes them from the
manifest. You can re-run the skill on a partially-renamed folder without the
name growing on every pass.
Every rule below was paid for.
During development, four files were destroyed by a one-line bash mistake. Each rule names the failure mode that earned its place. None are aspirational.
- Resize before vision.Retina screenshots exceed Read's image cap. Use
sips -Z 1568 -s format jpegfirst. The agent will fail mid-batch otherwise. - Frames, not videos.The vision tool can't read
.mp4or multi-page.pdf. Extract a frame withffmpeg -ss 1 -frames:v 1and describe that. - Never trust bash regex on filenames.zsh's
[[ =~ ]]does not populateBASH_REMATCH. Pattern silently fails, target name is empty, multiplemvs collide. Use Python. mvoverwrites silently.One off-by-one in target construction destroys data with no error. Usemv -nin shell, oros.renameafter anos.path.existscheck in Python.- Plan the full rename in memory first.Build every
(src, dst)tuple. Verify eachdstis unique, doesn't exist, and corresponds to a realsrc. Then mutate disk. - File-count audit, every time.
len(listdir(DEST))before and after must be equal. Inequality is the only evidence of silent loss you'll get. - iCloud snapshots are stubs, not bytes.Files in a Time Machine local snapshot inside an iCloud-synced tree are file-provider stubs.
catthem and the read times out. Real recovery comes from external backups. - Run renames foreground.
Bash run_in_backgroundwithwhile readmay exit early with no progress. Run via Python in the same shell —os.renameis just a syscall. - Validate the filename column.Haiku occasionally returns the resized
.jpgname instead of the original.png. The plan-builder must try alternate extensions when the claimed source isn't found. - Preserve the original extension.The pipeline reads from a resized JPEG but renames the original
.mp4/.pdf. Write the source extension back into the new name. - Apple Screenshot files use
U+202F.The narrow no-break space sits between the seconds and AM/PM. Haiku echoes it as ASCII space, the lookup misses, and every Screenshot file is dropped from the plan with a misleadingNO_DESCerror. Normalize on both sides; emit ASCII space in the new name. - Re-runs must skip already-renamed files.Without an
^App - .+ - timestamp.ext$exclusion rule the parser will pile a second AI description into every name on every run. The pipeline detects and excludes them. - User-typed keyword prefix is signal.A name like
jojo travel CleanShot ...carries knowledge the AI doesn't have. Strip the keyword phrase, title-case it, and prepend it to the description before assembly. Don't drop it.
What this looks like in practice.
The skill earns its keep when "Spotlight will find it" stops being true. Four scenarios where it has.
An audit of a year of work.
Run the skill on a ~year-old screenshot folder. The output is a chronologically-sorted narrative of what you were thinking about, week by week — readable from the filename column in Finder. No app needed.
"Find the screenshot of the bug from last March."
Renaming once buys you free-text search forever. mdfind "synqora session load"
surfaces the right file in a fraction of a second, with no manual tagging.
Designer joins. Hands them the folder.
Instead of curating a deck of "what we've shipped this quarter," point them at the renamed screenshot folder. The filenames are the deck. Categorize by app, by feature, by timeline — the descriptions are already there.
A searchable design memory.
Pair with a periodic re-run on new captures. The folder becomes a queryable artifact: every screenshot you took, with what was in it, in plain text, in the filesystem you already use. No new tool to adopt.
What it looks like when you ask.
The skill is conversational at the top, mechanical underneath. You ask in plain English; ten Haiku agents fan out in a single round-trip; Python validates the plan and applies it under the file-count audit. The whole thing fits on one page.
Three commands, one folder.
The skill installs as a Claude Code skill. Once cloned into ~/.claude/skills/, it
activates automatically when you ask Claude to rename a screenshot folder. It can also be
driven from the command line.
install# clone into your Claude Code skills directory git clone https://gitea.tojo.team/cardinale/screenshot-rename.git \ ~/.claude/skills/screenshot-rename
Driven by Claude Code
Open Claude Code in any project and say it conversationally. The skill activates from its description and runs the workflow end to end.
claude code> rename all the cleanshots in ~/Documents/Screenshots/ based on their content.
Driven directly from the shell
For folders too large for a single session, run each stage by hand. Dispatch the Haiku subagents from a Claude Code session in between.
clipython3 pipeline.py prep --src "./shots" # dispatch one haiku agent per batch... python3 pipeline.py plan --src "./shots" python3 pipeline.py execute --src "./shots"