In-place writes
overwriteFileHandle() replaces a regular file's contents through a handle the caller already owns. Use it when replacing the inode would break hardlinked aliases, or when an existing writable file lives in a directory where the caller cannot create a sibling temporary file.
import { open } from "node:fs/promises";
import { overwriteFileHandle } from "@openclaw/fs-safe/advanced";
const handle = await open(filePath, "r+");
try {
await overwriteFileHandle(handle, Buffer.from(nextContents, "utf8"), {
beforeWrite: () => assertCurrentOwner(),
});
} finally {
await handle.close();
}
#Contract
overwriteFileHandle(
handle: FileHandle,
data: Uint8Array,
options?: { beforeWrite?: () => void },
): Promise<void>;
The handle must be readable and writable, refer to a regular file, and have been opened without append mode. Node cannot portably inspect a handle's append flag, and some operating systems ignore positioned-write offsets for append handles. Opening, path admission, symlink and hardlink policy, and closing remain the caller's responsibility. The helper never reopens or replaces the inode, changes the handle's current position, or closes it. Every hardlinked alias sees the in-place changes.
The payload is borrowed, including its byte offset and length. Keep it unchanged, attached, and accessible until the returned promise settles. Do not close the handle or run concurrent I/O against the file, including through other aliases, during preparation, writing, or rollback. This helper does not acquire a lock.
#Preparation, ordering, and failure
The helper checks the file type and original size, then saves only the prefix that will be overwritten: min(data.byteLength, originalSize) bytes. A short prefix read fails with FsSafeError("read-failed") before writing. Memory for the backup is bounded by that prefix length; a short replacement of a large file does not read or buffer the untouched tail.
After preparation, beforeWrite runs synchronously once. A thrown value is propagated unchanged without any file mutation. A Promise or thenable return is rejected with TypeError; asynchronous callbacks cannot admit a write. Once admitted, the operation finishes the write or its failure recovery without calling beforeWrite again. This callback is a whole-operation admission point, not the per-mutation assertBeforeMutation callback used by Root operations. It must not change the file, handle, or payload. There is no cancellation option.
#Awaited authority preparation
Use overwriteFileHandleWithAuthority() from @openclaw/fs-safe/advanced when source validation requires awaited work immediately before admission:
overwriteFileHandleWithAuthority(
handle: FileHandle,
data: Uint8Array,
prepareAuthority: () => Promise<() => void>,
): Promise<void>;
It reads handle metadata asynchronously, performs rollback-prefix preparation, then awaits prepareAuthority() once. The preparer must return a synchronous assertion of current authority. That assertion runs immediately before the first mutation, with no intervening await. Missing assertions and Promise, thenable, or generator assertion results reject before any write. Preparation or assertion failures propagate unchanged. Once admitted, writing and recovery finish without another authority check, exactly as for overwriteFileHandle().
The caller owns off-thread validation: this helper does not move the preparer or assertion to another worker. Use the preparer for worker-backed storage or identity validation and the final assertion for current in-memory revocation or revision checks. Do not call blocking storage work inside either callback and assume that await offloads it. The handle, file, and payload ownership restrictions above also apply while preparation is pending. This API does not alter Root's per-mutation hooks or make the existing beforeWrite callback asynchronous.
#Writing and recovery
For growth, additional tail bytes are written before the existing prefix is touched. The prefix is then overwritten, completing partial writes. For shrinkage, truncation is last. If a write or truncation fails, the helper attempts to restore the saved prefix, if touched, and the original length. It waits for both recovery attempts and then rethrows the original error, even when recovery also fails. Zero-progress writes fail with FsSafeError("helper-failed").
Recovery is best effort, not atomic publication or a crash-recovery guarantee. Other observers can see intermediate contents; failed recovery can leave partial bytes. The helper does not chmod or synchronize the file or its directory. Normal filesystem effects such as timestamp updates or clearing special mode bits can still occur. Callers own any durability or broader transaction policy.
The implementation uses Node positional reads and writes in every native mode; it does not load a native binding. Prefer Root.write when atomic replacement and root-based path admission are the intended contract.