Skip to content

JSONOutputFormatParam.Type elided from request body, causes API to hang structured-output requests #328

@zwowbrands

Description

@zwowbrands

Bug

anthropic.JSONOutputFormatParam declares:

// The properties Schema, Type are required.
type JSONOutputFormatParam struct {
    Schema map[string]any `json:"schema,omitzero" api:"required"`
    // This field can be elided, and will marshal its zero value as "json_schema".
    Type constant.JSONSchema `json:"type" default:"json_schema"`
    paramObj
}

The comment promises the zero value will marshal as "json_schema". In practice, the field is elided entirely from the marshaled body.

Reproduction

SDK version: v1.33.0. Go: go1.25.0. OS: Windows.

params := anthropic.MessageNewParams{
    Model:     "claude-sonnet-4-6",
    MaxTokens: 100,
    Messages: []anthropic.MessageParam{
        anthropic.NewUserMessage(anthropic.NewTextBlock("hi")),
    },
    OutputConfig: anthropic.OutputConfigParam{
        Format: anthropic.JSONOutputFormatParam{
            Schema: map[string]any{"type": "object", "properties": map[string]any{}},
        },
    },
}

Captured request body via a logging RoundTripper:

"output_config":{"format":{"schema":{"type":"object","properties":{}}}}

The Python SDK (anthropic 0.92.0) on the same params produces:

"output_config":{"format":{"type":"json_schema","schema":{...}}}

Setting Type: constant.JSONSchema("json_schema") explicitly does not change the output (still elided). option.WithJSONSet("output_config.format.type", "json_schema") also does not insert the field.

Impact

When output_config.format.type is missing, the Anthropic API silently hangs the request rather than returning a 400. The SDK's CalculateNonStreamingTimeout fires after 10 minutes, returning context deadline exceeded. This is reproducible on claude-sonnet-4-6 with a real prompt; trivial requests sometimes complete (likely a different server-side fast path).

In our case, the same prompt and data run fine in seconds via the Python SDK and time out at 10 minutes via Go, with the only difference being the missing type field.

Workaround

A custom http.RoundTripper that injects the missing field into outgoing /v1/messages bodies:

const missing = `"format":{"schema":`
const fixed   = `"format":{"type":"json_schema","schema":`
body = bytes.Replace(body, []byte(missing), []byte(fixed), 1)

Suggested fix

Either:

  1. Make the encoder honor the default struct tag so the zero value of Type marshals as "json_schema".
  2. Or drop the elision and always emit the field.

Happy to provide a packet capture or any additional detail.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions