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

  1. Regenerate the Go client so its transport matches the AppHost server protocol version
  2. Ensure you never pass nil callbacks where a callback is expected — registerCallback returns "" for nil and the server may then invoke a nonexistent ID
  3. Check that callback IDs returned from capability calls are preserved (not stripped) when passed back to the server via handles
  4. 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

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


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)