JuliusBrussee/caveman · error

wire dialect must be "anthropic" or empty

Error message

wire dialect %q must be "anthropic" or empty

What it means

ValidateWireDialect rejects wire_dialect values the named compat mount cannot honor. Only "anthropic" or an empty string (default OpenAI shape) are accepted; anything else would cause the mount to parse an upstream's responses with the wrong wire grammar. Exported so the config loader fails at startup (issue #1026: Anthropic-dialect upstream silently misclassified).

Solutions

  1. Set wire_dialect to "anthropic" if the upstream speaks the Anthropic Messages API, or remove the key entirely for OpenAI-shaped upstreams.
  2. Fix casing/spelling exactly: lowercase "anthropic".
  3. Remove the wire_dialect line if the upstream is OpenAI-compatible and no override is needed.
  4. Check the proxy docs/version for the accepted dialect list — only "anthropic" and empty are valid.

Example fix

# before
[[compat_mount]]
name = "claude"
wire_dialect = "Anthropic"
# after
[[compat_mount]]
name = "claude"
wire_dialect = "anthropic"
Defensive patterns

Strategy: validation

Validate before calling

valid := dialect == "" || dialect == "anthropic"
if !valid {
    return fmt.Errorf("unsupported wire_dialect %q; use \"anthropic\" or omit", dialect)
}

Try / catch

if err := openaicompat.ValidateWireDialect(cfg.WireDialect); err != nil {
    log.Fatalf("mount %s: %v", cfg.Name, err)
}

Prevention

When it happens

Trigger: Config sets wire_dialect to a value not present in wireDialectUsageProvider — e.g. wire_dialect = "openai", "gemini", "Ollama", or a typo like "anthropic " — and validateCompat or NewNamedWithWireDialect calls ValidateWireDialect.

Common situations: Typo or casing mistake in config wire_dialect; copying a mount config that used a dialect name from another proxy; assuming other wire grammars are supported when only anthropic is.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/e9b1fb4e11b7f3c0. Report an issue: GitHub.

Appendix: source

Thrown at proxy/providers/openaicompat/openaicompat.go:348

// usage/cost telemetry keeps using the shared OpenAI-shape parser.
func NewNamed(name, baseURL string, forwardHeaders ...string) (providers.Adapter, error) {
	return NewNamedWithWireDialect(name, baseURL, "", forwardHeaders...)
}

// wireDialectUsageProvider maps a configured wire dialect to the provider whose
// usage parser implements it. "" is the default OpenAI-shape parser.
var wireDialectUsageProvider = map[string]string{
	"":          "",
	"anthropic": "anthropic",
}

// ValidateWireDialect rejects dialect values no named mount can honor. It is
// exported so the config loader fails at startup rather than silently parsing
// an Anthropic-dialect upstream with the OpenAI-shape rules — the exact
// misclassification issue #1026 reports.
func ValidateWireDialect(dialect string) error {
	if _, ok := wireDialectUsageProvider[dialect]; !ok {
		return fmt.Errorf("wire dialect %q must be \"anthropic\" or empty", dialect)
	}
	return nil
}

// NewNamedWithWireDialect builds a named mount whose upstream speaks a
// different wire grammar than the mount's OpenAI-compatible identity.
// "anthropic" is for an upstream that answers the Anthropic Messages protocol
// on the mount. It changes two things and nothing else:
//
//   - USAGE ACCOUNTING parses with the Anthropic dialect (Base.UsageProvider):
//     the usage block reports input_tokens EXCLUSIVE of cache reads/writes, so
//     the OpenAI-shape contradiction check reads a warm cache as malformed and
//     drops every such row from token accounting (issue #1026).
//   - COMPRESSION ZONES follow the Anthropic Messages grammar on Messages-path
//     requests and keep the OpenAI grammar on every other path (see
//     anthropicWireZones): the Anthropic extractor keys the live/frozen
//     boundary on the request's own cache_control breakpoints.
//

View on GitHub (pinned to 3ee70a1026)