dotnet/aspnetcore · critical · Error
Received persisted state for circuit ID '${circuitId}', but
Error message
Received persisted state for circuit ID '${circuitId}', but the current circuit ID is '${this._circuitId}'. What it means
Thrown in the 'JS.SavePersistedState' handler when the circuitId the server references does not equal the client's current _circuitId. Persisted state is circuit-scoped, so accepting it for a different circuit would corrupt state; the client refuses the mismatch.
Source
Thrown at src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts:169
this.changeActivity(1);
}
(this._dispatcher.beginInvokeJSFromDotNet as (...a: unknown[]) => unknown)
.call(this._dispatcher, asyncHandle, ...rest);
});
connection.on('JS.EndInvokeDotNet', (asyncCallId: string, success: boolean, resultJsonOrExceptionMessage: string) => {
if (asyncCallId) {
this.changeActivity(-1);
}
this._dispatcher.endInvokeDotNetFromJS(asyncCallId, success, resultJsonOrExceptionMessage);
});
connection.on('JS.ReceiveByteArray', this._dispatcher.receiveByteArray.bind(this._dispatcher));
connection.on('JS.SavePersistedState', (circuitId: string, components: string, applicationState: string) => {
if (!this._circuitId) {
throw new Error('Circuit host not initialized.');
}
if (circuitId !== this._circuitId) {
throw new Error(`Received persisted state for circuit ID '${circuitId}', but the current circuit ID is '${this._circuitId}'.`);
}
this._persistedCircuitState = { components, applicationState };
return true;
});
connection.on('JS.BeginTransmitStream', (streamId: number) => {
const readableStream = new ReadableStream({
start: (controller) => {
this.changeActivity(1);
connection.stream('SendDotNetStreamToJS', streamId).subscribe({
next: (chunk: Uint8Array) => controller.enqueue(chunk),
complete: () => { controller.close(); this.changeActivity(-1); },
error: (err) => { controller.error(err); this.changeActivity(-1); },
});
},
});
this._dispatcher.supplyDotNetStream(streamId, readableStream);View on GitHub (pinned to 294cab2f9b)
Solutions
- Stabilize the connection (network, proxy, WebSocket availability) so only one circuit lifecycle is active.
- Match client and server framework versions to keep the persisted-state protocol consistent.
- Avoid programmatically forcing reconnects in parallel with an in-flight resume; let the framework serialize them.
Defensive patterns
Strategy: try-catch
Try / catch
// The framework throws internally; surface a clean reconnect UI.
try { await circuitManager.resume(); }
catch (e) {
if (/current circuit ID/i.test(e.message)) {
showReconnectUI(); // prompt reload
} else throw e;
} Prevention
- Stabilize the WebSocket connection to avoid racing reconnects.
- Do not run multiple reconnect attempts in parallel.
- Keep client/server versions in sync.
When it happens
Trigger: A reconnection/restart assigned a new circuitId while a SavePersistedState message for the old circuit was still in flight; concurrent or racing circuit handshakes; server-side circuit reuse/rotation during a paused session.
Common situations: Unstable networks causing multiple rapid reconnects; tab freeze/thaw triggering pause-resume where the server rotated the circuit; version skew between client and server persistence protocols.
Related errors
- Circuit host not initialized.
- Blazor Server has already started.
- Cannot start the circuit until Blazor Server has started.
- Cannot start a disposed circuit.
- Circuit options have already been configured.
AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06).
Data as JSON: /api/errors/6ec95580488c9718.
Report an issue: GitHub.