mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
fix(daemon): ignore .venv and other large dirs in project file watcher (#531)
* fix(daemon): ignore .venv and other large dirs in project file watcher A project containing a Python virtual environment (.venv) could exhaust the daemon's file descriptor table — chokidar recursively watched every file in the tree, opening ~18 000 fds. With the fd table full, macOS posix_spawn returned EBADF when the daemon tried to create stdio pipes for a child process (codex exec, or any other agent), surfacing as "spawn failed: spawn EBADF" on every chat request. Adds .venv, venv, __pycache__, .mypy_cache, .pytest_cache, .tox, .ruff_cache, target, vendor, and .cargo to the per-segment IGNORE_NAMES set so the watcher skips these trees in any project. * fix(daemon): narrow project-watcher ignores to safe Python dirs only Remove target, vendor, and .cargo from IGNORE_NAMES — they match any path segment, so src/vendor/… or .cargo/config.toml (a valid Rust project file) would be silently dropped from file-change events. Keep only the Python-specific names (.venv, venv, __pycache__, and the mypy/pytest/tox/ruff caches) which are never legitimate authored source at any depth and were the root cause of the fd-exhaustion bug. Add a real-chokidar test covering all seven newly added ignore dirs. --------- Co-authored-by: hbrown <hbrown@mitre.org>
This commit is contained in:
parent
b1121c04f5
commit
99d443c512
2 changed files with 44 additions and 1 deletions
|
|
@ -17,7 +17,24 @@ import { projectDir } from './projects.js';
|
|||
// against the path *relative to the watch root* so that ancestor directories
|
||||
// (e.g. the daemon's own `.od/` runtime dir, which contains every project) do
|
||||
// not accidentally match and silence every event in the tree.
|
||||
const IGNORE_NAMES = new Set(['.git', 'node_modules', '.od', 'debug', '.DS_Store']);
|
||||
const IGNORE_NAMES = new Set([
|
||||
'.git',
|
||||
'node_modules',
|
||||
'.od',
|
||||
'debug',
|
||||
'.DS_Store',
|
||||
// Python virtual environments and caches — can contain tens of thousands of
|
||||
// files, exhausting the process fd table and breaking child-process spawning.
|
||||
// These names are safe to match at any path depth: a directory named `.venv`
|
||||
// or `__pycache__` is never legitimate authored source in a project tree.
|
||||
'.venv',
|
||||
'venv',
|
||||
'__pycache__',
|
||||
'.mypy_cache',
|
||||
'.pytest_cache',
|
||||
'.tox',
|
||||
'.ruff_cache',
|
||||
]);
|
||||
export function makeIgnored(rootDir) {
|
||||
return (absPath) => {
|
||||
const rel = path.relative(rootDir, absPath);
|
||||
|
|
|
|||
|
|
@ -181,6 +181,32 @@ describe('project-watchers (real chokidar)', () => {
|
|||
}
|
||||
}, 8_000);
|
||||
|
||||
it('ignores files inside Python venv and cache dirs', async () => {
|
||||
const { root, projectId } = await makeProjectsRoot();
|
||||
const events = [];
|
||||
const sub = subscribe(root, projectId, (e) => events.push(e));
|
||||
await sub.ready;
|
||||
|
||||
const ignoredDirs = ['.venv', 'venv', '__pycache__', '.mypy_cache', '.pytest_cache', '.tox', '.ruff_cache'];
|
||||
try {
|
||||
for (const dir of ignoredDirs) {
|
||||
await mkdir(path.join(root, projectId, dir), { recursive: true });
|
||||
await writeFile(path.join(root, projectId, dir, 'file.py'), '');
|
||||
}
|
||||
|
||||
await writeFile(path.join(root, projectId, 'real.txt'), 'real');
|
||||
await waitFor(() => events.some((e) => e.path === 'real.txt'));
|
||||
|
||||
const ignored = events.filter((e) =>
|
||||
ignoredDirs.some((dir) => e.path.startsWith(`${dir}/`)),
|
||||
);
|
||||
expect(ignored).toEqual([]);
|
||||
} finally {
|
||||
await sub.unsubscribe();
|
||||
await rm(root, { recursive: true, force: true });
|
||||
}
|
||||
}, 8_000);
|
||||
|
||||
it('attaches an error listener and survives an emitted error event', async () => {
|
||||
// Regression for codex P1: chokidar's FSWatcher is an EventEmitter.
|
||||
// Without an 'error' listener, transient FS faults (ENOSPC, EPERM,
|
||||
|
|
|
|||
Loading…
Reference in a new issue