BoundaryML/baml · error

unexpected result type in InvocationResponse: %T

Error message

unexpected result type in InvocationResponse: %T

What it means

If a successfully-decoded InvocationResponseSuccess contains neither an Object result nor any recognized result variant, the switch on success.GetResult() hits its default branch and reports the concrete Go type of the unexpected result. This guards the protocol: only known result kinds are accepted.

Source

Thrown at engine/language_client_go/pkg/callbacks.go:253

	switch resp := response.GetResponse().(type) {
	case *cffi.InvocationResponse_Error:
		safeSend(callback.channel, ResultCallback{Error: BamlError{Message: resp.Error}})
	case *cffi.InvocationResponse_Success:
		success := resp.Success
		if success == nil {
			safeSend(callback.channel, ResultCallback{Error: fmt.Errorf("nil success in InvocationResponse")})
		} else {
			switch result := success.GetResult().(type) {
			case *cffi.InvocationResponseSuccess_Object:
				decoded, decodeErr := decodeRawObjectImpl(callback.runtime, result.Object)
				if decodeErr != nil {
					safeSend(callback.channel, ResultCallback{Error: fmt.Errorf("failed to decode object handle: %w", decodeErr)})
				} else {
					safeSend(callback.channel, ResultCallback{HasData: true, Data: decoded})
				}
			default:
				safeSend(callback.channel, ResultCallback{Error: fmt.Errorf("unexpected result type in InvocationResponse: %T", success.GetResult())})
			}
		}
	default:
		safeSend(callback.channel, ResultCallback{Error: fmt.Errorf("unexpected response type in InvocationResponse")})
	}

	safeClose(callback.channel)
	callbackMutex.Lock()
	defer callbackMutex.Unlock()
	deleteCallback(id)
}

func create_unique_id(ctx context.Context, onTick OnTickCallbackData) (uint32, chan ResultCallback) {
	id := nextCallbackID.Add(1)
	callbackMutex.Lock()
	defer callbackMutex.Unlock()
	dynamicCallbacks[id] = CallbackData{channel: make(chan ResultCallback, 64), ctx: ctx, onTick: onTick, responseType: responseTypeValue}
	callbackLog("[CLIENT_GO_CALLBACK_ADD] id=%d map_size=%d", id, len(dynamicCallbacks))

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Upgrade the Go bindings to match the native runtime version (the %T in the message identifies the unknown variant).
  2. Pin both sides to the same BAML release.
  3. Search the BAML repo for the reported Go type to confirm it's a newer protocol addition.
  4. File an issue if versions already match — indicates a decoder gap.

Example fix

// before
go get github.com/boundaryml/baml@v0.8.0  # older bindings

// after
go get github.com/boundaryml/baml@latest     # match native runtime version
go mod tidy
Defensive patterns

Strategy: retry

Try / catch

res := <-ch
if res.Error != nil {
	if strings.Contains(res.Error.Error(), "unexpected result type in InvocationResponse") {
		// upgrade bindings to match native runtime, then retry once
		return retryWithUpgradedBindings(ctx)
	}
	return res.Error
}

Prevention

When it happens

Trigger: The native runtime emits a newer/unknown InvocationResponseSuccess result variant (e.g. after a protocol extension) while the Go bindings are older, so the oneof resolves to an unrecognized case.

Common situations: Version skew between the baml CLI-generated native library and the Go cffi bindings; custom forks adding new result types.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/d8e3f475c8baa6b2. Report an issue: GitHub.