FSEvents on macOS: Persistent Change Journals, Event Coalescing, and Rescan Boundaries
A correct mental model for macOS FSEvents streams, path coalescing, event IDs, per-disk persistence, dropped-event flags, exclusions, and rescanning.
FSEvents tells macOS applications that file-system hierarchies changed. It is optimized for efficient directory-tree monitoring and persistent catch-up, not for producing a lossless transaction record of every syscall. Events may be coalesced, paths may already have changed again when a callback runs, and explicit flags tell clients when incremental state is no longer trustworthy.
Streams describe changed regions
A client creates an FSEventStream for one or more root paths, chooses a starting event ID and latency, schedules the stream, and receives batches. A recursive backup scanner can then revisit affected directories instead of polling the entire volume. The event path is a reason to inspect current state; it is not guaranteed to name an object that still exists.
Default events are directory-granular and can combine several nearby mutations. The file-events creation flag requests finer events, but it still does not turn FSEvents into a synchronous audit log. Rename sequences, rapid replacement, hard links, and cache pressure can make reconstruction from individual names unsafe.
Latency trades prompt delivery for batching. A UI updater might choose a short latency; a background indexer can accept a longer window to reduce wakeups. kFSEventStreamCreateFlagNoDefer changes when the first event in a burst is delivered, which affects responsiveness and energy use.
Event IDs support catch-up with scope caveats
The service assigns event IDs and journals changes on supported local volumes. A client can persist the last completed ID and ask for events since then after restart. Persist the checkpoint only after durable processing of the batch; acknowledging first can create a silent gap after a crash.
Host-level IDs are convenient across watched paths, while per-disk streams avoid ambiguity when disks move between systems. Event ID histories from different hosts can collide or be reordered when volumes are attached. A durable index should bind its checkpoint to a volume identity and chosen stream model, not store one unexplained integer globally.
The journal has finite history. If the requested ID is too old or events are dropped, flags such as MustScanSubDirs, UserDropped, or KernelDropped require a full rescan under the affected root. Continuing incremental application after one of these signals turns a visible overflow into hidden stale state.
if event_flags include MustScanSubDirs:
mark root inconsistent
enumerate authoritative disk state
rebuild index
persist a new checkpoint only after completion
Treat root changes and device mount/unmount flags similarly. A watched tree can be replaced or disappear; verify device and object identity before attaching old index state to a new pathname.
Correct clients reconcile current truth
During initial setup, changes can occur between enumeration and stream start. One safe pattern records a starting ID, scans, then consumes all later events and reconciles. Another starts the stream and queues events while scanning. Both require idempotent indexing and a final comparison.
Respect privacy and access controls. Full Disk Access may be required for protected data; the absence of permission is not evidence that a directory is empty. Exclusions, ignored self-generated writes, symlinks, packages, and file-provider locations need an explicit product policy.
Test rename storms, atomic-save editors, large tree deletion, event queue pressure, sleep/wake, reboot, disk detach, checkpoint rollback, and permission denial. Compare the resulting index to a fresh authoritative crawl. FSEvents delivers scalable invalidation and catch-up; correctness comes from checkpoints, identity, flag handling, and a rescan procedure that is exercised rather than merely documented.
Related:
- Apple’s Endpoint Security Framework: Auth Events, Notify Events, and Safe Client Design
- How to Inspect and Deploy macOS Configuration Profiles Without Treating Them as Scripts
Sources: