An inotify file descriptor exposes filesystem activity as an ordered queue of event records, but that queue is not an authoritative history of namespace state. Identical unread events may be coalesced, queue capacity is bounded, and an overflow explicitly means events have been lost. A process that treats the stream as a complete transaction log can therefore preserve a state that no longer matches the filesystem.

The interface is better modeled as change notification with recovery obligations. Events can make a cache current incrementally while the stream remains intact; some conditions invalidate that incremental history and require reconciliation against filesystem state.

Event records describe observations, not transactions

inotify_init1() creates an inotify instance backed by an event queue. Watches added with inotify_add_watch() associate watch descriptors with filesystem objects and event masks. Reading the inotify descriptor returns one or more variable-length struct inotify_event records.

Each record contains a watch descriptor, mask, cookie, and optional child name. These fields identify an observed event in the monitored scope. They do not provide a filesystem-wide transaction identifier, a snapshot version, or a durable sequence number.

The distinction becomes important when one logical operation generates several records. Moving a file between watched directories can produce IN_MOVED_FROM for the source directory and IN_MOVED_TO for the destination. Their shared nonzero cookie lets an application associate the pair, but the pair is still delivered through the ordinary event queue.

Other events may appear between the two records. The pair is not guaranteed to enter the queue atomically. A consumer must therefore tolerate a source-side move record whose destination-side partner is not yet visible.

Rename cookies correlate events without creating atomic delivery

A rename cookie is correlation metadata, not a transaction boundary. It allows matching related move records when both sides are observed, but it cannot guarantee that both records exist in a consumer’s monitored scope.

If a file moves out of a watched directory into an unwatched directory, the consumer can receive IN_MOVED_FROM without a corresponding IN_MOVED_TO. A move into the watched scope can produce the inverse shape. Queue overflow can also destroy records that would otherwise form a pair.

This makes timeout-based rename matching an application policy rather than a kernel semantic. A consumer may temporarily retain unmatched move records, but it eventually needs to classify them according to its own cache model. The cookie itself does not state that a missing partner will arrive.

Pathnames add another race. An event names a child as it was associated with the watched directory for that event. By the time user space processes the record, later operations may already have renamed or removed that path. Resolving the event name again queries current filesystem state, not the historical object state at event-generation time.

Coalescing removes event-count semantics

Linux may coalesce successive unread inotify events when their watch descriptor, mask, cookie, and name are identical. This reduces queue pressure, but it also removes any reliable mapping between event-record count and operation count.

A consumer can use IN_MODIFY as a signal that content changed. It cannot infer that three writes occurred because it received three modification records, nor infer that one write occurred because it received one.

This boundary is relevant for counters, audit trails, and replication protocols. If the application requires every mutation as a distinct durable fact, inotify’s notification semantics are insufficient on their own. The filesystem state or another transactional source must carry the authoritative information.

Coalescing does not make the resulting notification false. It changes the granularity of observation. One record can stand for multiple equivalent changes that occurred before the older record was consumed.

Queue overflow invalidates incremental cache history

The kernel limits the number of queued events for an inotify instance. When that limit is exceeded, excess events are dropped and the stream reports IN_Q_OVERFLOW with a watch descriptor of -1.

After overflow, the consumer does not know which changes were omitted. Continuing to apply later events to a cache cannot reconstruct that missing interval in general. A create, rename, and delete could all have occurred while records were lost, leaving no later notification that reveals the intermediate path history.

For a cache whose required invariant is “matches current filesystem state,” overflow is therefore a reconciliation boundary. The application can rebuild the affected view from the filesystem, recreate watches when appropriate, and resume incremental processing from a newly established baseline.

The exact recovery scope depends on the cache. A process maintaining one directory may rescan that directory. A process maintaining a recursive model may need broader reconstruction because lost directory moves can invalidate cached descendant paths.

Recursive monitoring contains a watch-installation race

An inotify watch on a directory does not recursively cover its descendants. Recursive monitoring is built in user space by adding watches for subdirectories.

That creates a temporal gap for newly created or newly moved-in directories. The parent event can tell the process that a subdirectory appeared, but files may be created inside that subdirectory before the process installs its new watch. Those child operations need not be represented in the event stream seen by the consumer.

A common cache design compensates by scanning a new subdirectory after installing its watch. The scan establishes current state for the gap; later notifications maintain it incrementally. This still requires careful reconciliation because filesystem changes can continue during the scan.

The mechanism illustrates a broader boundary: a recursive watcher is a user-space composition of per-object watches, not an atomic recursive subscription established across a directory tree.

Watch descriptors are handles that need pathname bookkeeping

Events identify watches by integer watch descriptor. Applications that expose path-oriented state usually maintain a mapping from watch descriptor to pathname or another internal object identity.

Directory moves make that mapping stateful. If a watched directory moves within a monitored tree, cached paths for its descendants can become stale even though their watch descriptors remain useful. Updating only the moved directory’s displayed path can leave descendant entries attached to old prefixes.

Deletion and unmounting add lifetime transitions. IN_IGNORED reports that a watch was removed, whether explicitly or because the watched object disappeared or its filesystem was unmounted. Consumers need to treat watch identity as having a lifecycle rather than as a permanent pathname alias.

Hard links further separate object identity from path identity. Multiple pathnames can refer to one inode, while a watch is associated with the filesystem object reached when the watch is installed. A path cache layered over that mechanism must define which names it represents.

Notification and authority belong to different layers

inotify works well as a trigger for invalidation, reload, indexing, or cache maintenance when the consumer accepts its recovery model. Its file descriptor also integrates naturally with poll() and epoll, so filesystem notifications can share an event loop with sockets, timers, and other readiness sources.

Those integration properties do not strengthen the stream into a journal. Ordered delivery applies to records that reach the queue. Rename cookies correlate related move events that are observed. Neither property repairs coalesced operation counts, events lost to overflow, recursive watch gaps, or path state that changed before user space processed a record.

A robust design therefore assigns authority explicitly. inotify carries incremental hints about change; the filesystem supplies current namespace and metadata state; application storage may supply stronger history when exact mutation records are required. Once the notification stream reports that its history is incomplete, correctness comes from rebuilding against an authoritative layer rather than extrapolating from the remaining events.