dotnet/aspnetcore · error · Error

Message is incomplete.

Error message

Message is incomplete.

What it means

Thrown by TextMessageFormat.parse when the input string does not end with the RecordSeparator character (U+001E). SignalR's text protocol frames every message with a trailing 0x1E so multiple messages can be batched in one stream; this guard rejects a partial/unterminated frame before splitting. It is a low-level framing invariant used by JsonHubProtocol and the handshake decoder, not an API the application calls directly.

Solutions

  1. Ensure the transport's receive path feeds complete frames to the protocol (the built-in WebSocket/LongPolling/SSE transports already buffer correctly - prefer them over a custom transport).
  2. If you call TextMessageFormat.parse directly in tests, append TextMessageFormat.RecordSeparator to each input message before parsing.
  3. Inspect the raw bytes just before the throw: a missing trailing 0x1E usually points to a truncated chunk from the network layer or a proxy mangling binary/text control chars.
  4. Upgrade the client to the version matching your server; framing rules are stable but custom forks sometimes drop the separator.

Example fix

// before (test/fixture)
const msgs = TextMessageFormat.parse('{"type":6}'); // throws

// after
const rs = String.fromCharCode(0x1e);
const msgs = TextMessageFormat.parse('{"type":6}' + rs);
Defensive patterns

Strategy: validation

Validate before calling

function isCompleteTextFrame(input: string): boolean {
  return input.length > 0 && input[input.length - 1] === String.fromCharCode(0x1e);
}

// before calling TextMessageFormat.parse:
if (!isCompleteTextFrame(raw)) {
  // buffer more bytes or surface a framing error; do not call parse
}

Type guard

function isCompleteTextFrame(input: unknown): input is string {
  return typeof input === 'string'
    && input.length > 0
    && input.charCodeAt(input.length - 1) === 0x1e;
}

Try / catch

try {
  const messages = TextMessageFormat.parse(buffer);
} catch (e) {
  if (e instanceof Error && e.message === 'Message is incomplete.') {
    // keep buffering; this chunk was partial
  } else throw e;
}

Prevention

When it happens

Trigger: The parse() method receives a string whose last character is not String.fromCharCode(0x1e). This happens when a WebSocket/Long-polling chunk delivers a fragment of a message, when a custom transport delivers bytes before the full frame arrives, or when test fixtures feed an unframed JSON string into the protocol layer.

Common situations: Buffering logic upstream was removed or bypassed; a partial read raced ahead of the framing buffer; a server/proxy stripped control characters; unit tests pass raw JSON without appending the record separator.

Related errors


AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11). Data as JSON: /api/errors/96d2aa4cd5cb8681. Report an issue: GitHub.

Appendix: source

Thrown at src/SignalR/clients/ts/signalr/src/TextMessageFormat.ts:16

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

// Not exported from index
/** @private */
export class TextMessageFormat {
    public static RecordSeparatorCode = 0x1e;
    public static RecordSeparator = String.fromCharCode(TextMessageFormat.RecordSeparatorCode);

    public static write(output: string): string {
        return `${output}${TextMessageFormat.RecordSeparator}`;
    }

    public static parse(input: string): string[] {
        if (input[input.length - 1] !== TextMessageFormat.RecordSeparator) {
            throw new Error("Message is incomplete.");
        }

        const messages = input.split(TextMessageFormat.RecordSeparator);
        messages.pop();
        return messages;
    }
}

View on GitHub (pinned to 3600ca084e)