…tralize it
Review of the first commit surfaced a residual of the same bug: the .json
remap was an exact-string check, but ext_for returns the extension verbatim,
so a file named e.g. CONFIG.JSON kept ext .JSON and its content landed at
<fid>.JSON — the same path as the sidecar <fid>.json on a case-folding
filesystem (macOS APFS, Windows, Docker Desktop bind mounts), re-triggering
the clobber. Fold the disambiguation into a content_name() helper that
compares against the sidecar case-insensitively, single-source the sidecar
path behind sidecar_name(), and collapse the two sync_once guards into one
_is_sidecar_path() neutralization of prev_local.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Context
PR #1 (
8755c3b) fixed the.jsoncontent-vs-sidecar collision but was merged before the follow-up hardening from that PR's review landed, somainstill carries a residual of the same bug. This PR brings the missing fix ontomain.Bug (still live in main)
The
.json → .data.jsonremap indownload_fileis a case-sensitive exact match, butext_forreturns the extension verbatim. A Drive file namedCONFIG.JSONkeeps ext.JSON, so its content lands at<fid>.JSON— the same path as the sidecar<fid>.jsonon a case-folding filesystem (macOS APFS, Windows, Docker Desktop bind mounts). The sidecar write then clobbers the content, exactly the failure PR #1 set out to close.Fix
content_name()now owns the content-filename decision and disambiguates against the sidecar case-insensitively (<fid>.data.JSONfor an uppercase-.JSONsource).sidecar_name()(was duplicated across 5 sites — reuse/altitude findings from the PR fix(fetch): stop the metadata sidecar from clobbering .json file content #1 review).sync_onceguards collapse into one_is_sidecar_path()neutralization ofprev_local(simplification finding), which also makes the heal path case-insensitive.Test
New regression test
test_sync_once_uppercase_json_content_does_not_collide_with_sidecar, verified to fail against the case-sensitive version now in main. Full fetch suite: 26 passed, coverage 90% (gate 75%).ruff check pipeline evalsclean.Found by the autonomous bughunt loop: iteration 2 detected that the merged PR #1 was incomplete (the review's own hardening commit never reached main) and re-shipped the missing fix. Dry-run mode — human merges.
🤖 Generated with Claude Code