Reference

Test hooks

@openclaw/fs-safe/test-hooks

Internal injection points the fs-safe test suite uses to deterministically reproduce open/lstat races. They are exposed as a public subpath so downstream test suites can reuse the same harness, but they are not part of the supported runtime API.

import {
  getFsSafeTestHooks,
  __setFsSafeTestHooksForTest,
  type FsSafeTestHooks,
} from "@openclaw/fs-safe/test-hooks";

#When the hooks are active

Hooks are only honored when one of the following is true:

  • process.env.NODE_ENV === "test"
  • process.env.VITEST === "true"

Calling __setFsSafeTestHooksForTest(hooks) outside of those environments throws. getFsSafeTestHooks() returns undefined when no hooks are registered, regardless of the environment.

#Shape

type FsSafeTestHooks = {
  afterPreOpenLstat?: (filePath: string) => Promise<void> | void;
  beforeOpen?: (filePath: string, flags: number) => Promise<void> | void;
  afterOpen?: (filePath: string, handle: FileHandle) => Promise<void> | void;
  afterOpenedPathIdentityCheck?: (filePath: string, handle: FileHandle) => Promise<void> | void;
  afterRootReadPathResolution?: (filePath: string) => Promise<void> | void;
  beforeRootReadFinalFence?: (filePath: string, handle: FileHandle) => Promise<void> | void;
  afterRootReadFinalPathIdentityCheck?: (filePath: string, handle: FileHandle) => void;
  beforeArchiveOutputMutation?: (operation: "mkdir" | "chmod", targetPath: string) => Promise<void> | void;
  beforeFileStorePruneDescend?: (dirPath: string) => Promise<void> | void;
  beforeFileStoreSyncPrivateWrite?: (filePath: string) => void;
  beforeRootFallbackMutation?: (operation: "mkdir" | "move" | "remove", targetPath: string) => Promise<void> | void;
  beforeRootStatObservation?: (targetPath: string) => Promise<void> | void;
  beforeRootStatInitialObservation?: (targetPath: string) => Promise<void> | void;
  beforeRootListObservation?: (directoryPath: string, withFileTypes: boolean) => Promise<void> | void;
  afterPinnedWriteFallbackRename?: (targetPath: string) => Promise<void> | void;
  beforeSiblingTempWrite?: (tempPath: string) => Promise<void> | void;
  beforeSidecarLockSnapshotOpen?: (lockPath: string) => Promise<void> | void;
  beforeTrashMove?: (targetPath: string, destPath: string) => void;
  afterPublishTargetCreated?: (method, targetPath, identity) => Promise<void> | void;
  beforePublishDirectorySync?: (method, targetPath, identity) => Promise<void> | void;
};
HookFires when
afterPreOpenLstatA pre-open lstat has just resolved. Use this to swap a path between validation and open.
beforeOpenThe library is about to call open(path, flags). Use this to inject a TOCTOU window.
afterOpenAn open just succeeded. Use this to mutate state before the post-open identity check runs.
afterOpenedPathIdentityCheckA standalone local-file or absolute copy-source descriptor matches its pathname and the generic opened-path resolver is about to run. Root reads use their final admission hooks instead.
afterRootReadPathResolutionA Root read path was resolved and has not yet entered local-file open admission.
beforeRootReadFinalFenceA Root read descriptor passed pre-open/descriptor identity and hardlink checks and is about to enter its single final root/path/canonical-path/root admission fence.
afterRootReadFinalPathIdentityCheckThe final pathname-to-descriptor comparison passed and the second root check has not run. This hook is synchronous-only.
beforeArchiveOutputMutationArchive staging is about to create a directory or apply a mode.
beforeFileStorePruneDescendFile-store pruning is about to descend into a directory.
beforeFileStoreSyncPrivateWriteA synchronous private-store write is about to mutate its target.
beforeRootFallbackMutationA guarded JS root fallback is about to mkdir, move, or remove.
beforeRootStatObservationRoot.stat() admitted its exact target and parent and is about to collect returned metadata.
beforeRootStatInitialObservationRoot.stat() admitted its exact parent and is about to inspect the target for the first time.
beforeRootListObservationRoot.list() admitted its exact selected directory and is about to collect names and optional metadata.
afterPinnedWriteFallbackRenameA fallback rename committed and post-commit identity checks have not run yet.
beforeSiblingTempWriteA sibling temp file exists and its writer is about to run.
beforeSidecarLockSnapshotOpenA sidecar lock was inspected and is about to be opened for a bounded snapshot read.
beforeTrashMoveTrash handling is about to move the target.
afterPublishTargetCreatedExclusive publication created its target and final fences have not run yet.
beforePublishDirectorySyncPublication verified the target and is about to sync its parent directory.

Hooks typed Promise<void> | void may be sync or async and are awaited. Hooks used by synchronous code paths are typed void and must not return a promise.

#Usage

import { afterEach, beforeEach } from "vitest";
import { __setFsSafeTestHooksForTest } from "@openclaw/fs-safe/test-hooks";

beforeEach(() => {
  __setFsSafeTestHooksForTest({
    beforeOpen: async (filePath) => {
      // swap a victim file with a symlink right before fs-safe opens it
      await replaceWithSymlink(filePath);
    },
  });
});

afterEach(() => {
  __setFsSafeTestHooksForTest(undefined);
});

Always clear the hooks in afterEach so a stuck hook does not leak across tests.

#Stability

The shape can grow new optional fields between minor versions. Treat the surface as test-only and do not rely on it from production code.

  • Testing — broader notes on testing against fs-safe.
  • Security model — the races these hooks help reproduce.