[Bug]: opencode serve does not release port on exit — zombie self-loopback connection locks LISTENING socket (Windows) · Issue #32932 · anomalyco/opencode · GitHub
Skip to content

[Bug]: opencode serve does not release port on exit — zombie self-loopback connection locks LISTENING socket (Windows) #32932

Closed as not planned

Description

@WhiteGiverMa

Description

When opencode serve exits or crashes, the server port (e.g. 4096) is not released. A self-loopback TCP connection (127.0.0.1:54420 ↔ 127.0.0.1:4096, both endpoints owned by the now-dead server PID) remains in ESTABLISHED state, keeping the LISTENING socket locked. On Windows, this port cannot be reclaimed by a new opencode serve --port 4096 invocation — it fails with ServeError: Unexpected error.

The dead process (PID 19152) no longer exists in the process table, but the Windows kernel TCP stack keeps the socket open because the self-loopback ESTABLISHED connection never receives a FIN/RST from either end. All other client connections are stuck in CLOSE_WAIT, also waiting on this zombie self-connection.

This is related to #22198 (SSE connections stuck in CLOSE_WAIT) but is a distinct issue: the root cause here is that opencode serve does not perform graceful socket shutdown on exit, leaving a self-loopback connection that locks the listening port indefinitely on Windows.

Evidence

  1. ServeError on restart: opencode serve --port 4096 → Error: Unexpected error / ServeError
  2. Dead PID still holds LISTENING: netstat -ano shows LISTENING 127.0.0.1:4096 PID 19152, but tasklist /fi "pid eq 19152" → "No tasks running which match the specified criteria"
  3. Self-loopback ESTABLISHED: netstat -ano shows 127.0.0.1:54420 ↔ 127.0.0.1:4096 ESTABLISHED PID 19152 on both sides — a connection from the server to itself that was never closed
  4. 54+ CLOSE_WAIT connections: All other connections to port 4096 are in CLOSE_WAIT, waiting on the zombie self-connection
  5. Port never releases: Killed all OpenCode Desktop processes, killed all Edge browser processes, waited several minutes — port 4096 still held by dead PID 19152. Only a full OS reboot clears it.

Steps to reproduce

  1. Start opencode serve --port 4096 (WSL2 or Windows)
  2. Open OpenCode Desktop and connect it to the server (this creates SSE connections including self-loopback)
  3. Kill the opencode serve process (e.g. close the terminal, or the process crashes)
  4. Attempt to restart: opencode serve --port 4096 → ServeError: Unexpected error
  5. Verify with netstat -ano | findstr 4096: LISTENING socket still held by dead PID, with a self-loopback ESTABLISHED connection

Expected behavior

opencode serve should perform graceful shutdown on exit:

  • Close all SSE streams and send FIN to connected clients
  • Close the self-loopback connection (if any)
  • Close the LISTENING socket
  • The port should be immediately available for a new instance

Suggested fix

  1. Register a process exit handler (process.on('exit'), process.on('SIGTERM'), process.on('SIGINT')) that calls server.close() and closes all active connections
  2. On Windows, also call server.removeAllListeners() and explicitly destroy sockets that are in CLOSE_WAIT
  3. Consider adding a TCP keepalive/idle timeout on SSE connections so zombie connections eventually self-close even if the server process dies ungracefully

Plugins

oh-my-openagent

OpenCode version

OpenCode Desktop v1.17.8 / opencode-web (latest)

Operating System

Windows 11 (WSL2, Ubuntu 24.04)

Terminal

Windows Terminal / PowerShell

Activity

  1. HaonanQiu commented on Jul 8, 2026

    @HaonanQiu

    same problem

  2. arashi-dev commented on Jul 22, 2026

    @arashi-dev

    Any updates? I still have this problem and looks like there is something going on here aictrl-dev/cli#94

  3. WhiteGiverMa commented on Jul 22, 2026

    @WhiteGiverMa
    Author

    Any updates? I still have this problem and looks like there is something going on here aictrl-dev/cli#94

    now I use systemd to manage opencode service in WSL so I didnt meet this for a long time.

  4. arashi-dev commented on Jul 23, 2026

    @arashi-dev

    @WhiteGiverMa That's sounds interesting, can you share your systemd config please?

  5. WhiteGiverMa commented on Jul 23, 2026

    @WhiteGiverMa
    Author

    @arashi-dev Sure. This is the user-level systemd unit I use in WSL (~/.config/systemd/user/opencode-serve.service):

    [Unit]
    Description=OpenCode Serve (WSL)
    After=network.target
    
    [Service]
    Type=simple
    ExecStart=/home/<your-user>/.nvm/versions/node/v24.14.0/bin/opencode serve --hostname 0.0.0.0 --port 4097 --mdns --mdns-domain opencode.local
    Restart=on-failure
    RestartSec=5
    Environment=HOME=/home/<your-user>
    Environment=PATH=/home/<your-user>/.nvm/versions/node/v24.14.0/bin:/usr/bin:/bin
    
    [Install]
    WantedBy=default.target

    I enable and start it with:

    systemctl --user daemon-reload
    systemctl --user enable --now opencode-serve.service

    I also use local drop-ins for authentication and proxy settings. For example, the authentication drop-in should look like this, with your own password:

    # ~/.config/systemd/user/opencode-serve.service.d/auth.conf
    [Service]
    Environment="OPENCODE_SERVER_PASSWORD=<your-password>"

    Using systemd as the supervisor has worked around the issue for me: when the process exits or fails, systemd owns the process lifecycle and restarts the service, so I have not encountered the stuck-port problem for a long time.

    This is only a workaround, not a fix in OpenCode itself. Running opencode web or opencode serve directly, outside systemd, may still reproduce the same issue.

  6. arashi-dev commented on Jul 23, 2026

    @arashi-dev

    @WhiteGiverMa Thank you very much!

  7. github-actions commented on Sep 22, 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