microsoft/aspire · error
callback ID missing
Error message
callback ID missing
What it means
invokeCallback rejects a server-originated callback request whose callback ID parameter is the empty string. The client dispatches callbacks by ID from its registry; an empty ID can never be registered (registerCallback returns "" only for nil callbacks), so this guards against malformed JSON-RPC callback requests from the AppHost side.
Solutions
- Regenerate the Go client so its transport matches the AppHost server protocol version
- Ensure you never pass nil callbacks where a callback is expected — registerCallback returns "" for nil and the server may then invoke a nonexistent ID
- Check that callback IDs returned from capability calls are preserved (not stripped) when passed back to the server via handles
- File/log the raw callback request to diagnose why params[0] is empty
Example fix
// before
builder.WithCallback(nil) // registers as "" -> server callback fails
// after
builder.WithCallback(func(args ...any) any {
// real implementation; gets a real callback ID
return nil
}) Defensive patterns
Strategy: type-guard
Validate before calling
if callback == nil {
log.Fatal("callbacks must be non-nil; nil callbacks register an empty ID")
} Type guard
func hasCallbackID(params []any) bool {
return len(params) > 0 && id, _ := params[0].(string); id != ""
} Prevention
- Never register nil callbacks
- Keep generated Go transport in sync with the AppHost server version
- Preserve callback IDs returned by capability calls
- Log raw server callback requests when diagnosing protocol issues
When it happens
Trigger: The AppHost sends a callback request whose params[0] (the callback ID string) is missing or empty — handleCallbackRequest extracts callbackID from params and calls invokeCallback, which returns this error and it is sent back as a JSON-RPC error with code -32000.
Common situations: Protocol/version mismatch between generated Go code and the AppHost server (server encoding callbacks in an unexpected shape); a nil Go callback was registered (registerCallback returned "") yet the server still references it; a DTO mutation callback invoked after its registration ID was never persisted.
Related errors
- callback not found
- -32000
- aspire: dict getter returned unexpected type %T
- aspire: dict.toObject: unexpected result type %T
- aspire: list getter returned unexpected type %T
AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16).
Data as JSON: /api/errors/a8afb0f07bfbc329.
Report an issue: GitHub.
Appendix: source
Thrown at src/Aspire.Hosting.CodeGeneration.Go/Resources/transport.go:564
id := fmt.Sprintf("callback_%d_%d", c.callbackCounter.Add(1), time.Now().UnixMilli())
c.callbacksMu.Lock()
c.callbacks[id] = callback
c.callbacksMu.Unlock()
return id
}
// unregisterCallback removes a callback by ID.
func (c *client) unregisterCallback(id string) bool {
c.callbacksMu.Lock()
defer c.callbacksMu.Unlock()
_, exists := c.callbacks[id]
delete(c.callbacks, id)
return exists
}
func (c *client) invokeCallback(callbackID string, args any) (any, error) {
if callbackID == "" {
return nil, errors.New("callback ID missing")
}
c.callbacksMu.RLock()
callback, ok := c.callbacks[callbackID]
c.callbacksMu.RUnlock()
if !ok {
return nil, fmt.Errorf("callback not found: %s", callbackID)
}
var positionalArgs []any
if argsMap, ok := args.(map[string]any); ok {
for i := 0; ; i++ {
key := fmt.Sprintf("p%d", i)
if val, exists := argsMap[key]; exists {
positionalArgs = append(positionalArgs, wrapIfHandle(val, c))
} else {
break
}View on GitHub (pinned to 25830f84bd)