Skip to content

fix(fetch): make the .json sidecar collision guard case-insensitive#2

Merged
sturlese merged 2 commits into
mainfrom
fix/bughunt-json-collision-case-insensitive
Jul 12, 2026
Merged

fix(fetch): make the .json sidecar collision guard case-insensitive#2
sturlese merged 2 commits into
mainfrom
fix/bughunt-json-collision-case-insensitive

Conversation

@sturlese

Copy link
Copy Markdown
Owner

Context

PR #1 (8755c3b) fixed the .json content-vs-sidecar collision but was merged before the follow-up hardening from that PR's review landed, so main still carries a residual of the same bug. This PR brings the missing fix onto main.

Bug (still live in main)

The .json → .data.json remap in download_file is a case-sensitive exact match, but ext_for returns the extension verbatim. A Drive file named CONFIG.JSON keeps ext .JSON, so its content lands at <fid>.JSON — the same path as the sidecar <fid>.json on 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.JSON for an uppercase-.JSON source).
  • The sidecar path is single-sourced behind 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).
  • The two sync_once guards collapse into one _is_sidecar_path() neutralization of prev_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 evals clean.


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

sturlese and others added 2 commits July 12, 2026 05:14
…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>
The name-only _is_sidecar_path regressed case-sensitive filesystems (Linux/ext4
in production): a legitimately distinct <fid>.JSON content file, which is NOT the
sidecar there, was neutralized as clobbered — forcing a spurious re-download and
orphaning the old file. Decide by samefile() instead: an exact sidecar-name
localPath is always the clobber; a case-variant is the clobber only when it
resolves to the same file as the sidecar (case-folding filesystems).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sturlese

Copy link
Copy Markdown
Owner Author

Gate: adversarial verify → one regression found and fixed (14739ed)

The gate's adversarial verifier confirmed the core fix is complete (no remaining collision, converges in one pass, converter detection unaffected) but caught a low-severity regression on case-sensitive filesystems (Linux/ext4 — the production target):

  • The name-only _is_sidecar_path neutralized prev_local for any case-insensitive sidecar-name match. On a case-sensitive FS a distinct <fid>.JSON content file is not the sidecar, so it was wrongly treated as clobbered → one spurious re-download + the old file orphaned on disk. main's exact-match guard handled this cleanly.

Fix: decide the clobber by file identity (samefile), not name. An exact sidecar-name localPath is always the clobber; a case-variant is the clobber only when it resolves to the same file as the sidecar (i.e. case-folding filesystems). Added test_clobbered_by_sidecar_uses_file_identity_not_name, which locks both filesystem branches deterministically (via a samefile monkeypatch, since a case-sensitive volume can't be created on the macOS dev box).

Full fetch suite: 27 passed, coverage 89% (gate 75%). ruff check pipeline evals clean.

Gate verdict: clean after 1 fix round. Dry-run mode → open for human merge.

@sturlese
sturlese merged commit f8d6144 into main Jul 12, 2026
7 checks passed
@sturlese
sturlese deleted the fix/bughunt-json-collision-case-insensitive branch July 12, 2026 07:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant