Files
rclone-jav/bugs-python.md
T
admin f7fc15b17c Sync working tree before initial Gitea push
Includes:
- cli.py path fix (parents[1]) for config/catalog resolution
- Library cleanup feature design docs (TODO.md, mockup)
- Audit + bug-queue markdowns from May 2026 reliability pass
- .gitignore expanded for transient artifacts
2026-05-26 22:35:42 +02:00

6.4 KiB

Bug Report — Python CLI — audit-snapshot-2026-05-24T15-55Z.md

Snapshot: audit-snapshot-2026-05-24T15-55Z.md Required-reading docs read: AGENTS.md / TODO.md / CACHE_CONTRACT.md (at D:\DEV\Extensions\Production\rclone-jav\docs\CACHE_CONTRACT.md) / bug-audit-plan.md / project memory Auditor agent: fresh Explore agent (chunk 1 auditor) Verifier agents: fresh Explore agents per candidate, blind context, stricter contract-check prompt

This file contains CONFIRMED + PARTIAL findings only. Candidate scratch lives in bugs-candidates-python.md. REFUTED / NEEDS-INFO candidates stay in scratch with verifier response appended.

Chunk 1 calibration note: Moderate verification yielded 1 confirmed bug with 75% rejection rate (3/4 REFUTED). Auditor's recurring weakness: flagging f["key"] direct access as KeyError risk without checking the contract that guarantees the dict shape upstream (rclone lsjson schema, cache.json schema enforced by load_cache validation + CACHE_CONTRACT.md). Stricter verifier prompt with required contract-check caught all 3 false positives. Light candidates were NOT verified per audit-plan stop condition (>30% rejection → halt L verification). The Python auditor likely shares a similar pattern-matching weakness on L candidates — revisit only if needed. See bugs-candidates-python.md for unverified L list (C-5, C-6, C-7, C-8, C-9).


Severe (S)

(none flagged by auditor in this chunk)


Moderate (M)

M-1 — save_config lacks Windows file-locking retry that save_cache has

  • File: D:\DEV\Project\rclone-jav\rcjav\cli.py:186-189 (save_config), with comparison at rcjav/cache.py:142-147 (save_cache)
  • Symptom (one sentence): When a user runs --save while config.json is briefly locked by antivirus, Windows Search indexer, or any reader, os.replace(tmp, CONFIG_PATH) raises uncaught PermissionError and the user sees a Python traceback — config write fails. save_cache for the same os.replace pattern has explicit PermissionError + 0.5s retry; save_config does not.
  • Why it's a bug: Asymmetric protection. save_cache (cache.py:142-147):
    try: os.replace(tmp, CACHE_PATH)
    except PermissionError: time.sleep(0.5); os.replace(tmp, CACHE_PATH)
    
    save_config (cli.py:186-189):
    tmp.write_text(json.dumps(cfg, indent=2), encoding="utf-8")
    os.replace(tmp, CONFIG_PATH)
    
    Single call site at cli.py:465 inside --save flag handler, NOT wrapped in try/except. Outer exception handler at cli.py:1000-1004 catches only KeyboardInterrupt. PermissionError propagates uncaught → Python traceback to user. On Windows with active AV (Defender, Avast, etc.), file-lock-during-replace is common.
  • Reproduction:
    1. Input: user runs python rc-jav.py --save --target cq:JAV while config.json is being read by another process (AV scan, Windows Search indexer reindexing, manual file open in editor)
    2. Expected: write retries briefly + succeeds, OR clear "config write failed, retry" message
    3. Actual: PermissionError raised from os.replace, uncaught, Python prints traceback PermissionError: [WinError 32] The process cannot access the file because it is being used by another process. tmp file may remain on disk. Config not persisted. User confused.
  • Suggested fix sketch: copy save_cache's pattern verbatim — wrap os.replace in try/except PermissionError with 0.5s sleep + single retry
  • Verifier agent: fresh Explore, blind context, stricter prompt
  • Verifier verdict: CONFIRMED
  • Verifier confidence: high (95%)
  • Contract refs verifier read: save_cache implementation as comparison; outer exception handler scope
  • Mirror check needed in: any other os.replace callsite in rcjav/ package that writes user-visible config/state (search for os.replace in rcjav/ — only save_cache and save_config currently)
  • Status: fixed
  • Fix: D:\DEV\Project\rclone-jav\rcjav\cli.py:186-194 — wrapped os.replace(tmp, CONFIG_PATH) in same try/except PermissionError + time.sleep(0.5) + retry pattern that save_cache uses (rcjav/cache.py:142-147). Now symmetric: both writers handle transient Windows file locks identically. Single retry (not infinite) — persistent locks still bubble PermissionError to caller, matching save_cache behavior. time already imported in cli.py:14 — no new import needed. No manifest bump — CLI repo only, no extension files touched. Python syntax verified via py_compile. Smoke-tested in isolation: (1) normal write produces correct file; (2) first os.replace raises PermissionError then succeeds on retry — final state correct, 0.5s sleep observed (2 calls, elapsed 0.50s); (3) persistent PermissionError on both attempts → bubbles up to caller (2 attempts, matches save_cache). Mirror check resolved: only save_cache and save_config use os.replace in rcjav/; both now have retry.

Light (L)

(none promoted — chunk 1 L verification skipped per stop condition)


Needs Input (N)

(none promoted)


False Positives (discarded)

  • rcjav/rclone_io.py:66 — flagged as Moderate "rclone KeyError on Path". REFUTED. rclone lsjson output contract guarantees Path field on every item per official docs. Direct item["Path"] access is appropriate fail-fast for contract violation. Lines 77-78's .get() pattern for Size/ModTime is defensive over-engineering for those fields, NOT evidence Path needs the same.

  • rcjav/library.py:257 — flagged as Moderate "library cache KeyError on path". REFUTED via 3 converging facts: (1) CACHE_CONTRACT.md mandates path key on every file entry, (2) load_cache() (cache.py:67-106) validates schema before find_library_issues runs — non-conformant caches get wiped via _fresh_cache(), (3) FileEntry dataclass + every cache write site explicitly emits path. The .get() pattern at cli.py:526 (--reextract) is defensive because that path reads cache.json directly without re-validation; library.py operates on already-validated data.

  • rcjav/library.py:328-330 — flagged as Moderate "rename_file KeyError on path/jav_id". REFUTED. f comes only from cache entries (remote_data.get("files", [])), which are contract-guaranteed to have path. Caller scalar args (old_rel_path, new_rel_path) are strings, not dicts. Line 330's or f["jav_id"] fallback is for extract_id returning None, NOT for missing key — correct design. Auditor conflated scalar caller args with iterated dict entries.