Skip to content

Show why the gateway refused an exec, attached run or log stream - #199

Merged
ndbroadbent merged 4 commits into
mainfrom
nathan/websocket-refusal-errors
Oct 27, 2026
Merged

ndbroadbent merged 4 commits into
mainfrom
nathan/websocket-refusal-errors

Conversation

@ndbroadbent

Copy link
Copy Markdown
Member

Closes #196.

What changes

The Convox SDK (stdsdk.Client.Websocket) drops the response gorilla returns with websocket.ErrBadHandshake. So whenever the gateway refused an exec, an attached run or logs (401, 403, 429, 400), the CLI printed only:

Error: websocket: bad handshake

The CLI already owns both websocket dialers (secureConvoxSDK sets NetDialTLSContext to verify the gateway's certificate). Each connection is now wrapped in a handshakeRecordingConn:

  • It records what the gateway sends until the handshake is over.
  • On an accepted handshake (101 Switching Protocols), it stops at the headers and drops what it has, so nothing from the stream is kept.
  • On a refused one, it keeps the response, body included, up to 4 KB. gorilla only reads 1 KB of the body anyway.

Execute then turns ErrBadHandshake into the status and the gateway's reason, through RenderGatewayError, so JSON error fields are unwrapped and control characters are neutralised:

Error: the gateway refused the connection (403 Forbidden): You don't have permission to run processes.

With no body, it shows just the status. If nothing was recorded, the SDK's error stands, and the new error still unwraps to ErrBadHandshake. The plain ws:// dialer (a gateway on this machine) records too, via NetDialContext.

The CLI's early checks stay: a command too long for the audit log, or a missing justification, is still refused before any MFA prompt. Their comments no longer say the gateway's refusal can't be shown. Also updated: internal/cli/AGENTS.md ("Exec and log streams") and the audit logging page.

Testing

  • Unit tests (-race, and nofido), through stdsdk's real Websocket call against httptest gateways:
    • plain-text 403, JSON 429, empty 401, a 400 with control characters;
    • the same over TLS with a pinned CA;
    • responses split across reads and cut at the cap;
    • an accepted stream whose data looks like an HTTP refusal is not recorded;
    • other errors pass through unchanged.
  • CLI E2E: a new step logs in as a viewer and runs exec. The gateway refuses it at the handshake, and the CLI now prints Error: the gateway refused the connection (403 Forbidden): You don't have permission to run processes. The full suite passed locally. The deployer and viewer stages moved to scripts/lib/cli-e2e/roles.sh, because stages.sh hit the 500-line limit.

…tream

The Convox SDK drops the response to a refused WebSocket handshake, so any refusal of exec, an attached
run or logs printed only "websocket: bad handshake". The CLI's websocket dialers now wrap each connection
so it records the gateway's response to a refused handshake (and stops at an accepted one's headers, so
nothing from the stream is kept). Execute turns websocket.ErrBadHandshake into the status and the
gateway's reason, for example:

    the gateway refused the connection (429 Too Many Requests): too many MFA attempts, try again in a few minutes

Closes #196
Moves the deployer and viewer stages into roles.sh: stages.sh was at the 500-line limit.
@coderabbitai

coderabbitai Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

  • Run on-demand review

This review includes 15 billable files and costs up to $3.75.

  • Ask an admin to make reviews automatic

Open in CodeRabbit

Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing.

Or wait 11 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available. Your 69 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: d02e25fb-0402-4e22-94ea-e0d0ea615cfe
Commits

Reviewing files that changed from the base of the PR and between 88c8664 and a7816a3.

Files selected for processing: 15
  • convox.yml
  • docs/src/content/docs/concepts/audit-logging.mdx
  • internal/cli/AGENTS.md
  • internal/cli/convox_process_approval.go
  • internal/cli/convox_process_command.go
  • internal/cli/convox_process_command_test.go
  • internal/cli/convox_processes_test.go
  • internal/cli/root.go
  • internal/cli/sdk_tls.go
  • internal/cli/websocket_refusal.go
  • internal/cli/websocket_refusal_test.go
  • scripts/cli-e2e.sh
  • scripts/lib/cli-e2e/roles.sh
  • scripts/lib/cli-e2e/stages.sh
  • web/package.json
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@deepsource-io

deepsource-io Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 88c8664...a7816a3 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
JavaScript Oct 27, 2026 11:07p.m. Review ↗
Go Oct 27, 2026 11:07p.m. Review ↗

Important

AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.

@ndbroadbent
ndbroadbent merged commit fe78d6c into main Oct 27, 2026
14 checks passed
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.

CLI shows only "websocket: bad handshake" when the gateway refuses an exec, attached run or logs stream

1 participant