Files
screenshot-rename/SKILL.md
T
Anthony Cardinale 63edc33fc4 Initial commit: skill files, docs site, README
- SKILL.md and pipeline.py from ~/.claude/skills/screenshot-rename/
- docs/index.html — archival/typewriter aesthetic homepage with hero
  monument, problem, 4-stage pipeline, before/after split, run-log
  receipt, ten gotchas, four use cases, install snippets
- MIT license

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 09:23:02 -04:00

9.2 KiB

name, description
name description
screenshot-rename Use when renaming a folder of screenshots, images, or short clips with AI-generated descriptive names — particularly CleanShot exports or any directory of images named only by timestamp. Triggers on requests like "rename these screenshots based on their content", "describe each of these images and rename it", or batch rename of files by visual content.

Screenshot Rename

Overview

Rename a directory of timestamp-named images (PNG / GIF / MP4 / PDF) to include AI-generated content descriptions, dispatched as parallel Haiku subagents from this Claude Code session. Each rename has the form:

<original prefix> - <Title Cased Description> - <original timestamp>.<ext>

The pipeline is prep → batch → describe (parallel agents) → validate plan → execute renames with hard data-loss guards at every stage.

Core principle: Plan in memory, validate exhaustively, then mutate the filesystem in a single pass with os.rename and pre-existence checks. Never let mv overwrite — that's how you lose files.

When to Use

  • Renaming CleanShot / screenshot folders by content
  • Any image batch where the source filenames are timestamps and the user wants them human-scannable
  • ≥ ~10 files (otherwise just rename them inline)
  • Files include PNG/GIF and optionally MP4 or PDF (pipeline handles all four)

Don't use for:

  • Code or text files — vision isn't needed
  • Files where the name pattern is already meaningful
  • Single-file rename (just do it directly)

Workflow

1. Prep
   ├─ Extract first frame from each .mp4 (ffmpeg) and .pdf (sips) to /tmp/frames/<base>.jpg
   ├─ Resize every source image to max 1568px on long edge → /tmp/small/<base>.jpg
   └─ Build manifest TSV: <small_image_path>\t<original_filename>

2. Batch
   └─ Split manifest into N batches of ≤ 20 lines each (file: full-batch-NN)

3. Describe (parallel)
   └─ Dispatch N Haiku subagents (model: "haiku") in a single message
      Each agent: reads its batch manifest, uses Read on each image_path,
      writes desc-full-NN.tsv with: <original_filename>\t<6-8 word description>

4. Plan (Python)
   ├─ Aggregate all desc-*.tsv into desc-all.tsv
   ├─ Validate every line: 6+ words, alnum+space only, source exists, target doesn't,
   │  no plan-internal collisions
   ├─ Truncate descriptions to 8 words max, title-case
   └─ Write plan-full.tsv: <original>\t<new_name>

5. Execute (Python, NEVER bash)
   ├─ Read plan, for each line: pre-check src exists & dst doesn't, then os.rename
   ├─ Audit before/after file count — must be equal
   └─ Log failures, report ok/fail counts

The Critical Gotchas (every one of these caused real pain)

  1. Read tool has an image-size cap. Original Retina screenshots can exceed it. Always downscale to ≤ 1568px before handing to a subagent. Use sips -Z 1568 -s format jpeg.

  2. Vision API can't read .mp4 or multi-page .pdf directly. Extract the first frame to a JPEG first (ffmpeg -ss 1 -i in.mp4 -frames:v 1 out.jpg, sips -s format jpeg in.pdf --out out.jpg).

  3. Bash regex with [[ =~ ]] + BASH_REMATCH does NOT work in zsh. zsh uses $match[1] etc. instead. Pattern silently fails, target name becomes empty, multiple mvs collide on the same empty target, files vanish. Use Python for any filename mutation. No exceptions.

  4. mv silently overwrites. A loop that constructs target names from a buggy parse will happily destroy your data. Use mv -n (no-clobber) in shell, or os.rename after os.path.exists(dst) check in Python. Never bare mv.

  5. Pre-flight the full plan in memory before mutating the filesystem. Build a list of (orig, new) tuples; verify every new is unique within the plan, doesn't collide with anything in the destination directory, and that every orig exists. Only then start renaming.

  6. File-count audit. Record len(os.listdir(DEST)) before and after — must be equal. Any drop = data loss.

  7. iCloud-synced trees and Time Machine local snapshots: files in the snapshot are file-provider stubs, not the bytes. cat / cp from a snapshot path inside an iCloud-synced folder returns "Operation timed out" with a 0-byte file. External backups (Backblaze, Time Machine to a real disk) are the actual recovery source for iCloud data, not local APFS snapshots.

  8. Bash background jobs in the Claude Code Bash tool can die silently. A while read loop redirected from a file may exit immediately when run in the background. Run renames foreground via Python — it's the same code path locally and reliably runs to completion.

  9. Haiku occasionally returns the wrong filename extension (the resized .jpg instead of the original .png). The plan-builder must accept that and try alternate extensions when the claimed source isn't found in the destination directory.

  10. Always preserve mp4/pdf source files — the pipeline reads from the resized JPEG but renames the original mp4/pdf. Don't lose the source extension.

Quick Reference

Step Command
Extract mp4 frame ffmpeg -y -ss 1 -i "$f" -frames:v 1 -q:v 3 "$out"
Convert pdf to jpg sips -s format jpeg "$f" --out "$out"
Resize for vision sips -Z 1568 -s format jpeg "$f" --out "$out"
Split TSV into batches of 20 awk -v w=DIR 'BEGIN{n=1;c=0} {print > sprintf("%s/batch-%02d", w, n); c++; if(c>=20){c=0;n++}}'
Dispatch agent Agent tool, subagent_type=general-purpose, model="haiku", run_in_background=true
Execute renames Python os.rename with pre-existence check (NEVER bash mv in a loop)

Reusable Pipeline

The prep, plan, and rename phases are in pipeline.py. The dispatch phase is performed by Claude Code itself (Agent tool calls) and cannot be scripted from inside Python — that's the trade-off of option (b).

Run order:

# 1. Prep + batch
python3 ~/.claude/skills/screenshot-rename/pipeline.py prep \
    --src "/path/to/folder" --batch-size 19

# Now dispatch one Haiku Agent per /tmp/screenshot-rename/full-batch-NN file
# (Claude Code does this — see SKILL.md "Workflow" step 3)

# 2. After all desc-full-NN.tsv files exist:
python3 ~/.claude/skills/screenshot-rename/pipeline.py plan \
    --src "/path/to/folder"

# 3. Review the plan, then:
python3 ~/.claude/skills/screenshot-rename/pipeline.py execute \
    --src "/path/to/folder"

Subagent Prompt Template

Use exactly this prompt for each batch (substitute the batch number):

Describe screenshots so they can be renamed.

Read the manifest at `/tmp/screenshot-rename/full-batch-NN`. Each line: `image_path<TAB>original_filename`.

For EACH line:
1. Use Read on `image_path` (first column) to view the image.
2. Generate a description of EXACTLY 6, 7, or 8 words describing "what app is shown and what the content is". Count your words. Be specific about app names when visible. Use only ASCII letters, numbers, and spaces — NO slashes, colons, dashes, quotes, special characters. Lowercase. 6-8 words.

Output: write `/tmp/screenshot-rename/desc-full-NN.tsv` via Write tool. Each line: `original_filename<TAB>description`. <count> lines total.

Then run `wc -l` on the output file to verify the line count.

Return only "DONE: <count> lines" or an error report.

Dispatch all batches in a single message with multiple Agent tool calls so they run in parallel. Use run_in_background=true so you can keep working.

Common Mistakes

Mistake What goes wrong Fix
mv $f $newname in a bash loop One bug → silent overwrite → data loss os.rename in Python with pre-existence check
Building target name with bash regex zsh doesn't populate BASH_REMATCH; empty targets Use Python os.path.splitext and string ops
Sending original Retina images to Read "Image too large" error mid-batch, partial output Resize to 1568px first
Sending .mp4 to vision Read fails Extract first frame to JPEG first
Skipping the file-count audit Silent data loss goes unnoticed len(os.listdir(DEST)) before & after — must be equal
Trusting Haiku's filename column 30%+ of entries may have wrong extension Plan-builder tries alt extensions
Running rename loop in background Bash run_in_background=true Background while read may exit immediately, 0 progress Run via Python foreground (it's fast — os.rename is just a syscall)

Recovery — if something does go wrong

  1. Check ~/Library/Application Support/CleanShot/media/ — CleanShot keeps a recent media history.
  2. Check external backups (Backblaze, Time Machine to physical disk) — these contain real file bytes.
  3. Local APFS Time Machine snapshots are NOT useful for iCloud-synced files — they store file-provider stubs that time out on read.
  4. Check icloud.com → Drive → Recently Deleted — iCloud keeps deleted files for ~30 days, but mv overwrites are NOT "deletes" from iCloud's perspective and may not appear there.

Real-World Impact

First run on 196 CleanShot files lost 4 of them due to the bash-regex-in-zsh gotcha (rule #3). After the rebuild with Python and mv -n, second run renamed 189 files cleanly with zero loss. This skill exists so that doesn't happen again.