Skip to content

fix(listen): stop when Hookdeck ends the key's access, and show the MFA setup link - #477

Open
rahulbhadja wants to merge 1 commit into
mainfrom
fix/listen-stop-when-access-ends
Open

rahulbhadja wants to merge 1 commit into
mainfrom
fix/listen-stop-when-access-ends

Conversation

@rahulbhadja

@rahulbhadja rahulbhadja commented Oct 27, 2026 •

Copy link
Copy Markdown

What

hookdeck listen stops, with the reason, when Hookdeck won't accept its key again, instead of reconnecting forever. API errors for an organization that requires MFA now include the link to set it up.

Why

  • Once connected, listen retried every reconnect forever. When the key was revoked (close 4003), or a reconnect was refused with 401 (key revoked or expired) or 403 (no access to the project, or MFA required), it looped on "Connection lost, reconnecting..." and never said why.
  • The MFA refusal names the organization but not where to fix it. The API already sends the dashboard path (redirect), and the CLI dropped it.

Change

  • pkg/websocket: a 401 or 403 on connect, or a 4003 close, is recorded as AccessEndedError, with the server's message when it sends one and a default otherwise. 4003 is no longer logged as an error with the "file a GitHub issue" hint.
  • pkg/listen/proxy: when access has ended, listen reports it through the renderer, stops, and exits 1. 503, network drops, 1001, 1006 and 4001 still reconnect as before.
  • pkg/hookdeck and pkg/cmd: API errors keep the redirect path. A 403 whose path starts with /setup-mfa adds Set it up here: <dashboard>/setup-mfa?.... Only a path is accepted, joined to the CLI's own dashboard address, so a response can't point the user to another site. The error decoder only takes redirect when it's a string, so other bodies decode as before.

Example:

Connected. Waiting for events...
Your CLI key was revoked. Run `hookdeck login` to log in again.
error: Acme requires multi-factor authentication. Please set up MFA to continue.
Set it up here: https://dashboard.hookdeck.com/setup-mfa?organization_id=org_123

An upcoming API update adds the reason to refused connections (for example "This CLI key can't reach this project"), and the CLI shows it.

Testing

  • New tests: refused connects (401/403, with and without a reason) end access and 503 doesn't; a 4003 close ends access without an error log; listen stops after one connect when refused, and when revoked while connected; the MFA redirect is kept and a non-string one is ignored; the setup link is built from the CLI's dashboard and ignored for other sites, other paths and other errors. Each fix's tests fail without it.
  • go test -short ./pkg/... and go vet ./... pass.
  • Against a local Hookdeck stack running the server change: revoking a running listen from the dashboard stopped it with the revoked message (exit 1); removing the member stopped theirs with "This CLI key can't reach this project..." (exit 1); an MFA organization showed the setup link for listen and gateway source list; restarting the server reconnected and kept listening.

Not in this PR

  • The MFA link isn't added to MCP tool results, or to the rare case where MFA is switched on between listen reading its sources and starting its session.

🤖 Generated with Claude Code

…FA setup link

listen reconnected forever after a revoke (4003) or a refused reconnect (401/403). It now stops, says why and exits 1. MFA refusals show the dashboard link to set it up.

Co-Authored-By: Claude <noreply@anthropic.com>
@rahulbhadja

Copy link
Copy Markdown
Author

@leggetter FYI, this stops hookdeck listen from reconnecting forever after a revoke or lost access, and shows the MFA setup link. Please let me know if you'd like any changes.

@leggetter leggetter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. I ran the new unit tests with each fix reverted and they go red, so they're guarding the right things.

The slice 0 acceptance failure is the MCP harness flake (no JSON-RPC response with id 2), not this change. It's shown up on other PRs and passes locally. I'll re-run it before merging.

Not blocking: is an acceptance test against the built binary worth adding? Point --ws-base at a fake websocket server under the listen tag and check for exit 1, the message printed once and a single connect. The unit tests call proxy.Run() directly, so they don't cover that path.

Comment thread pkg/websocket/client.go
case http.StatusUnauthorized:
return "This CLI key is no longer valid. Run `hookdeck login` to log in again."
default:
return "Hookdeck refused the connection: this CLI key can't use this project. Run `hookdeck project use` to pick another one, or `hookdeck login`."

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: a 403 from a corporate proxy or CDN lands here too, and until the API sends a reason, this is what everyone sees. Maybe state the status, e.g. Hookdeck refused the connection (HTTP 403), rather than blame the key?

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.

2 participants