fix(atomic): tolerate FAT-family rename identity drift on exFAT/FAT32 - #231
fix(atomic): tolerate FAT-family rename identity drift on exFAT/FAT32#231dongsheng123132 wants to merge 1 commit into
Conversation
Windows mints a fresh file identity on rename when names exceed the 8.3 short-name budget (dest basenames >=16 chars observed), so replaceFileAtomic failed with path-mismatch on exFAT/FAT32 USB sticks even though the bytes landed correctly. - temp/staging basenames stay within 8.3 (<=12 chars) - post-rename verification re-anchors to the published file's live identity after structural checks plus a staged-bytes equality gate (swapped bytes still fail closed, never rollback) - mkdir -p skipped when the directory exists (FAT USB roots reject mkdir-with-mode with EPERM) - hostile tempPrefix (path separators) still throws
|
🦞👀 Pull request received. I will update this pull request when review starts. ClawSweeper review completeClawSweeper finished reviewing this revision. The review result is being finalized. |
|
Codex review: needs real behavior proof before merge. Reviewed September 5, 2026, 3:17 AM ET / 07:17 UTC. ClawSweeper reviewWhat this changesThe PR shortens staging filenames, accepts matching-content files after rename identity changes, and adjusts directory creation to accommodate FAT-family filesystems. Regression provenancePossible regression — suspected (reviewed change). No predecessor PR is attributed. Merge readiness⛔ Blocked before merge - 15 items remain The reported hardware failure merits follow-up, but this branch introduces blocking ownership and temporary-file regressions. Current main provides an explicit compatibility policy; the complete FAT scenario is not demonstrably resolved there. Priority: P2 Review scores
Verification
How this fits togetherfs-safe stages application data in temporary files, then publishes it by rename. Identity checks determine whether the published file is still the one the operation created. flowchart TD
A[Application data and destination] --> B[Choose temporary filename]
B --> C[Write staged file]
C --> D[Rename to destination]
D --> E{Published identity matches?}
E -->|Yes| G[Report success]
E -->|No| F[Compare content and adopt identity]
F -->|Matching bytes| G
F -->|Different bytes| H[Report mismatch]
Decision needed
Why: The proposed automatic adoption changes a deliberately documented security contract; successful hardware writes alone cannot authorize that policy change. Before merge
Findings
Agent review detailsSecurityNeeds attention: The patch weakens filesystem ownership guarantees through unconditional identity adoption and predictable callback staging; no supply-chain changes were introduced. Review metrics
Merge-risk optionsMaintainer options:
Technical reviewBest possible solution: Preserve strict defaults and implement FAT compatibility through the existing locked policy, with validated, collision-safe temporary names and demonstrated upgrade safety. Do we have a high-confidence way to reproduce the issue? Yes for the introduced defects: source establishes deterministic short-prefix collisions and acceptance of identical-content replacement files in strict mode. The reported physical FAT failure was not independently executed. Is this the best way to solve the issue? No. Main and v0.8.1 already provide explicit locked content verification; FAT-specific adjustments should extend that path without replacing strict semantics or duplicating its verifier. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 899f91b97d26. LabelsLabel changes:
Label justifications:
EvidenceSecurity concerns:
What I checked:
Likely related people:
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
|
|
Closing this implementation after independent review with real filesystem probes. The exFAT/FAT32 rename identity drift you report is a real problem, and thank you for the hardware evidence. The proposed fix, however, changes the security contract for every caller: with the branch's changed modules, explicit strict mode accepted a substituted inode containing identical bytes with mode 0777 (main rejects it with path-mismatch), and tempPrefix "save" consumed an unrelated existing save.tmp (main preserves it). The branch also replaces the shared prefix validation and exceeds the enforced file-size limits. Main already offers an explicit opt-in for this situation via renameIdentity: "verify-content-with-lock", with documented limitations. Transparent acceptance would weaken existing callers without their opting in, so we will not land it in this form. Your report remains useful evidence for future work under that explicit compatibility policy. |
Summary
Windows mints a fresh file identity (new ino) on rename when names exceed the 8.3 short-name budget. Observed on real hardware: destination basenames >=16 chars get a fresh ino after rename on exFAT/FAT32 USB sticks, <=15 chars keep it. replaceFileAtomic then fails with path-mismatch even though the bytes landed correctly -- every atomic write to a USB stick with a normal-length filename fails (e.g. OpenClaw gateway config writes, portable USB apps).
Changes
Test plan
Repro (minimal, Windows + exFAT USB stick)
replaceFileAtomic({ filePath: 'E:/state/xxxxxxxxxxxxxxxx.json', content: 'new' })
before: rejects path-mismatch (bytes correct on disk). after: resolves rename.
Environment: @openclaw/fs-safe 0.8.1, openclaw 2026.9.1, Node v22.22.3, Windows 11, exFAT + FAT32 USB sticks.