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

  1. Do not persist the serialized form of a JSObjectReference across call boundaries or sessions.
  2. Dispose references symmetrically and stop serializing them after disposal.
  3. In tests, seed cachedJSObjectsById or avoid replaying raw payloads.
  4. 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

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


AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06). Data as JSON: /api/errors/84e2e8cceda2f63f. Report an issue: GitHub.