dotnet/aspnetcore · error
JS object instance with Id '${id}' does not exist. It may ha
Error message
JS object instance with Id '${id}' does not exist. It may have been disposed. What it means
Thrown inside the JSON reviveReference reviver when a payload carries a {jsObjectId: N} marker but cachedJSObjectsById[N] is undefined. Unlike 83 (which is thrown on the invoke path for findJSFunction), this fires during deserialization of a value being passed into JS — a JS object reference being sent back across the boundary that no longer exists in the cache.
Source
Thrown at src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts:739
public serializeAsArg() {
return { [dotNetObjectRefKey]: this._id };
}
}
attachReviver(function reviveReference(key: any, value: any) {
if (value && typeof value === "object") {
if (value.hasOwnProperty(dotNetObjectRefKey)) {
return new DotNetObject(value[dotNetObjectRefKey], currentCallDispatcher!);
} else if (value.hasOwnProperty(jsObjectIdKey)) {
const id = value[jsObjectIdKey];
const jsObject = cachedJSObjectsById[id];
if (jsObject) {
return jsObject.getWrappedObject();
}
throw new Error(`JS object instance with Id '${id}' does not exist. It may have been disposed.`);
} else if (value.hasOwnProperty(byteArrayRefKey)) {
const index = value[byteArrayRefKey];
const byteArray = currentCallDispatcher!.processByteArray(index);
if (byteArray === undefined) {
throw new Error(`Byte array index '${index}' does not exist.`);
}
return byteArray;
} else if (value.hasOwnProperty(dotNetStreamRefKey)) {
const streamId = value[dotNetStreamRefKey];
const streamPromise = currentCallDispatcher!.getDotNetStreamPromise(streamId);
return new DotNetStream(streamPromise);
}
}
// Unrecognized - let another reviver handle it
return value;
});
View on GitHub (pinned to 294cab2f9b)
Solutions
- Do not persist the serialized form of a JSObjectReference across call boundaries or sessions.
- Dispose references symmetrically and stop serializing them after disposal.
- In tests, seed cachedJSObjectsById or avoid replaying raw payloads.
- On circuit reconnect, re-acquire references instead of reusing stale ids.
Example fix
// before
// .NET caches string json = JsonSerializer.Serialize(jsObjRef) and reuses after dispose
// after
// re-acquire the JS object via interop after reconnect
var fresh = await JS.InvokeAsync<IJSObjectReference>("getHandle"); Defensive patterns
Strategy: validation
Validate before calling
// do not persist serialized JSObjectReference across calls
// reacquire on the live dispatcher each time
var fresh = await JS.InvokeAsync<IJSObjectReference>("getHandle"); Type guard
null
Try / catch
try { JSON.parse(json, reviver); } catch (e) { if (/does not exist\. It may have been disposed/i.test(e.message)) { /* reacquire ref */ } else throw e; } Prevention
- Never cache the serialized form of a JSObjectReference.
- Re-acquire references after circuit/worker reconnect.
- Dispose references symmetrically with .NET.
When it happens
Trigger: .NET passes back to JS a previously-captured {__jsObjectId:N} whose object was disposed via disposeJSObjectReferenceById; deserializing an old message in a test harness without the cache populated; crossing a circuit/worker boundary where the cache is per-dispatcher.
Common situations: Round-tripping a JSObjectReference JSON blob after it was disposed; serializing a stale ref captured before a reconnect; replaying a captured interop payload in tests against a fresh runtime; a long-lived .NET object that held onto a serialized jsObjectId across a circuit restart.
Related errors
- JS object instance with ID ${targetInstanceId} does not exis
- Byte array index '${index}' does not exist.
- Persisting state is only allowed during an OnPersisting call
- Interop methods are already registered for renderer ${render
- Interop methods are not registered for renderer ${rendererId
AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06).
Data as JSON: /api/errors/84e2e8cceda2f63f.
Report an issue: GitHub.