SignalR/SignalR · error · FormatException

Invalid cursor.

Error message

Invalid cursor.

What it means

Thrown by Cursor.GetCursors when the cursor string is null or empty. Cursors are opaque tokens SignalR uses to track how many messages a client has consumed from each topic/stream; the client echoes them back on reconnect. A null/empty cursor means the client sent nothing or the server-side caller bypassed normal flow. This is treated as a hard format error (unlike a wrong-prefix cursor, which returns null gracefully).

Source

Thrown at src/Microsoft.AspNet.SignalR.Core/Messaging/Cursor.cs:135

            return sb.ToString();
        }

        public static List<Cursor> GetCursors(string cursor, string prefix)
        {
            return GetCursors(cursor, prefix, s => s);
        }

        public static List<Cursor> GetCursors(string cursor, string prefix, Func<string, string> keyMaximizer)
        {
            return GetCursors(cursor, prefix, (key, state) => ((Func<string, string>)state).Invoke(key), keyMaximizer);
        }

        public static List<Cursor> GetCursors(string cursor, string prefix, Func<string, object, string> keyMaximizer, object state)
        {
            // Technically GetCursors should never be called with a null value, so this is extra cautious
            if (String.IsNullOrEmpty(cursor))
            {
                throw new FormatException(Resources.Error_InvalidCursorFormat);
            }

            // If the cursor does not begin with the prefix stream, it isn't necessarily a formatting problem.
            // The cursor with a different prefix might have had different, but also valid, formatting.
            // Null should be returned so new cursors will be generated
            if (!cursor.StartsWith(prefix, StringComparison.Ordinal))
            {
                return null;
            }

            var signals = new HashSet<string>();
            var cursors = new List<Cursor>();
            string currentKey = null;
            string currentEscapedKey = null;
            ulong currentId;
            bool escape = false;
            bool consumingKey = true;
            var sb = new StringBuilder();

View on GitHub (pinned to 693053b89a)

Solutions

  1. Verify the client sends the cursor (messageId) query parameter on every reconnect/poll request after the initial negotiate.
  2. If you are calling GetCursors directly, guard against null/empty before calling and pass a valid cursor or skip the call.
  3. Check for proxy/load-balancer URL rewriting that strips query parameters.
  4. Update the client library to a version compatible with the server.

Example fix

// before — caller passes null cursor directly
var cursors = Cursor.GetCursors(null, prefix);

// after — guard before calling
if (String.IsNullOrEmpty(cursor)) {
    cursors = null; // let the framework generate new cursors
} else {
    cursors = Cursor.GetCursors(cursor, prefix);
}
Defensive patterns

Strategy: validation

Validate before calling

// Check cursor before calling GetCursors
if (string.IsNullOrEmpty(cursor)) {
    // Do not call GetCursors; return null to let framework generate new cursors
    return null;
}
var cursors = Cursor.GetCursors(cursor, prefix);

Try / catch

try {
    var cursors = Cursor.GetCursors(cursor, prefix);
} catch (FormatException ex) when (ex.Message.Contains("Invalid cursor")) {
    // treat as a fresh connection — generate new cursors
    logger.Warn("Client sent empty/invalid cursor, regenerating", ex);
    cursors = null;
}

Prevention

When it happens

Trigger: GetCursors is invoked with a null or zero-length cursor argument. This can happen if a client reconnect request omits the cursor (messageId) query parameter entirely, or if internal code passes null instead of letting the framework generate a fresh cursor set.

Common situations: A misbehaving or very old client that does not send a message cursor on poll/reconnect; a custom host that strips the cursor parameter; a proxy that drops query-string parameters; manual testing with a hand-crafted URL missing the cursor.

Related errors


AI-assisted analysis of SignalR/SignalR@693053b89a (2026-08-13). Data as JSON: /api/errors/82893b710dc44162. Report an issue: GitHub.