fix(sessions): replace append(true) with write(true)+seek to fix Windows file lock (LockFileEx os error 5)#436
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes a Windows-specific bug where OpenOptions::append(true) opens files with FILE_APPEND_DATA access, which doesn't satisfy LockFileEx's requirement for GENERIC_WRITE, causing "Access is denied" (os error 5). The fix uses write(true) + seek(SeekFrom::End(0)) under an exclusive lock.
Changes:
- Replace
.append(true)with.write(true)and add aseek(SeekFrom::End(0))call under the exclusive file lock to preserve append semantics - Add Windows-specific regression test
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Greptile SummaryThis PR fixes a Windows-only file-lock failure ( Key changes in
Confidence Score: 4/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant Caller
participant AppendFn as SessionStore::append
participant OS as OS File API
Caller->>AppendFn: append(key, message)
AppendFn->>OS: OpenOptions write(true) create(true) open(path)
note over OS: Windows: GENERIC_WRITE satisfies LockFileEx
OS-->>AppendFn: File handle returned
AppendFn->>OS: fd_lock RwLock write() — blocking exclusive lock
OS-->>AppendFn: Exclusive lock acquired
AppendFn->>OS: seek(SeekFrom::End(0)) — position cursor at EOF
OS-->>AppendFn: cursor at end of file
AppendFn->>OS: writeln — write line at EOF
OS-->>AppendFn: write complete
AppendFn->>OS: drop guard — release exclusive lock
AppendFn-->>Caller: Ok(())
|
00dc258 to
3cf2565
Compare
|
Removed Test from PR description due to duplication and there should be no host-specific test for this fix |
32da873 to
8ea959c
Compare
…ck on Windows On Windows, OpenOptions::append(true) opens the file with FILE_APPEND_DATA access — a restricted subset of GENERIC_WRITE that LockFileEx rejects with 'Access is denied' (os error 5). Fixes moltis-org#434. Entire-Checkpoint: d5b5fb8650fe
8ea959c to
4570b55
Compare
Entire-Checkpoint: e7b94c8413c7
…correctly Entire-Checkpoint: 33b3df44ce2f
Entire-Checkpoint: c2d42febd038
|
Thanks for the detailed write-up on the Windows I do have concerns about the CI workflow changes bundled in here: 1.
|
|
Hi @penso , thank you for your time to review and leave a feedback to my PR. The whole idea of changing the release action because I need a way to build windows artifact for verifying from my end. I modified the action to build only if dry-run is enabled. But you are right, the release action change should be decoupled from the fix. And there should be a separate action focusing on building the artifact only. |
Entire-Checkpoint: 9807c7643bbc
Summary
Fixes #434.
Root Cause
On Windows,
OpenOptions::append(true)opens the file withFILE_APPEND_DATAaccess — a restricted subset ofGENERIC_WRITEthat explicitly stripsFILE_WRITE_DATA. This is intentional in the Rust stdlib (source):However, the Windows
LockFileExAPI requires:FILE_APPEND_DATAalone satisfies neither requirement, soLockFileExreturnsERROR_ACCESS_DENIED(os error 5) — even with no other process holding the file.This is a known issue in the Rust ecosystem: rust-lang/rust#54118, closed as "by design" — the fix is the caller's responsibility.
Fix
In
SessionStore::append, replace:with:
write(true)maps toGENERIC_WRITE, which satisfiesLockFileEx. Append semantics are preserved by seeking to EOF while holding the exclusive lock —fd_lock::RwLock::write()callsLockFileExwithoutLOCKFILE_FAIL_IMMEDIATELY, so it is a blocking call that serialises all writers at the OS level. No two callers can race between the seek and the write..truncate(false)is added explicitly to silence thesuspicious_open_optionsClippy lint (which fires whenwrite(true).create(true)appears without a declared truncation intent).Cross-platform considerations
LockFileEx/flockaccess requirementLockFileExrequiresGENERIC_READorGENERIC_WRITEERROR_ACCESS_DENIED(os error 5)flock(LOCK_EX)has no access-right requirement — works on any fdThe two existing
replace_historymethods already use.write(true).truncate(true)correctly and are unaffected.save_configand the log/network-filter appenders use a Ruststd::sync::Mutexfor in-process serialisation and never callfd_lock, so they are also unaffected.Changes
crates/sessions/src/store.rs— fixappend()open mode + seek under lockValidation
Completed
cargo test -p moltis-sessions -- store::— 33 tests pass on macOScargo +nightly-2025-11-30 fmt --all -- --checkcargo +nightly-2025-11-30 clippy -Z unstable-options -p moltis-sessions --all-targets -- -D warnings— cleanRemaining
just release-preflight— currently blocked by a pre-existingcmake-not-installed failure inllama-cpp-sys-2on this host, unrelated to this changeManual QA
cargo run)file lock failed: Access is denied. (os error 5)appears in the log