Files
claude-projects/claude-config/config/prompts/media-pipeline/MP-24_filebot_no_overwrite.md
T
2026-10-02 00:19:54 -05:00

6.4 KiB
Raw Blame History

MP-24 — file-monitor: never overwrite a library episode, never delete the only copy (FM-1 + FM-5) — test → GO/NO-GO (spawned 2026-10-01, main session 771b2744)

You are a bounded background agent for media_pipeline (tracking row personal_projects #61). Budget: --max-turns 60. Same tool call twice with identical arguments → STOP with a partial wrap-up.

CONTEXT (owner-approved 2026-10-01 18:09: fix order step 1 of the MP-23 review). Read first:

  • /opt/appdata/docker/docker-compose/media-transcoder/design/REVIEW_2026-10-01_full_code_review.md (FM-1, FM-5, plus FM-2/FM-4 for context only — NOT in scope)
  • Live code: /opt/appdata/docker/docker-compose/filebot-monitor/file-monitor.py (sha256 c0c1c46f… at review time — record it as BASELINE at start; container filebot-monitor-filebot-monitor-1 bind-mounts it :ro at /app/file-monitor.py). Main session verified FM-1 independently: rename_jobs 545 ('Yu Yu Hakusho 001.mkv') and 645 ('Yu Yu Hakusho 101.mkv') both landed on '.../Yu Yu Hakusho - S01E01 - …mkv' (04-06); 105 destination paths in rename_jobs have >1 distinct source.

DECISIONS (pre-made by the main session; apply exactly — ONE feature at a time, verify each before the next):

  • F1 (FM-1, no-overwrite guard): immediately before ANY move/rename into the library (every code path that ends in the library — find them all, incl. the season-1 remap paths at ~1819/1880 and _move_to_destination ~2276), if the destination file already exists: a) if it is the SAME content (same size AND same video duration within 1 s — use ffprobe as the code already does, or size+mtime if no probe helper exists; say which) → treat as already done (log, no overwrite, remove the incoming duplicate only AFTER confirming the library copy is intact); b) otherwise → NEVER overwrite. Move the incoming file to a conflicts dir <Media to be Renamed root>/_conflicts/<show>/<original filename> (create dirs; never overwrite inside it either — append .1, .2), record rename_jobs status conflict with error_msg naming both paths, and send ONE ntfy alert (existing notify helper, existing topic) "FileBot rename conflict: already holds a different file; incoming parked in _conflicts". No FileBot --conflict override anywhere: if the code passes --conflict override (or similar) to filebot, change it to --conflict skip and treat FileBot's skip result as case (b).
  • F2 (FM-5, no early delete): the finished transcode must exist in at least one of {its input location, the library destination, _conflicts} at EVERY point. Any rmtree/unlink of a job/temp dir that may still hold the only copy must first move any remaining video file back to the input location (or _conflicts if that path is now occupied). Concretely: after FileBot (which runs with --action move into staging), if any later step fails, move the staged file(s) back instead of deleting the staging dir. Never delete a source/staged file until the library destination is verified (exists, size matches).
  • Out of scope: FM-2 (double processing), FM-3/FM-4, DL-, T-. Do not fix them even if you see them; note any interaction.

TEST APPROACH (decided — the real tier-3 FileBot sandbox is deliberately not built: MP-12 owner decision, no docker.sock on server-01 because prod services run there):

  • Build a hermetic test: copy file-monitor.py to your working copy at /opt/appdata/docker/docker-compose/server-01/media-pipeline-sandbox/file-monitor.py (that path is the sandbox twin; it exists — overwrite it with the LIVE copy first, record its old sha256 in notes). Tests go in a new /opt/appdata/docker/docker-compose/server-01/media-pipeline-sandbox/test_mp24_filebot_guard.py using pytest + tmp_path dirs and a FAKE filebot: monkeypatch the function(s) that shell out to docker exec filebot-cli filebot so they "rename" deterministically (move file to a computed destination name) and can simulate: success, FileBot skip, FileBot failure, and a failure in a later step. Monkeypatch DB/ntfy/media-api calls to recorders.
  • Required scenarios (each must pass on NEW code; F1 cases 1–3 must FAIL on BASELINE to prove the bug):
    1. YYH case: library already has 'Show - S01E01 - X.mkv' (content A); incoming different file B maps to the same name → A untouched, B in _conflicts, rename_jobs status conflict, 1 ntfy.
    2. Same-content re-delivery → no overwrite, duplicate removed only after the library copy verified.
    3. Season-1 remap path collision (use the code's remap branch) → same as 1.
    4. FM-5: later step fails after FileBot moved the file → file back in input (or _conflicts), staging empty, no data loss. Must lose the file on BASELINE.
    5. Happy path unchanged: no existing destination → normal move, status done.
    6. Conflict dir name collision → .1 suffix, nothing overwritten.
  • Also run the existing sandbox test files (python3 -m pytest -q test_*.py in that dir) — all must still pass.
  • Then a LIVE-SAFE dry check: python3 -m py_compile; import the module with its startup side effects disabled if the code allows (do NOT run the real main loop).

HARD RULES

  • NO edit to the LIVE file-monitor.py, NO container restarts (live or sandbox), NO git, NO Postgres writes (prod DB read-only SELECTs only), NO sudo (⏸ OWNER-COMMAND-REQUEST block if needed: host, command, why, paste_back, resume_at). Do not touch the media library or any real media file. Mask secrets; never dump env.
  • Do not make design decisions beyond F1/F2; if the code contradicts this block (e.g. no --action move, a different library-write path), STOP and report precisely.

PROMOTION: NOT in this run. On GO, the main session reviews the diff and promotes (cp keeps the inode; owner restarts filebot-monitor). Put in your output the exact promote + verify + restart commands with the BASELINE guard.

PERSIST: append "## MP-24 — 2026-10-01 (background agent)" to /opt/appdata/docker/docker-compose/media-downloader-local/.claude/context.md (what/decisions/state/next); run python3 /opt/appdata/docker/.claude/scripts/embed_memory_dir.py --only-recent 5. FINAL message = wrap-up JSON only: status, project "media_pipeline", verdict (GO|NO-GO), baseline_sha256, working_copy_sha256, diff_summary, library_write_paths_found[], scenarios[] (name, baseline_result, new_result, pass), tests_passed, promote_commands, interactions_with_out_of_scope_bugs[], actions_taken[], actions_failed[], unverified_claims[], next_step, notes.