dotnet/aspnetcore · error · Error

Invalid payload for Completion message.

Error message

Invalid payload for Completion message.

What it means

A Completion message (type 3) is the terminal result of an invocation and the protocol forbids it from carrying both `result` and `error` at once. The invariant is: zero or one of them, never both, mirroring the IHubProtocol.ts CompletionMessage contract.

Source

Thrown at src/SignalR/clients/ts/signalr/src/JsonHubProtocol.ts:111

    private _isInvocationMessage(message: InvocationMessage): void {
        this._assertNotEmptyString(message.target, "Invalid payload for Invocation message.");

        if (message.invocationId !== undefined) {
            this._assertNotEmptyString(message.invocationId, "Invalid payload for Invocation message.");
        }
    }

    private _isStreamItemMessage(message: StreamItemMessage): void {
        this._assertNotEmptyString(message.invocationId, "Invalid payload for StreamItem message.");

        if (message.item === undefined) {
            throw new Error("Invalid payload for StreamItem message.");
        }
    }

    private _isCompletionMessage(message: CompletionMessage): void {
        if (message.result && message.error) {
            throw new Error("Invalid payload for Completion message.");
        }

        if (!message.result && message.error) {
            this._assertNotEmptyString(message.error, "Invalid payload for Completion message.");
        }

        this._assertNotEmptyString(message.invocationId, "Invalid payload for Completion message.");
    }

    private _isAckMessage(message: AckMessage): void {
        if (typeof message.sequenceId !== 'number') {
            throw new Error("Invalid SequenceId for Ack message.");
        }
    }

    private _isSequenceMessage(message: SequenceMessage): void {
        if (typeof message.sequenceId !== 'number') {
            throw new Error("Invalid SequenceId for Sequence message.");

View on GitHub (pinned to 294cab2f9b)

Solutions

  1. Fix the server to set either result or error on completion, never both.
  2. Capture the raw wire message with logMessageContent:true to confirm both fields are present.
  3. Upgrade the server SignalR package to a version where the completion invariant is enforced server-side.
  4. If testing a custom protocol, validate the message shape before sending.

Example fix

// before
{ "type": 3, "invocationId": "x", "result": 42, "error": "oops" }

// after
{ "type": 3, "invocationId": "x", "result": 42 }
Defensive patterns

Strategy: validation

Validate before calling

// when constructing/sending a completion
function buildCompletion(invocationId: string, result?: any, error?: string) {
  if (result !== undefined && error) {
    throw new Error('result and error are mutually exclusive');
  }
  return { type: 3, invocationId, result, error };
}

Type guard

function isValidCompletion(m: any): boolean {
  return !(m?.result && m?.error);
}

Prevention

When it happens

Trigger: Server sends {"type":3,"invocationId":"x","result":...,"error":"boom"}. This is a server-side bug or a custom hub/protocol implementation that sets both fields.

Common situations: A custom server hub that populates both fields, an older server with a serialization bug, or a buggy intermediate that injects an error string while preserving a result.

Related errors


AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06). Data as JSON: /api/errors/a432ec2fb22f57db. Report an issue: GitHub.