dotnet/aspnetcore · critical · Error
Received persisted state for circuit ID
Error message
Received persisted state for circuit ID '${circuitId}', but the current circuit ID is '${this._circuitId}'. What it means
Inside `JS.SavePersistedState` the handler compares the incoming `circuitId` against `this._circuitId`. A mismatch means the server is sending state for a circuit this client is not (or no longer) hosting — accepting it would corrupt the local persisted-state slot, so the handler throws with both ids for diagnostics.
Solutions
- Ensure sticky sessions are configured for Blazor Server in a load-balanced deployment so a client always reaches the server holding its circuit.
- Confirm client and server are the same build version so circuit id semantics match across resume.
- Avoid manually invoking resume/reconnect concurrently from multiple code paths.
- If the error recurs, capture the reported pair of ids and correlate with server circuit lifecycle logs to find the stale push.
Defensive patterns
Strategy: validation
Validate before calling
// guard the handler
if (circuitId !== this._circuitId) { this._logger.log(LogLevel.Error, `Stale state for ${circuitId}, have ${this._circuitId}`); return; } Try / catch
try { /* save persisted state */ }
catch (e) { if (/current circuit ID/.test(e.message)) { /* mismatch - drop stale state */ return; } throw e; } Prevention
- Configure sticky sessions in load-balanced Blazor Server deployments.
- Match client/server Blazor versions across resume.
- Do not run concurrent resume/reconnect paths.
- Correlate the reported id pair with server circuit logs.
When it happens
Trigger: Server transmits `JS.SavePersistedState` with a circuit id different from the one returned by `StartCircuit`/`ResumeCircuit` and stored in `_circuitId`. Typically a stale server-side circuit record pushing state, or a reconnect where a new circuit id was assigned but an old state push is still in flight.
Common situations: Reconnect/resume flows where the server created a fresh circuit but a queued persisted-state message references the previous one; load-balancer/sticky-session misconfiguration routing a client's messages to a server holding a different circuit; multiple tabs sharing a connection unexpectedly; server restart losing circuit identity.
Related errors
- Circuit host not initialized.
- Cannot start a disposed circuit.
- Cannot start the circuit until Blazor Server has started.
- Circuit state is already in progress
- Circuit state is in progress
AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11).
Data as JSON: /api/errors/6ec95580488c9718.
Report an issue: GitHub.
Appendix: 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 3600ca084e)