Skip to content

fix(websocket): stop requiring ACP subprotocol - #17

Open
roseengineering wants to merge 1 commit into
formulahendry:mainfrom
roseengineering:fix/websocket-subprotocol
Open

roseengineering wants to merge 1 commit into
formulahendry:mainfrom
roseengineering:fix/websocket-subprotocol

Conversation

@roseengineering

@roseengineering roseengineering commented Jul 27, 2026 •

Copy link
Copy Markdown

I’m learning ACP by testing this browser app against my Goose server, and I made this fix because the websocket handshake was failing in the browser path.

I reproduced the browser-side failure on main and confirmed it happens before session creation:

  • the browser app sends a non-empty Sec-WebSocket-Protocol header on the websocket upgrade
  • Goose does not respond with a matching subprotocol
  • the handshake fails with WebSocket connect failed

This patch removes the unconditional acp.v1 websocket subprotocol for plain ACP websocket connections. It only adds a subprotocol when bearer auth actually needs to be tunneled through the browser WebSocket API.

I was learning ACP against my Goose server and the browser websocket handshake kept failing. The client was always offering acp.v1, which can make a plain /acp websocket reject the upgrade if the server does not echo that subprotocol. This patch leaves normal ACP websocket connections subprotocol-free and only adds a subprotocol when bearer auth has to be tunneled through the browser API.
@roseengineering
roseengineering marked this pull request as ready for review July 28, 2026 17:37
Copilot AI review requested due to automatic review settings July 28, 2026 17:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adjusts the ACP WebSocket transport to avoid sending a Sec-WebSocket-Protocol header unless it’s actually needed for browser-auth tunneling, preventing browser handshake failures when servers don’t negotiate the ACP subprotocol.

Changes:

  • Avoid passing an empty subprotocol list to the WebSocket constructor (omit the parameter entirely when none are needed).
  • Make subprotocol advertisement conditional, so plain ACP WebSocket connections don’t always include acp.v1.
Comments suppressed due to low confidence (1)

src/lib/transport/websocket.ts:294

  • buildSubprotocols currently advertises acp.v1 whenever an Authorization header is present, even if it is not a Bearer token (or if the Bearer parse fails). That can reintroduce the browser handshake failure this PR is trying to avoid, because it will send a non-empty Sec-WebSocket-Protocol header without actually tunneling auth. Consider only returning subprotocols when a valid Authorization: Bearer … value is successfully parsed, and update the doc comment accordingly.
  if (!headers) return [];
  const auth = pickHeader(headers, 'authorization');
  if (!auth) return [];
  const protocols: string[] = [ACP_SUBPROTOCOL];
  {
    const m = /^Bearer\s+(.+)$/i.exec(auth.trim());
    if (m) {
      // Subprotocol tokens cannot contain whitespace; the bearer token in
      // practice is base64-ish so this is safe, but we still strip just in
      // case to avoid handing the browser an invalid header value.
      const tok = m[1].replace(/\s+/g, '');
      protocols.push(`bearer.${tok}`);
    }
  }
  return protocols;

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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