fix(write): tolerate raw stat modes with file-type bits in dirMode - #228
Conversation
0.8.0 regressed input tolerance for replaceFileAtomic({ dirMode }): a
raw fs.stat mode such as 0o40755 (S_IFDIR | 0o755) was compared
unmasked against the pinned directory's masked mode (0o7777), failing
with 'directory final mode could not be verified' even though chmod
ignores file-type bits.
Mask requested directory modes to 0o7777 at the ownDirectoryMode.apply
comparison point (covering applyDirectoryMode, sibling-temp,
secret-file, and archive-merge) and before the synchronous fchmod in
applyDirectoryModeSync. Add a regression test passing a raw stat mode
through async and sync replaceFileAtomic.
|
🦞👀 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 maintainer review before merge. Reviewed September 4, 2026, 6:34 PM ET / 22:34 UTC. ClawSweeper reviewWhat this changesThe PR masks raw Regression provenancePossible regression — probable (reviewed change; failure trace). No predecessor PR is attributed. Merge readiness✅ Ready for maintainer review Keep open: this is a necessary, narrowly scoped fix for a released directory-mode regression, with no actionable defect found in the introduced patch. Priority: P2 Review scores
Verification
How this fits togetherAtomic write helpers pin and verify a parent directory before changing its permissions and publishing a replacement file. A caller-provided directory mode flows through descriptor-based chmod and verification while confinement and directory identity checks remain in place. flowchart LR
A[Caller directory mode] --> B[Atomic write helper]
B --> C[Pinned directory descriptor]
C --> D[Mask permission bits]
D --> E[Apply and verify mode]
E --> F[Replacement file]
C --> G[Directory identity check]
Before mergeNone. Agent review detailsSecurityNone. Review metrics
Technical reviewBest possible solution: Land the shared boundary normalization and its async/sync regression coverage so raw directory stat modes retain their documented chmod-equivalent behavior without changing identity or confinement safeguards. Do we have a high-confidence way to reproduce the issue? Yes—source-reproducible with high confidence: current main compares a permission-only inspected mode to an unmasked caller value, and the supplied POSIX regression run reports the exact base failure and fixed result. Is this the best way to solve the issue? Yes. Normalizing at descriptor application and verification preserves all AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 5872f54f62a7. LabelsLabel changes:
Label justifications:
EvidenceWhat I checked:
Likely related people:
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
|
Problem
0.8.0 regressed input tolerance for
replaceFileAtomic({ dirMode }): passing a rawfs.statmode (e.g.0o40755fromstat.mode, which includes theS_IFDIRtype bits) now fails withpath-mismatch: directory final mode could not be verified.Root cause:
ownDirectoryMode.apply()compares the requested mode unmasked against the pinned directory descriptor's mode, whichinspect()reports masked to0o7777. chmod itself ignores file-type bits, so pre-0.8.0 tolerated the raw value; the final-mode verification introduced in 0.8.0 did not.This broke
openclaw/openclaw'sdoctor-session-transcripts.test.tsin CI (openclaw/openclaw#138500).Fix
Mask requested directory modes to
0o7777:src/directory-mode-owner.ts— at theapply()comparison point, covering every owner-based entry point (applyDirectoryMode/replaceFileAtomic,sibling-temp,secret-file,archive-merge).src/replace-file-descriptor.ts— before the synchronousfchmodSyncinapplyDirectoryModeSyncfor parity (that path has no verification compare, but the mask keeps requested-mode semantics consistent).This widens input tolerance only; the mode actually applied and verified is unchanged, so no confinement or identity boundary is weakened.
Regression test
test/atomic-dirmode-regression.test.tsnow passes a rawstat.mode(asserted to includeS_IFDIR) asdirModethrough both asyncreplaceFileAtomicandreplaceFileAtomicSync, verifying success and the final0o755mode.Proof
FsSafeError: directory final mode could not be verifiedatdirectory-mode-owner.ts:76; passes with the fix.pnpm vitest run test/atomic-dirmode-regression.test.ts test/sibling-temp-directory-mode.test.ts test/directory-mode-owner.test.ts→ 21 passed.pnpm lint:file-size && pnpm lint:fs-boundary && pnpm build && pnpm docs:check && node scripts/check-pack.mjs→ all pass.pnpm test: remaining failures (consumer-pnpm-lifecyclepnpm-CLI environment requirement, plus timing-sensitive stress/archive tests under parallel load) reproduce identically on cleanmain; every one passes when re-run serially with this change applied.archive-directory-publicationandarchive-publication-modes(closest to the change) pass 3/3 repeat runs.scoped-clean, no accepted/actionable findings.