BoundaryML/baml · error

encoding client options: %w

Error message

encoding client options: %w

What it means

encodeClientRegistry serializes each client's options map into CFFI structures before crossing the FFI boundary to the BAML runtime. If serde.EncodeMapEntries fails on a client's options map, the failure is wrapped as "encoding client options: %w". This means one of the values in the client options map is not encodable (unsupported type, nil where a value is required, etc.), so the registry cannot be marshaled.

Source

Thrown at engine/language_client_go/pkg/rawobjects_client_registry.go:43

		c.clients = make(clientRegistryMap)
	}

	c.clients[name] = clientProperty{
		provider: provider,
		options:  options,
	}
}

func (c *ClientRegistry) SetPrimaryClient(name string) {
	c.primary = &name
}

func encodeClientRegistry(clientRegistryVal *ClientRegistry) (*cffi.HostClientRegistry, error) {
	clientOffsets := make([]*cffi.HostClientProperty, 0, len(clientRegistryVal.clients))
	for name, client := range clientRegistryVal.clients {
		options, err := serde.EncodeMapEntries(client.options, "client options")
		if err != nil {
			return nil, fmt.Errorf("encoding client options: %w", err)
		}
		clientOffsets = append(clientOffsets, &cffi.HostClientProperty{
			Name:        name,
			Provider:    client.provider,
			RetryPolicy: client.retryPolicy,
			Options:     options,
		})
	}

	clients := cffi.HostClientRegistry{
		Clients: clientOffsets,
		Primary: clientRegistryVal.primary,
	}

	return &clients, nil
}

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Inspect the wrapped inner error to find which option entry failed, then restrict client options to supported primitive types (string, int, float, bool).
  2. Normalize options coming from JSON/config files with explicit decoding into a typed struct instead of map[string]any passthrough.
  3. Remove or replace function/pointer/nested-struct option values with their serializable equivalents.
  4. Log the full options map (fmt.Sprintf("%#v")) before registering the client to spot the offending entry.

Example fix

// before
b.SetClientRegistry(registryWith(map[string]any{"retries": customRetryer{}, "url": nil}))
// after
b.SetClientRegistry(registryWith(map[string]any{
    "retries": 3,          // supported primitive
    "url":    "https://example.com",
}))
Defensive patterns

Strategy: validation

Validate before calling

func validateClientOptions(opts map[string]any) error {
    for k, v := range opts {
        switch v.(type) {
        case string, int, int64, float64, bool, nil:
            continue
        default:
            return fmt.Errorf("client option %q has unsupported type %T", k, v)
        }
    }
    return nil
}

Type guard

func isEncodableOption(v any) bool {
    switch v.(type) {
    case string, int, int64, float64, bool, nil:
        return true
    }
    return false
}

Try / catch

if err := validateClientOptions(clientOptions); err != nil {
    return fmt.Errorf("rejecting client before registration: %w", err)
}
// then register; wrap any residual error:
if err := registerClient(clientOptions); err != nil {
    return fmt.Errorf("client registration failed: %w", err)
}

Prevention

When it happens

Trigger: Registering or encoding a client (e.g. in baml.ClientRegistry / b.SetClientRegistry) whose options map contains a value type the CFFI serializer cannot encode — nested non-primitive types, func values, or malformed entries.

Common situations: Passing arbitrary Go values as client options instead of the supported primitives (string/number/bool); typos in option keys leading to structurally wrong maps; embedding options read from JSON with unexpected value shapes.

Understand the failure class

Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.

Related errors


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