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
- Register the callback on the same AspireClient instance (and before connecting or before the .NET side dispatches) so the id exists in callbackRegistry.
- Log the requested callbackId and compare with the ids returned when registering callbacks to find the mismatch.
- If the client was recreated, re-register all callbacks and pass the new ids to the .NET side.
- 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
- Register all callbacks before connect() and re-register after any reconnect.
- Log registered callback ids at startup for diagnosis.
- Keep one client instance per session; do not rebuild clients mid-session.
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
- Callback ' ' timed out after s
- Callback execution failed
- Failed to authenticate to the AppHost server.
- No client connection available for callback invocation
- -32602
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)