microsoft/aspire · error · Error

Callback not found

Error message

Callback not found: ${callbackId}

What it means

When the .NET AppHost invokes a callback by id over JSON-RPC ('invokeCallback'), the client looks the id up in its local callback registry. If no callback is registered under that id the request handler throws this Error, which is returned to the .NET side as a failed request.

Solutions

  1. Register the callback on the same AspireClient instance (and before connecting or before the .NET side dispatches) so the id exists in callbackRegistry.
  2. Log the requested callbackId and compare with the ids returned when registering callbacks to find the mismatch.
  3. If the client was recreated, re-register all callbacks and pass the new ids to the .NET side.
  4. Check for version mismatches between the generated TypeScript client and the AppHost that change callback id derivation.

Example fix

// before
const client = new AspireClient();
client.connect(); // callbacks never registered

// after
const client = new AspireClient();
client.registerCallback('myCallbackId', async (args) => { ... });
await client.connect();
Defensive patterns

Strategy: validation

Validate before calling

if (!callbackRegistry.has(callbackId)) {
  console.warn(`callback '${callbackId}' not registered; re-register before connecting`);
}

Type guard

function isRegistered(registry: Map<string, unknown>, id: string): boolean {
  return registry.has(id);
}

Try / catch

// server side of the RPC sees this error; on client:
try {
  await handleRemoteInvoke(callbackId, args);
} catch (err) {
  if (String(err.message).startsWith('Callback not found:')) reRegisterCallbacks();
  throw err;
}

Prevention

When it happens

Trigger: The .NET side calls invokeCallback with a callbackId that was never registered on the client, was registered on a different AspireClient instance, or whose registration was removed/disposed before invocation.

Common situations: Recreating a client (or reconnecting) without re-registering callbacks; callback registered after the .NET side already dispatched; stale handle referencing a callback from a previous session; mismatched callback ids across client versions.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/2cfeb362a3b46460. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Hosting.CodeGeneration.TypeScript/Resources/transport.mts:948

                clearTimeout(timeout);
                cleanupPendingListeners();

                try {
                    const reader = new rpc.SocketMessageReader(socket);
                    const writer = new rpc.SocketMessageWriter(socket);
                    this.connection = rpc.createMessageConnection(reader, writer);

                    this.connection.onClose(() => {
                        this.connection = null;
                    });
                    this.connection.onError((err: any) => console.error('JsonRpc connection error:', err));

                    // Handle callback invocations from the .NET side
                    this.connection.onRequest('invokeCallback', async (callbackId: string, args: unknown) => {
                        const callback = callbackRegistry.get(callbackId);
                        if (!callback) {
                            throw new Error(`Callback not found: ${callbackId}`);
                        }
                        try {
                            // The registered wrapper handles arg unpacking and handle wrapping
                            // Pass this client so handles can be wrapped with typed wrapper classes
                            return await Promise.resolve(callback(args, this));
                        } catch (error) {
                            const message = error instanceof Error ? error.message : String(error);
                            throw new Error(`Callback execution failed: ${message}`);
                        }
                    });

                    socket.on('error', onConnectedSocketError);
                    socket.on('close', onConnectedSocketClose);

                    const authToken = process.env.ASPIRE_REMOTE_APPHOST_TOKEN;
                    if (!authToken) {
                        throw new Error('ASPIRE_REMOTE_APPHOST_TOKEN environment variable is not set.');
                    }

View on GitHub (pinned to 25830f84bd)