Skip to content

Opencode hangs at startup if a .git repo is present and inotify user instances run out #16610

Closed as not planned
sailod/opencode
#1

Description

@Christoph-D

Description

Steps to reproduce

  1. Run sudo sysctl -w fs.inotify.max_user_instances=16 or some other lower number
  2. Run opencode in a path that contains a .git directory.

Expected behavior

Opencode starts normally. Or, if it really can't work without a lot of inotify instances, then it should show a clear error message and exit.

Actual behavior

Opencode hangs at startup with a blank screen, completely unresponsive.

Likely root cause

Opencode doesn't handle the error "EMFILE (Too many open files)" when creating new inotify instances.

Workaround

Increase the systemwide limit of inotify user instances: sudo sysctl -w fs.inotify.max_user_instances=2048

Log

Last log lines from where it hangs:

$ opencode run hello --print-logs
[...]
INFO  2026-03-08T15:39:44 +2ms service=server method=GET path=/event request
INFO  2026-03-08T15:39:44 +0ms service=server status=started method=GET path=/event request
INFO  2026-03-08T15:39:44 +0ms service=server method=POST path=/session/ses_331e54564ffeUxW4QHflpXACOJ/message request
INFO  2026-03-08T15:39:44 +0ms service=server status=started method=POST path=/session/ses_331e54564ffeUxW4QHflpXACOJ/message request
INFO  2026-03-08T15:39:44 +0ms service=server event connected
INFO  2026-03-08T15:39:44 +1ms service=bus type=* subscribing
INFO  2026-03-08T15:39:44 +5ms service=server status=completed duration=6 method=GET path=/event request
INFO  2026-03-08T15:39:44 +1ms service=server status=completed duration=7 method=POST path=/session/ses_331e54564ffeUxW4QHflpXACOJ/message request
INFO  2026-03-08T15:39:44 +3ms service=vcs branch=HEAD initialized
INFO  2026-03-08T15:39:44 +0ms service=bus type=file.watcher.updated subscribing
^C

Plugins

None. The same issue happens when I disable all plugins.

OpenCode version

1.2.21

Steps to reproduce

  1. Run sudo sysctl -w fs.inotify.max_user_instances=16 or some other lower number
  2. Run opencode in a path that contains a .git directory.

Screenshot and/or share link

No response

Operating System

Ubuntu 24.04, Alpine 3.23

Terminal

bash

Activity

  1. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on Mar 8, 2026
  2. Christoph-D commented on Mar 8, 2026

    @Christoph-D
    Author

    parcel-bundler/watcher#159 looks like the same issue. Perhaps this causes opencode to hang.

  3. MichaelFisher1997 commented on Mar 27, 2026

    @MichaelFisher1997

    Also affected — NixOS, opencode-desktop 1.3.3, 102% CPU on FileWatcher.init

    I'm experiencing the exact same issue on the desktop GUI (not TUI). The TUI works perfectly fine — it's specifically the desktop's bundled sidecar (opencode-cli serve) that spins at 102% CPU indefinitely.

    Environment

    • OS: NixOS (Linux)
    • OpenCode Desktop: 1.3.3 (via nix package opencode-desktop-unwrapped-1.3.3)
    • Sidecar: bun-based opencode-cli --print-logs --log-level WARN serve
    • inotify settings: max_user_instances=524288, max_user_watches=524288 (already very high)

    Symptoms

    The desktop sidecar's FileWatcher.init enters a tight CPU loop on every project directory and worktree simultaneously. Every single call times out:

    ERROR service=file.watcher dir=/home/micqdf cause=TimeoutError: TimeoutError
        at FileWatcher.state (index.js:63114:57)
        at FileWatcher.init (index.js:23526:109)
        at FileWatcher.init (definition) (index.js:236755:32) failed to subscribe
    

    This repeats for:

    • The home directory (/home/micqdf)
    • All project directories (~/nixos/nix-config, ~/github/...)
    • All .git directories within projects
    • All worktree directories under ~/.local/share/opencode/worktree/

    The sidecar never recovers — it's stuck at 102% CPU in this loop and never reaches the point of sending LLM API requests. The desktop GUI shows infinite "thinking" on all models.

    Key observation

    The system inotify limits are already set very high (524288). The issue isn't system-level inotify exhaustion — it's the FileWatcher implementation itself timing out on every directory regardless. The watcher.ignore config option also has no effect on the desktop sidecar (only works for TUI/CLI).

    Workaround

    Only the TUI works. No config-based workaround has been effective for the desktop.

  4. removed
    bugSomething isn't working
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on May 3, 2026
  5. EmpireJones commented on May 8, 2026

    @EmpireJones

    I just ran into this same issue; I had to increase fs.inotify.* values to get opencode to start 👍

    I agree that a clear error message is needed.

  6. cfal commented on May 11, 2026

    @cfal

    adding the following to config allowed opencode to startup for me. +1 to clear error message, this is painful since the process completely hangs with a black screen.

      "watcher": {
        "ignore": [".git"]
      }
    
  7. Phantal commented on May 13, 2026

    @Phantal

    One of my coworkers was having the same issue. They use GoLand / PyCharm which use inotify extensively. They ran sysctl fs.inotify.max_user_instances and it's set to 128. Closing GoLand / PyCharm fixed the issue.

  8. lee-b commented on Jun 3, 2026

    @lee-b

    Just another confirmation. Got this in multiple repos, including a brand new directory that opencode worked fine in before git init ; git commit --allow-empty -m "Initial root commit". Increasing max_user_instances fixed it.

  9. dtinth commented on Jun 8, 2026

    @dtinth

    Also encountering this issue. Right now I work around by increasing these to a massive number.

    sudo sysctl fs.inotify.max_user_watches=524288
    sudo sysctl fs.inotify.max_user_instances=512
  10. esaesa commented on Jun 12, 2026

    @esaesa

    same for me.after clicking ctrl+c i get

    ===== KERNEL INOTIFY LIMITS =====
    fs.inotify.max_user_watches = 8192
    fs.inotify.max_user_instances = 128
    fs.inotify.max_queued_events = 16384
    
    ===== CURRENT INOTIFY WATCH USAGE BY PROCESS =====
        8021  1782541 opencode                  opencode -c 
         101        1 systemd                   /sbin/init 
          34  1711199 f2b/server                /usr/bin/python3 /usr/bin/fail2ban-server -xf start 
          18      483 polkitd                   /usr/lib/policykit-1/polkitd --no-debug 
          12      288 systemd-udevd             /lib/systemd/systemd-udevd 
           3      395 dbus-daemon               /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only 
           2  1799583 imap                      dovecot/imap 
           2  1748704 gpg-agent                 gpg-agent --homedir /root/.gnupg --use-standard-socket --daemon 
           2      393 accounts-daemon           /usr/lib/accountsservice/accounts-daemon 
           1   467577 systemd-resolve           /lib/systemd/systemd-resolved 
           1     3655 agetty                    /sbin/agetty -o -p -- \u --noclear tty1 linux 
           1      414 systemd-logind            /lib/systemd/systemd-logind 
           1      390 systemd-timesyn           /lib/systemd/systemd-timesyncd 
    
    
  11. sailod commented on Jun 18, 2026

    @sailod

    I can reproduce a closely related failure on OpenCode 1.17.8 / Bun 1.3.14 in a Linux dev container.

    In my case, plain opencode black-screens / crashes when run inside a Git repo under /workspaces, while this works:

    OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER=true opencode

    A few useful details:

    • opencode run "hello" works, so the model/agent path is fine.
    • A non-Git directory under /workspaces works.
    • An empty Git repo under /workspaces crashes.
    • A fake Git env also works: GIT_DIR=/tmp/no-git-dir GIT_WORK_TREE=/tmp/no-git-work-tree opencode
    • Watcher usage was not near kernel limits during testing (roughly 7k–13k watches, 5 inotify instances; limits 65,536 / 128), so this is not only active global inotify exhaustion.
    • The crash signature is exit code 132 (SIGILL), Bun panic: Segmentation fault at address 0x0.

    I’d like to submit a fix: watcher initialization should not block or kill TUI startup. If watcher setup fails, OpenCode should log a warning, disable the watcher, and continue running.

  12. lee-b commented on Jun 18, 2026

    @lee-b

    132 (SIGILL), Bun panic: Segmentation fault at address 0x0.

    This looks a lot like a null pointer dereference, which should never happen in bug-free code.

  13. added a commit that references this issue on Jun 18, 2026
    b0d6ef6
  14. sailod commented on Jun 18, 2026

    @sailod

    Update: This PR (#32854) has been closed for further investigation. After deeper analysis of @parcel/watcher's C++ source, we found the .git watcher crash is in the native subscribe() call itself (SIGILL/SIGSEGV at address 0x0), not in std::regex_match as initially suspected. The JS-level error handling improvements in the PR are valid but cannot catch a native crash. We're systematically reproducing the crash to isolate the exact failure point and will open a new PR with the correct fix.


    Original comment:

    I've opened PR #32854 with a fix for this. The core issue is that the file watcher layer had no error isolation — any failure in the .git watcher setup (git.dir timeout, subscribe timeout, readDirectoryEntries failure) would propagate to the outer catchCause and return an empty service, killing the entire watcher including the TUI.

    The fix makes the watcher non-fatal at the JS level:

    1. Effect.promise → Effect.tryPromise — catches synchronous throws from the native @parcel/watcher binding, not just promise rejections
    2. .git watcher isolated into its own forkScoped with catchCause — git watcher failures no longer kill the entire watcher layer; only git HEAD tracking is lost
    3. Callback error parameter now logged — @parcel/watcher reports backend errors through the callback's first parameter; these were silently discarded
    4. Improved catchCause logging — makes it clear the watcher was disabled, not just that something failed
    5. Suppress unsubscribe errors — prevents unhandled rejections during cleanup

    Important caveat: This fixes JS-level errors in the watcher, but the native segfault (std::regex_match crash in @parcel/watcher on long paths with glob ignore patterns — see parcel-bundler/watcher#250) cannot be caught from JS. A follow-up PR should filter glob patterns out of the native watcher's ignore list and do glob matching in JS instead, which eliminates the native crash vector entirely.

  15. sailod commented on Jun 19, 2026

    @sailod

    Ive opened PR #32930 with a fix for this.

    Root cause

    Two bugs in @parcel/watcher v2.5.1 cause the hang/crash:

    1. Instance exhaustion → hang: When inotify_init1() fails, Backend::run() blocks forever on mStartedSignal.wait() because InotifyBackend::start() throws before calling notifyStarted(). No JS-level timeout can recover.

    2. Watch exhaustion → crash: When inotify_add_watch() fails with ENOSPC, subscribe() throws a WatcherError that is not a subclass of std::exception, so it escapes all catch blocks and triggers std::terminate().

    Additionally, on overlayfs (Docker dev containers), inotify_add_watch() can fail with ENOSPC even when the watch count is far below max_user_watches, and fs.watch() may silently succeed but never fire events.

    Fix

    Replace the .git watchers @parcel/watcher.subscribe() (which needed ~228 inotify watches to watch the entire .git/ directory tree) with fs.watchFile() on .git/HEAD (uses stat polling, zero inotify watches).

    The VCS service only listens for HEAD changes for branch tracking (it filters if (!data.file.endsWith("HEAD")) return), so watching the entire .git/ tree was unnecessary.

    fs.watchFile works on any filesystem regardless of inotify state, handles atomic file replacement (git writes to HEAD.lock then renames), and needs zero inotify watches. Branch changes are detected within ~2 seconds.

    The root watcher (behind OPENCODE_EXPERIMENTAL_FILEWATCHER) continues using @parcel/watcher unchanged — its opt-in and experimental.

    Verification

    • All 7 watcher tests pass, all 36 filesystem tests pass, typecheck passes
    • Reproduced the hang using LD_PRELOAD to intercept inotify_init1()
    • C diagnostic confirmed inotify_add_watch() fails with ENOSPC despite only 7119/65536 watches in use (overlayfs quirk)
    • Built standalone binary and tested on the problematic machine — opencode starts normally and branch switching is detected within ~2 seconds
  16. OmniReality commented on Jun 24, 2026

    @OmniReality

    I encountered this bug and tried sailod’s fix and it worked!
    I hope this issue would be resolved in the live version as well.

  17. sailod commented on Jun 25, 2026

    @sailod

    @OmniReality thanks for confirming the fix works on your machine too.

    This is the most robust fix for this issue. The .git watcher was using @parcel/watcher to watch the entire .git/ tree, which requires inotify and has unfixable C++ bugs when resources are exhausted. Since the VCS service only needs HEAD changes for branch tracking, watching the entire tree was unnecessary. fs.watchFile on .git/HEAD uses stat() syscalls instead of inotify, so it works on any filesystem regardless of inotify state. The 2-second poll interval is negligible for branch switching.

    The root watcher (behind OPENCODE_EXPERIMENTAL_FILEWATCHER) still uses @parcel/watcher. Only the always-active .git watcher was changed.

  18. github-actions commented on Aug 25, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

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