Skip to content

opencode serve stops polling listener epoll fd; all connections hang until restart #21375

Closed as not planned

Description

@dylangarcia

Description

opencode serve can get into a state where the process is still alive and listening on 127.0.0.1:4096, but it no longer responds to any HTTP request, including /global/health.

Restarting the opencode serve process immediately fixes it.

This does not look like a slow route, project bootstrap, MCP, LSP, or plugin initialization issue because /global/health also hangs and receives 0 bytes. The socket/runtime state suggests the listener remains open and registered in epoll, but the thread that normally polls the listener epoll fd stops doing so.

OpenCode version

1.3.17

Command

opencode serve --hostname=127.0.0.1 --port=4096 --log-level=DEBUG

Steps to reproduce

I do not have a deterministic reproduction yet. I observed this on a long-running opencode serve process inside a Linux sandbox/container while using the server from a local client.

When wedged, this request connects but times out with 0 bytes:

curl -sv --http1.1 --no-keepalive --max-time 5 http://127.0.0.1:4096/global/health

Output:

*   Trying 127.0.0.1:4096...
* Connected to 127.0.0.1 (127.0.0.1) port 4096
> GET /global/health HTTP/1.1
> Host: 127.0.0.1:4096
> User-Agent: curl/8.5.0
> Accept: */*
>
* Operation timed out after 5002 milliseconds with 0 bytes received
* Closing connection

Expected behavior

/global/health should return quickly:

{"healthy":true,"version":"1.3.17"}

Actual behavior

The TCP connection is established, but no HTTP response bytes are returned. The process remains alive and listening, but requests pile up until clients time out. Killing and restarting opencode serve restores normal behavior.

Process state

In one occurrence, the listener process was PID 900:

PID=$(ss -ltnp 'sport = :4096' | grep -o 'pid=[0-9]*' | head -1 | cut -d= -f2)
echo "$PID"
ps -p "$PID" -o pid,ppid,stat,etime,pcpu,pmem,wchan:32,args

Output:

900

    PID    PPID STAT     ELAPSED %CPU %MEM WCHAN                            COMMAND
    900       7 Sl         40:48  0.1  0.0 futex_wait_queue_me              opencode serve --hostname=127.0.0.1 --port=4096 --log-level=DEBUG

After a timed-out /global/health request:

ss -tanpi '( sport = :4096 or dport = :4096 )'

Output excerpt:

LISTEN       13   512   127.0.0.1:4096        0.0.0.0:*        users:(("opencode",pid=900,fd=16))
         cubic cwnd:10 unacked:13

CLOSE-WAIT   91   0     127.0.0.1:4096        127.0.0.1:42694
         bytes_received:91 ...

ESTAB        218  0     127.0.0.1:4096        127.0.0.1:59964
         bytes_received:218 ...

This suggests opencode is still listening, but not draining the accept backlog or reading request bytes.

strace

I attached strace while issuing a /global/health curl. The trace showed no accept4, read, recvfrom, or write activity for the request. Threads were blocked in futex(...), with one thread blocked in epoll_pwait2(...).

Excerpt from a wedged process:

698   19:16:29.615002 futex(0x3112fa48, FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME, 0, NULL, FUTEX_BITSET_MATCH_ANY <unfinished ...>
627   19:16:29.615050 futex(0x618660c, FUTEX_WAIT_PRIVATE, 1, NULL <unfinished ...>
626   19:16:29.615057 futex(0x618660c, FUTEX_WAIT_PRIVATE, 1, NULL <unfinished ...>
625   19:16:29.615063 futex(0x618660c, FUTEX_WAIT_PRIVATE, 1, NULL <unfinished ...>
609   19:16:29.615068 futex(0x618660c, FUTEX_WAIT_PRIVATE, 1, NULL <unfinished ...>
608   19:16:29.615073 futex(0x618660c, FUTEX_WAIT_PRIVATE, 1, NULL <unfinished ...>
607   19:16:29.615079 futex(0x618660c, FUTEX_WAIT_PRIVATE, 1, NULL <unfinished ...>
606   19:16:29.615083 futex(0x618660c, FUTEX_WAIT_PRIVATE, 1, NULL <unfinished ...>
605   19:16:29.615089 futex(0x618660c, FUTEX_WAIT_PRIVATE, 1, NULL <unfinished ...>
604   19:16:29.615094 futex(0x618660c, FUTEX_WAIT_PRIVATE, 1, NULL <unfinished ...>
603   19:16:29.615099 futex(0x618660c, FUTEX_WAIT_PRIVATE, 1, NULL <unfinished ...>
602   19:16:29.615104 futex(0x618660c, FUTEX_WAIT_PRIVATE, 1, NULL <unfinished ...>
601   19:16:29.615109 futex(0x618660c, FUTEX_WAIT_PRIVATE, 1, NULL <unfinished ...>
600   19:16:29.615114 futex(0x618660c, FUTEX_WAIT_PRIVATE, 1, NULL <unfinished ...>
595   19:16:29.615119 futex(0x618660c, FUTEX_WAIT_PRIVATE, 1, NULL <unfinished ...>
469   19:16:29.615125 epoll_pwait2(14,  <unfinished ...>
460   19:16:29.615131 futex(0x618660c, FUTEX_WAIT_PRIVATE, 1, NULL <unfinished ...>
459   19:16:29.615135 futex(0x618660c, FUTEX_WAIT_PRIVATE, 1, NULL <unfinished ...>
458   19:16:29.615141 futex(0x618660c, FUTEX_WAIT_PRIVATE, 1, NULL <unfinished ...>
326   19:16:29.615146 futex(0x3112fda8, FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME, 0, NULL <detached ...>

epoll/fdinfo comparison

In a wedged occurrence, the actual listener fd was 20:

listener=$(ss -ltnp 'sport = :4096' | grep -o 'fd=[0-9]*' | head -1 | cut -d= -f2)
echo "listener fd: $listener"
readlink /proc/$PID/fd/$listener
grep -R "tfd: *$listener" /proc/$PID/fdinfo

Output:

listener fd: 20
socket:[1065114658]
/proc/326/fdinfo/5:tfd:       20 events:       19 data:      28b300a20c0  pos:0 ino:3f7c5c22 sdev:8

So the listener socket was still registered under epoll fd 5.

In a working sandbox, strace during curl /global/health shows a thread polling that listener epoll fd:

epoll_pwait2(5, ...)

In the wedged sandbox, strace no longer shows any thread polling epoll fd 5. Instead, the only visible event-loop wait was:

epoll_pwait2(14, ...)

During that wedged state, /global/health connected but timed out with 0 bytes, ss showed pending listener backlog / unread request bytes / CLOSE-WAIT sockets, and no accept4 / read / write appeared in strace.

This suggests the listener socket remains registered, but the event-loop thread responsible for the listener epoll fd is no longer polling it, or the runtime has otherwise stopped dispatching listener readiness.

Environment

OS: Linux, inside a sandbox/container
OpenCode: 1.3.17
Server command: opencode serve --hostname=127.0.0.1 --port=4096 --log-level=DEBUG

gdb was not available on the machine, so I could not collect native backtraces.

I have observed this in more than one repository/sandbox. Restarting the opencode process fixes the issue immediately.

Additional context

Because /global/health hangs and there is no request/response activity in strace, this appears to be below opencode route handling. It seems more likely to involve the Bun runtime, Node HTTP compatibility, or the @hono/node-server adapter than project/plugin/MCP/LSP code.

Would you prefer this be reported directly to Bun with a reduced reproduction, or is there additional opencode-specific debugging I can collect next time it occurs?

Plugins

No response

OpenCode version

1.3.17

Steps to reproduce

No response

Screenshot and/or share link

No response

Operating System

No response

Terminal

No response

Activity

  1. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on Apr 7, 2026
  2. github-actions commented on Apr 7, 2026

    @github-actions
    Contributor

    This issue might be related to previously reported issues. Please check:

    If your issue is a distinct epoll-polling regression not covered by either of those, this report's strace/fdinfo evidence should help distinguish it.

  3. thdxr commented on Apr 7, 2026

    @thdxr
    Member

    what version were you guys on before?

  4. assigned and unassigned on Apr 26, 2026
  5. removed
    bugSomething isn't working
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on May 3, 2026
  6. panta82 commented on Jun 17, 2026

    @panta82
    Contributor

    I've been struggling with this for a while. Excerpts from clanker's slop report:


    I observed what looks like the same failure mode on opencode 1.17.7.

    Environment

    • OpenCode version: 1.17.7
    • Command:
      /root/.nvm/versions/node/v24.4.1/bin/opencode web --port 4096 --hostname 127.0.0.1 --print-logs
    • Packaged binary appears to embed:
      Bun v1.3.14 (0d9b296a) Linux x64 baseline
      
    • Host OS: Linux x64

    Important context

    The process had an LD_PRELOAD shim loaded to no-op Bun/WebKit allocator madvise(..., MADV_DODUMP/MADV_DONTDUMP) calls:

    LD_PRELOAD=/root/opencode-madvise-shim.so
    

    This was added because we previously observed a separate Bun allocator issue with an madvise(..., MADV_DODUMP) = -1 EAGAIN loop. During this HTTP wedge, CPU was low and the shim was loaded, so this seems separate from the prior high-CPU madvise loop.

    Socket state while wedged

    Listener:

    LISTEN 32 512 127.0.0.1:4096 users:(("opencode",pid=2331269,fd=22))
    

    There were many server-side sockets stuck in CLOSE-WAIT with unread request bytes, plus an established nginx upstream connection with unread bytes:

    LISTEN     32     512        127.0.0.1:4096       0.0.0.0:*     users:(("opencode",pid=2331269,fd=22))
    ESTAB      1008   0          127.0.0.1:4096     127.0.0.1:56722
    CLOSE-WAIT 786    0          127.0.0.1:4096     127.0.0.1:54414
    CLOSE-WAIT 786    0          127.0.0.1:4096     127.0.0.1:52026
    CLOSE-WAIT 786    0          127.0.0.1:4096     127.0.0.1:45410
    CLOSE-WAIT 785    0          127.0.0.1:4096     127.0.0.1:53668
    ...
    

    The listener backlog kept increasing as probes were made, which suggests connections were arriving but not being drained.

    Process/thread state

    Process state:

    PID       PPID STAT ELAPSED  %CPU %MEM WCHAN             CMD
    2331269   821 Ssl  01:04:21  0.4  1.1 futex_wait_queue   /root/.nvm/versions/node/v24.4.1/bin/opencode web --port 4096 --hostname 127.0.0.1 --print-logs
    

    Thread state:

    PID      TID      STAT %CPU WCHAN              COMMAND
    2331269  2331269  Ssl  0.4  futex_wait_queue   opencode
    2331269  2331274  Ssl  0.1  futex_wait_queue   opencode
    2331269  2331355  Ssl  0.0  futex_wait_queue   Bun Pool 0
    2331269  2331356  Ssl  0.0  futex_wait_queue   Bun Pool 1
    2331269  2331357  Ssl  0.0  futex_wait_queue   Bun Pool 2
    2331269  2331358  Ssl  0.0  futex_wait_queue   Bun Pool 3
    2331269  2331404  Ssl  0.0  do_epoll_wait      HTTP Client
    2331269  2331734  Ssl  0.0  futex_wait_queue   fff-bg-0
    2331269  2331735  Ssl  0.0  futex_wait_queue   fff-bg-1
    2331269  2331736  Ssl  0.0  futex_wait_queue   fff-bg-2
    2331269  2331737  Ssl  0.0  futex_wait_queue   fff-bg-3
    2331269  2331738  Ssl  0.0  futex_wait_queue   fff-bg-4
    2331269  2331759  Ssl  0.0  futex_wait_queue   opencode
    2331269  2331771  Ssl  0.1  hrtimer_nanosleep  notify-rs debou
    

    strace while wedged

    I attached strace to the listener PID from ss, then made a localhost request:

    timeout 10 strace -ff -tt -s 200 \
      -p 2331269 \
      -e trace=accept,accept4,recvfrom,recvmsg,sendto,sendmsg,read,write,close,epoll_wait,epoll_pwait,poll,ppoll,select,pselect6,futex,clock_nanosleep,nanosleep \
      -o /tmp/opencode-web-live-trace

    During the curl probe, there were no observed accept, accept4, read, recvfrom, sendto, or write syscalls servicing the new request.

    Representative trace excerpts:

    2331269: futex(... FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME ... <detached ...>
    2331274: futex(... FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME ...) = -1 ETIMEDOUT
    2331771: clock_nanosleep(CLOCK_MONOTONIC, 0, {tv_sec=0, tv_nsec=25000000}, ...) = 0
    2331771: clock_nanosleep(CLOCK_MONOTONIC, 0, {tv_sec=0, tv_nsec=25000000}, ...) = 0
    ...
    

    So the process was alive, but did not appear to accept or read the request.

    Logs

    opencode-web logs showed startup and bootstrap messages, but no useful error around the wedge.

    Recent startup entries:

    timestamp=2026-06-17T08:00:06.649Z level=INFO run=654bd5e0 message=loading path=/root/.config/opencode/config.json
    timestamp=2026-06-17T08:00:06.674Z level=INFO run=654bd5e0 message=loading path=/root/.config/opencode/opencode.json
    timestamp=2026-06-17T08:00:06.681Z level=INFO run=654bd5e0 message=loading path=/root/.config/opencode/opencode.jsonc
    Web interface: http://127.0.0.1:4096/
    timestamp=2026-06-17T08:00:11.441Z level=INFO run=654bd5e0 message="global event connected"
    timestamp=2026-06-17T08:00:12.823Z level=INFO run=654bd5e0 message="booting location services" directory=/data_playground/main workspaceID=undefined
    timestamp=2026-06-17T08:00:13.118Z level=INFO run=654bd5e0 message="watcher backend" directory=/data_playground/main platform=linux backend=inotify
    

    No crash, no panic, no route-level error.

  7. github-actions commented on Aug 17, 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