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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions