Repository navigation
Conversation
added 4 commits
June 19, 2026 00:31
@parcel/watcher has two bugs that cause hangs/crashes when inotify resources are exhausted: 1. Instance exhaustion: Backend::run() blocks forever on mStartedSignal.wait() when inotify_init1() fails, because InotifyBackend::start() throws before calling notifyStarted(). 2. Watch exhaustion: inotify_add_watch() fails with ENOSPC when max_user_watches is full. WatcherError thrown by subscribe() is not a subclass of std::exception, so it escapes all catch blocks and triggers std::terminate(). Fix by replacing the .git watcher's @parcel/watcher subscription (which needs ~228 inotify watches for a typical .git directory tree) with fs.watch() on .git/HEAD (needs 1 watch). The VCS service only listens for HEAD changes for branch tracking, so this is sufficient. Also add canWatch() pre-check using fs.watch() on a temp directory before subscribing the root watcher (experimental flag), covering both instance and watch exhaustion. Fixes anomalyco#16610
Git updates HEAD atomically (write to HEAD.lock, rename to HEAD). fs.watch on the file watches the inode, which goes stale after atomic replace. Watch the parent directory instead and filter for filename === 'HEAD', which detects the rename event.
fs.watch on overlayfs silently fails — the watch is created but no events fire, and no error event is emitted, so fallback logic never activates. Remove the fs.watch attempt entirely and use fs.watchFile (stat polling) which uses zero inotify watches and works on any filesystem. Also remove canWatch() pre-check that tested /tmp and could fail due to overlayfs inotify quirks unrelated to the .git watcher.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue
Fixes anomalyco#16610
Type of change
What does this PR do?
Prevents opencode from hanging or crashing at startup when inotify resources are exhausted on Linux. This happens when
max_user_instancesormax_user_watchesis near the system limit (e.g., other processes like IDEs, file managers, or containers have consumed most of the available inotify resources).Root cause
Two bugs in
@parcel/watcherv2.5.1 cause hangs/crashes when inotify resources are exhausted:Bug 1: Instance exhaustion → hang
When
inotify_init1()fails (EMFILE — no more inotify instances),@parcel/watchersBackend::run()blocks forever onmStartedSignal.wait()becauseInotifyBackend::start()throws before callingnotifyStarted(). This blocks the main thread — no JS-level timeout can recover.Bug 2: Watch exhaustion → crash
When
inotify_add_watch()fails (ENOSPC — no more watches),subscribe()throws aWatcherError. ButWatcherErroris not a subclass ofstd::exception, so it escapes allcatch (std::exception&)blocks and triggersstd::terminate(), crashing the process.The
.gitwatcher needs ~228 inotify watches (one per subdirectory in.git/). On systems near themax_user_watcheslimit, this exhausts remaining watches and triggers the crash.Fix
1. Replace
.gitwatcher withfs.watch()on.git/HEADThe
.gitwatcher previously used@parcel/watcher.subscribe()on the entire.git/directory tree, needing ~228 inotify watches. The VCS service only listens for HEAD changes (branch switching — seevcs.ts:325-336where it filtersif (!data.file.endsWith("HEAD")) return Effect.void), so watching the entire tree is unnecessary.The fix replaces it with
fs.watch()on.git/HEADonly — 1 watch instead of 228. This completely bypasses both@parcel/watcherbugs for the always-active.gitwatcher.2.
canWatch()pre-check for the root watcherBefore subscribing the root watcher (behind
OPENCODE_EXPERIMENTAL_FILEWATCHER), the fix runscanWatch()— triesfs.watch()on a temp directory. If it fails (either instance or watch exhaustion), the entire watcher layer is skipped with a warning log, preventing the hang/crash.The root watcher still uses
@parcel/watcherwhen inotify is available, but the pre-check prevents calling it when resources are exhausted.How did you verify your code works?
bun test test/filesystem/watcher.test.ts)bun test test/filesystem/)bun typecheck)LD_PRELOADto interceptinotify_init1()— confirmedsubscribe()blocks the main thread foreverinotify_add_watch()fails with ENOSPC (watch exhaustion, not instance exhaustion)Checklist