Skip to content

fix(server): treat peer resets as disconnects, not server errors (#316) - #317

Open
DukeLog wants to merge 1 commit into
coder:mainfrom
DukeLog:fix/peer-reset-noise
Open

fix(server): treat peer resets as disconnects, not server errors (#316)#317
DukeLog wants to merge 1 commit into
coder:mainfrom
DukeLog:fix/peer-reset-noise

Conversation

@DukeLog

@DukeLog DukeLog commented Sep 1, 2026

Copy link
Copy Markdown

Summary

Fixes #316. Making a visual selection pops up

[ClaudeCode] [server] [ERROR] WebSocket server error: Client read error: ECONNRESET

and a Press ENTER or type command to continue prompt, once per stale client.

Cause

Selection changes are broadcast to every connected client (selection.lua -> server/init.lua:398 -> tcp.lua:296). When a client has already gone away without sending a Close frame (killed CLI, closed terminal, suspended machine), that write resets the connection and libuv reports ECONNRESET to the read callback.

tcp.lua treats every read error as a server fault and routes it to server.on_error, which server/init.lua:91 logs at ERROR level, i.e. vim.notify(..., ERROR). The disconnect itself is already handled correctly on the same branch, so the notification is just noise. It shows up on selections because that is what generates traffic to a dead socket.

Easy to hit if you keep several Claude sessions attached to one Neovim instance and close some of them.

Fix

Handle ECONNRESET and ECONNABORTED like EOF: disconnect the client with 1006 and skip on_error. Every other read error, EPIPE and ENOTCONN included, still surfaces as before.

The lookup keys off the leading error name rather than the whole string. Stream callbacks currently pass the bare name (ECONNRESET), but luv also formats errors as NAME: message, and matching only one of the two shapes would leave the branch dead if that ever changes.

Testing

  • Full suite passes (714 tests), luacheck and stylua clean.
  • New cases in tests/unit/server/tcp_spec.lua cover both error shapes for both codes, plus EPIPE, ENOTCONN, ETIMEDOUT and EHOSTUNREACH: no route to host to pin down that genuine errors are still reported. The four peer-reset cases fail on main and pass here.
  • Live check against a real server in headless Neovim, with a peer closing its socket with SO_LINGER 0 to force an RST. Before: 1 disconnect, 1 error notification. After: 1 disconnect, 0 notifications.

A client that goes away without sending a Close frame (killed CLI, closed
terminal, suspended machine) makes libuv report ECONNRESET on the next read.
tcp.lua escalated every read error to server.on_error, which logs at ERROR
level and therefore raises a vim.notify(ERROR).

Selection changes are broadcast to every connected client, so each selection
that probed a dead socket interrupted the user with a hit-enter prompt, once
per stale client. The disconnect itself was already handled correctly on the
same branch, so the notification was pure noise.

Handle ECONNRESET and ECONNABORTED like EOF: disconnect the client with 1006
and skip on_error. Every other read error still surfaces as before. Stream
callbacks pass the bare error name, but luv also formats errors as
"NAME: message", so match on the leading name and keep the whole string as the
disconnect reason.

Fixes coder#316
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] 框选文本一直抱错误信息

1 participant