Skip to content

tui: startup crash with EMFILE when inotify instances are exhausted (unguarded fs.watch in ConfigProvider) #51489

Closed

Description

@sunoldman

Title

tui: startup crash with EMFILE when inotify instances are exhausted (unguarded fs.watch in ConfigProvider)

Description

The TUI crashes at startup with a fatal error screen when the host's per-uid inotify instance quota is exhausted:

OpenCode crashed
An unexpected error stopped the session.

Error
EMFILE: too many open files, watch '~/.config/opencode'

Stack trace
Error: EMFILE: too many open files, watch '/home/<redacted>/.config/opencode'
    at watch (unknown)
    at new FSWatcher [internal/fs/watch:101:31]
    at watch [internal/fs/watch:172:23]
    at GG ($bunfs/root/chunk-pramjmve.js:3:54669)
    at tn ($bunfs/root/chunk-pramjmve.js:3:7340)
    at get ($bunfs/root/chunk-pramjmve.js:2:18355)
    at children ($bunfs/root/chunk-2g9pk755.js:2590:3707)
    at <anonymous> ($bunfs/root/chunk-pramjmve.js:2:15016)
    at RA ($bunfs/root/chunk-pramjmve.js:2:10350)
    at Ne ($bunfs/root/chunk-pramjmve.js:2:10130)

At startup the TUI calls fs.watch() in four places. Only one of them (the storage directory watcher) is guarded with try/catch — that guard landed on 2026-09-24 ("fix(tui): don't crash when fs.watch throws (e.g. ENOSPC)", "fix(tui): handle storage watcher errors after startup"). The other three are unguarded, so the synchronous EMFILE thrown by new FSWatcher() propagates through the SolidJS render tree and kills the whole TUI.

This is the TUI sibling of #16610 (server hangs at startup on inotify exhaustion). The server side was mitigated (.git watcher moved to fs.watchFile(), plus the OPENCODE_DISABLE_FILEWATCHER switch), but the TUI config watcher still hard-depends on inotify: no error handling, no fallback, no env switch.

OpenCode version

2.0.18 (@opencode/cli installed via bun install -g). The unguarded call sites are unchanged on the latest v2 branch.

Steps to reproduce

  1. Exhaust the per-uid inotify instance quota for the user running opencode. Easiest inside a shared container host: sibling containers of the same uid exhaust fs.inotify.max_user_instances (kernel counts inotify instances per real uid across containers). Verify independently of opencode: a one-line fs.watch('/tmp', ...) in bun/node fails with EMFILE while the process itself is far below its fd limit.
    • Observed in the affected environment: container-local processes hold only 8 inotify instances out of max_user_instances=128, per-process fd count is ~4 of soft limit 1024, and inotify_init1() still fails with EMFILE consistently. sysctl -w inside the container is rejected (/proc/sys read-only).
  2. Run opencode (TUI).
  3. Crash screen appears immediately: OpenCode crashed — EMFILE: too many open files, watch '~/.config/opencode'.

Note: the same binary works fine when an inotify instance is available; and older 2.0.x releases crash identically in this state — the crash follows the environment, not a specific 2.0.x regression.

Expected behavior

  • A failed fs.watch() (EMFILE / ENOSPC / ENOSYS) should degrade gracefully: log a warning, disable config hot-reload, and keep the TUI usable — same treatment as the storage watcher fix.
  • Ideally the TUI watchers honor an env switch (mirroring the server-side OPENCODE_DISABLE_FILEWATCHER) so users in constrained containers can opt out entirely.

Actual behavior

Fatal crash screen at startup; the TUI is completely unusable while the server and web clients keep working.

Operating system

Linux (Docker container; host kernel 3.10 / CentOS 7; fs.inotify.max_user_instances=128 shared per-uid across containers on the same host)

Plugins

None — stock install; the crash happens during startup before plugins load.

Proposed fixes

  • Option A (minimal, consistent with the storage fix): wrap the three remaining unguarded watch() calls in try/catch and continue with live-reload disabled on failure:
    • packages/tui/src/config/index.tsx:357 (watch(path.dirname(host.path)) in ConfigProvider — the crash site)
    • packages/tui/src/model-preference.ts:155
    • packages/tui/src/plugin/watch.ts:47
  • Option B: centralize a safeWatch() helper used by all TUI watchers, so future watch sites inherit the guard.
  • Option C (ergonomics): honor OPENCODE_DISABLE_FILEWATCHER (already supported by the server) in the TUI as a full opt-out.

Activity

  1. github-actions commented on Sep 26, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    • #50566: TUI startup crash with EMFILE while watching the state directory.
    • #51208: TUI startup crash when the inotify watch limit is exhausted (ENOSPC).
    • #48384: TUI startup crash when an inotify watcher cannot be created (ENOSPC).
  2. added 2 commits that reference this issue on Oct 22, 2026
    bb136e9
    5eabe2c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

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