Skip to content

🐛 [BUG] Infinite OpenAPI regeneration loop: IN_OPEN in inotify mask #137

@rsisson

Description

@rsisson

Category: bug

Description

apx dev start enters an infinite OpenAPI regeneration loop on WSL2 (Linux 5.15.167.4-microsoft-standard-WSL2). The loop fires "Python change
detected, regenerating OpenAPI…" every ~2 seconds continuously, causing constant Vite HMR updates that break interactive UI components (e.g.,
editable table cells lose focus on re-render). The apx process consumes ~22% CPU during the loop.

Root Cause

The file watcher (crates/core/src/dev/watcher.rs) registers inotify watches with mask 0x3EE, which includes IN_OPEN (0x020). This means any file
opened for reading triggers an event.

The OpenAPI regeneration subprocess (uv run apx __generate_openapi) opens files across the project tree to build the schema. These read-only opens
generate IN_OPEN events that apx interprets as "Python changes", triggering another regeneration — an infinite loop.

Evidence

  1. inotifywait -e open captured 2,700 OPEN events in 8 seconds during the loop — all from the regeneration subprocess reading project files
  2. inotifywait -e modify,create,delete shows zero actual file changes after initial startup (when api.ts is read-only), yet the loop continues
    — proving IN_OPEN events are the trigger
  3. The inotify mask from /proc/pid/fdinfo/fd is 0x3EE = IN_MODIFY | IN_ATTRIB | IN_CLOSE_WRITE | IN_OPEN | IN_MOVED_FROM | IN_MOVED_TO |
    IN_CREATE | IN_DELETE
  4. apx registers 5,299 inotify watches across the project tree

Workaround

An LD_PRELOAD shim that strips IN_OPEN and IN_ATTRIB from inotify_add_watch calls completely fixes the issue. With the fix:

  • No regeneration loop after startup
  • File change detection still works correctly (tested: editing router.py triggers exactly one regeneration cycle, then stops)
// fix_inotify.c — compile with: gcc -shared -fPIC -o fix_inotify.so fix_inotify.c -ldl
#define _GNU_SOURCE
#include <stddef.h>
#include <dlfcn.h>
#include <sys/inotify.h>

int inotify_add_watch(int fd, const char *pathname, uint32_t mask) {
    typedef int (*orig_fn)(int, const char *, uint32_t);
    static orig_fn orig = NULL;
    if (!orig) orig = (orig_fn)dlsym(RTLD_NEXT, "inotify_add_watch");
    mask &= ~(IN_OPEN | IN_ATTRIB);
    return orig(fd, pathname, mask);
}

Usage: LD_PRELOAD=./fix_inotify.so apx dev start

Suggested Fix

Remove IN_OPEN (0x020) from the inotify watch mask in crates/core/src/dev/watcher.rs. Consider also removing IN_ATTRIB (0x004). The corrected mask
0x3CA (IN_MODIFY | IN_CLOSE_WRITE | IN_MOVED_FROM | IN_MOVED_TO | IN_CREATE | IN_DELETE) is sufficient for detecting file modifications without
triggering on read-only access.

Environment

- apx: 0.3.8
- OS: WSL2 (Linux 5.15.167.4-microsoft-standard-WSL2)
- Python: 3.11
- notify crate: 0.11.0 (inotify backend)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions