Skip to content

Canceling a server-stream request throws an error with non-standard Code 20 instead of Canceled #1648

@eKazim

Description

@eKazim

Describe the bug

I've found an issue when canceling a server-streaming gRPC-web request using an AbortController.

The call correctly aborts, but instead of throwing a ConnectError with Code.Canceled (value 1), it throws an error with the message "BodyStreamBuffer was aborted" and a code of 20.

As per the gRPC specification, a client-side cancellation should be mapped to the Canceled status. Furthermore, the code 20 does not exist in the official Code enum, which makes it impossible to reliably check for this specific error type in a standard way (e.g., error.code === Code.Canceled). This can break application logic that needs to differentiate between a cancellation and other types of network or server errors.

The following example uses the standard ElizaService and cancels the server-streaming introduce call after 500ms.

import { createClient, ConnectError, Code } from '@connectrpc/connect';
import { createGrpcWebTransport } from '@connectrpc/connect-web';
import { ElizaService } from '@buf/connectrpc_eliza.connectrpc_es/eliza_connect.js';
import type { IntroduceRequest } from '@buf/connectrpc_eliza.connectrpc_es/eliza_pb.js';

// Client setup
const transport = createGrpcWebTransport({
  baseUrl: 'https://demo.connectrpc.com',
});

const client = createClient(ElizaService, transport);

// Function to call the service and cancel it
async function callAndCancel() {
  const controller = new AbortController();

  // Schedule cancellation
  setTimeout(() => {
    console.log('Aborting request...');
    controller.abort();
  }, 500);

  try {
    const request: IntroduceRequest = { name: 'User' };
    const stream = client.introduce(request, { signal: controller.signal });

    for await (const response of stream) {
      console.log('Received response:', response.sentence);
    }
    console.log('Stream finished without error.');
  } catch (e) {
    if (e instanceof ConnectError) {
      console.error('Caught a ConnectError:');
      console.error(' > Message:', e.message);
      console.error(' > Code:', e.code); // This is 20
      console.error(' > Is Code.Canceled?', e.code === Code.Canceled); // This is false
    } else {
      console.error('Caught an unexpected error:', e);
    }
  }
}

callAndCancel();

Expected Behavior

The catch block should receive a ConnectError where error.code is equal to Code.Canceled (with a value of 1). The console output should look like this:

Caught a ConnectError:
 > Message: BodyStreamBuffer was aborted
 > Code: 1
 > Is Code.Canceled? true

Actual Behavior

The catch block receives a ConnectError where error.code is 20 and the message is "BodyStreamBuffer was aborted". The console output is:

Caught a ConnectError:
 > Message: BodyStreamBuffer was aborted
 > Code: 20
 > Is Code.Canceled? false

Environment (please complete the following information):

  • @connectrpc/connect: 2.1.1
  • @connectrpc/connect-web: 2.1.1
  • @bufbuild/protobuf: 2.11.0
  • Browser: Google Chrome 144.0.7559.133

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions