Skip to content

Fixed PreToolUse and Stop hook HTTP 502 errors (regression in claude-code 2.1.118) - #86

Merged
oleg68 merged 1 commit into
nbplugins:mainfrom
oleg68:bugfix/hook
Apr 24, 2026
Merged

oleg68 merged 1 commit into
nbplugins:mainfrom
oleg68:bugfix/hook

Conversation

@oleg68

@oleg68 oleg68 commented Apr 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

Two root causes for HTTP 502 on PreToolUse/Stop hooks:

  • Thread exhaustion: HookServlet blocked a Jetty thread per pending request. With multiple diff dialogs open simultaneously the thread pool was exhausted. Fixed by converting HookServlet to async Jetty API (startAsync / whenComplete).
  • IPv6 vs IPv4: Hook and MCP SSE URLs used localhost which on systems where localhost resolves to ::1 (IPv6) first causes claude-code's Bun HTTP client to connect to ::1:PORT. Jetty's ServerConnector binds to 0.0.0.0 (IPv4 only) and refuses the connection; claude-code reports this as "HTTP 502". Fixed by using 127.0.0.1 instead of localhost in all URLs written to settings.local.json.

Test plan

  • MCPSseServerPerSessionTest TC-5 and TC-6: verify async servlet does not block threads and N concurrent hook requests all return 200
  • claude-launch-tests/test_hook_concurrent.py: starts claude, asks it to write multiple files simultaneously, verifies no 502 in PTY output
  • claude-launch-tests/test_hook_ipv6_localhost.py: starts an IPv4-only HTTP server, verifies localhost URL causes 502 while 127.0.0.1 URL works correctly

🤖 Generated with Claude Code

…code 2.1.118)

Two root causes:

1. Hook servlet blocked a Jetty thread per pending request (user had not yet
   clicked Accept/Reject in the diff dialog). When multiple diffs were open
   simultaneously the thread pool was exhausted and subsequent requests returned
   502. Fixed by converting HookServlet to async Jetty API (startAsync /
   whenComplete) so threads are released immediately after reading the request.
   Added MCPSseServerPerSessionTest TC-5/TC-6 and test_hook_concurrent.py to
   reproduce and guard against regression.

2. Hook and MCP SSE URLs used "localhost" which on systems where localhost
   resolves to ::1 (IPv6) first causes claude-code's Bun HTTP client to connect
   to ::1:PORT. Jetty's ServerConnector binds to 0.0.0.0 (IPv4 only) and refuses
   the connection; claude-code reports the failure as "HTTP 502". Fixed by using
   127.0.0.1 instead of localhost in all hook and SSE URLs written to
   settings.local.json. Added test_hook_ipv6_localhost.py to verify.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@oleg68
oleg68 merged commit a9684d6 into nbplugins:main Apr 24, 2026
3 checks passed
@oleg68
oleg68 deleted the bugfix/hook branch April 24, 2026 14:09
oleg68 added a commit that referenced this pull request Apr 24, 2026
…code 2.1.118) (#87)

Backport of #86 to release/0.21.

## Summary

Two root causes for HTTP 502 on PreToolUse/Stop hooks:

- **Thread exhaustion**: `HookServlet` blocked a Jetty thread per
pending request. With multiple diff dialogs open simultaneously the
thread pool was exhausted. Fixed by converting `HookServlet` to async
Jetty API (`startAsync` / `whenComplete`).
- **IPv6 vs IPv4**: Hook and MCP SSE URLs used `localhost` which on
systems where `localhost` resolves to `::1` (IPv6) first causes
claude-code's Bun HTTP client to connect to `::1:PORT`. Jetty's
`ServerConnector` binds to `0.0.0.0` (IPv4 only) and refuses the
connection; claude-code reports this as "HTTP 502". Fixed by using
`127.0.0.1` instead of `localhost` in all URLs written to
`settings.local.json`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Oleg Samarin <osamarin@openintegration.inc>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

1 participant