Behavior changes (all opt-in or safety-first):
- prep refuses to operate inside .photoslibrary, .lrlibrary, .aplibrary,
.fcpbundle, .band, .logicx, .app, etc. unless --allow-app-libraries
- --year YYYY restricts to files whose embedded ts (or btime) starts with YYYY
- --include-untagged accepts hand-named image files (no CleanShot/Screenshot
prefix) and dates them via stat btime → mtime fallback. Gated on the folder
containing ≥10 tagged matches to prevent sweeping ~/Pictures or similar
- prep pre-pass auto-normalizes the missing-space typo
('foo barCleanShot 2026-...' → 'foo bar CleanShot 2026-...') by os.rename
- plan now iterates the desc-tsv contents instead of the full src dir, with
alt-extension fallback for Haiku's occasional .jpg-instead-of-.png echo
- build_new_name supports app=None (untagged) — emits
'<keywords> - <Description> - YYYY-MM-DD.ext'
SKILL.md: gotchas #14-17 documenting each new guard, run-order updated
with the new flags, common-mistakes table extended.
Verified by smoke test with seeded files: --year filter, --include-untagged
threshold gate, app-library refusal, and typo normalization all behave.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
screenshot-rename
A Claude Code skill that turns a folder of timestamp-named screenshots
into a folder of human-readable, searchable filenames —
using parallel Haiku vision agents.
Homepage · SKILL.md · pipeline.py · MIT
CleanShot 2026-04-15 at 09.14.07.png
↓
CleanShot - Shamel Studio Affiliate Referral Code Modal - 2026-04-15 at 09.14.07.png
Built for CleanShot-style screenshot folders, but works on any directory of .png / .gif / .mp4 / .pdf files named only by timestamp. Recognizes both CleanShot ... and Apple Screenshot ... filenames in the same pass, preserves any leading user-typed keyword prefix, and is safe to re-run on a folder that's already partially renamed.
Highlights
- Parallel — describes ~200 files in 3 minutes using 10 concurrent Haiku subagents.
- Safe — pre-builds the full rename plan in memory, validates uniqueness and target collisions, then renames atomically with a file-count audit. Designed after losing 4 files to a
mvoverwrite during prototyping. - Multi-prefix — same pipeline handles
CleanShot ..., AppleScreenshot ..., and files with hand-typed leading keywords (e.g.jojo travel CleanShot ...). - Idempotent — re-running on a folder skips files already in the renamed
App - Description - timestamp.extform. No description-stacking. - Handles video / PDF — extracts the first frame so vision agents can describe them.
- Resizes for the vision tool — Retina screenshots exceed Read's image cap; pipeline downsamples to 1568 px max.
A session, end to end
The skill activates when you ask Claude conversationally. Behind the scenes it preps the folder, fans out ten Haiku agents in a single round-trip, validates the resulting plan, then applies the renames in a single Python pass with a file-count audit at the end.
Installation
This is a Claude Code skill. Drop the repo into ~/.claude/skills/:
git clone https://gitea.tojo.team/cardinale/screenshot-rename.git \
~/.claude/skills/screenshot-rename
In your next Claude Code session, ask:
rename all the cleanshot files in
~/Documents/Screenshots/based on their content
The skill will activate automatically from its description.
Usage from the command line
You can also drive the pipeline directly:
# 1. Prep — extract frames, resize, build batches
python3 pipeline.py prep --src "/path/to/folder" --batch-size 19
# 2. (In a Claude Code session, dispatch one Haiku subagent per
# /tmp/screenshot-rename/full-batch-NN file using the prompt template
# in SKILL.md.)
# 3. Plan — aggregate descriptions, validate, build rename map
python3 pipeline.py plan --src "/path/to/folder"
# 4. Execute — apply the plan, audit file count
python3 pipeline.py execute --src "/path/to/folder"
The dispatch step (#2) currently requires a Claude Code session.
What the parser accepts
| Form | Recognized | Becomes |
|---|---|---|
CleanShot 2026-MM-DD at HH.MM.SS.png |
yes | CleanShot - <description> - 2026-MM-DD at HH.MM.SS.png |
Screenshot 2026-MM-DD at H.MM.SS PM.png (with U+202F) |
yes — U+202F normalized to ASCII space | Screenshot - <description> - 2026-MM-DD at H.MM.SS PM.png |
<keywords> CleanShot 2026-MM-DD at HH.MM.SS.png |
yes — keywords title-cased and prepended to the AI description | CleanShot - <Keywords + description> - 2026-MM-DD at HH.MM.SS.png |
App - <description> - 2026-MM-DD at HH.MM.SS.png |
already renamed → skipped | (unchanged) |
The gotchas this skill encodes
This skill exists because every one of these caused real damage during development:
- The
Readtool has an image-size cap. Resize first. - Vision can't read
.mp4or multi-page.pdfdirectly. Extract a frame. - Bash regex
[[ =~ ]]does NOT populateBASH_REMATCHin zsh. Targets become empty. Loops collide on the same filename. Files vanish. Use Python for any filename mutation. mvsilently overwrites. Usemv -noros.renamewith explicit pre-existence check.- Pre-build the entire rename plan in memory and validate uniqueness before any
mv. - Audit
len(os.listdir(DEST))before and after. Equal count == proof no overwrites. - iCloud-synced files in Time Machine local snapshots are file-provider stubs, not bytes. External backups (Backblaze, Time Machine to physical disk) are the real recovery source.
Bash run_in_backgroundmay exit early onwhile readloops. Run renames foreground via Python.- Haiku occasionally returns the resized
.jpgfilename instead of the original.png. Validator must try alt extensions. - Always preserve the original
.mp4/.pdfextension — describe via the extracted frame, rename the source. - macOS
Screenshotfilenames contain U+202F (NARROW NO-BREAK SPACE) before AM/PM. Haiku echoes it as ASCII space, so a verbatim filename lookup misses every Screenshot file. Normalize on both sides of the lookup; emit ASCII space in the new name. - Re-running is only safe if the parser skips already-renamed files. Detect
^App - .+ - timestamp.ext$and exclude. - Leading user-typed keyword prefix is signal, not noise. Title-case the keywords and prepend them to the AI description before assembling the new name.
The full discussion is in SKILL.md.
Real-world impact
| Run | Files | What happened |
|---|---|---|
| 1 | 196 CleanShot | Lost 4 to the bash-regex-in-zsh gotcha (#3). |
| 2 | 196 CleanShot | Rebuilt with Python and mv -n — 189 renamed cleanly, zero loss. |
| 3 | 20 mixed (CleanShot + Apple Screenshot + one user-prefixed) | First plan attempt dropped every Screenshot file with a misleading NO_DESC error. Diagnosed the U+202F gotcha (#11) via repr() of the live filename. After adding U+202F normalization, multi-prefix support, and keyword preservation — all 20 renamed in one pass. |
Roadmap
- Direct Anthropic API mode (no Claude Code session required) — needs
ANTHROPIC_API_KEY - Custom prompt templates per-folder
- Optional preservation of dots in technical strings (
v2.1currently becomesV21) - Dry-run flag on
execute
License
MIT — see LICENSE.
