You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The /v1-suffix bug family (#745, ffa7cf4, the deleted WarnAndStripV1Suffix fossil) is a recurring type error: "service root without /v1" and "OpenAI base with /v1" are two different types carried in one string. The invariant is currently documentation-enforced (CLAUDE.md pitfall 6, a doc comment on buildCustomEndpointEntryWithOptions) — and documentation-enforced invariants are exactly what decayed into this incident: the codebase carried two contradictory theories of who appends /v1 for months, with no test or build failure.
typeServiceRootstring// scheme://host:port — never a pathtypeOpenAIBasestring// ServiceRoot + "/v1" — what a LiteLLM openai/ api_base must befuncNewServiceRoot(rawstring) (ServiceRoot, error) // trims trailing "/"; REJECTS a trailing /v1 with an error — never silently stripsfunc (rServiceRoot) OpenAI() OpenAIBasefunc (rServiceRoot) ChatCompletions() string
buildCustomEndpointEntryWithOptions takes OpenAIBase; the discovery bug class becomes a compile error. Buyer-side code keeps ServiceRoot and derives full URLs at the edge (it already follows this convention — buyprompts.ChatCompletionsURL).
Runtime tripwire for what types can't reach (the live ConfigMap): the config-drift checker compares model names only, so a same-name entry with a divergent api_base — exactly the poisoned model group in fix(model): register discovered endpoints with /v1 api_base #745's incident — is invisible to it. Add an assertion at reconcile: every openai/ entry's api_base ends in /v1. This alone would have caught the incident in production.
Rejection over rewriting: constructors error on ambiguous input. Silent normalization is how the WarnAndStripV1Suffix disaster happened — a helpful auto-fix encoding a wrong theory.
Non-goals
No behavior change; pure enforcement of the invariant #745 documented. Not release-blocking for v0.13.0.
Problem
The
/v1-suffix bug family (#745, ffa7cf4, the deletedWarnAndStripV1Suffixfossil) is a recurring type error: "service root without/v1" and "OpenAI base with/v1" are two different types carried in onestring. The invariant is currently documentation-enforced (CLAUDE.md pitfall 6, a doc comment onbuildCustomEndpointEntryWithOptions) — and documentation-enforced invariants are exactly what decayed into this incident: the codebase carried two contradictory theories of who appends/v1for months, with no test or build failure.Full audit of every producer/consumer: #745 (comment)
Proposal (~40 lines, one package)
internal/model:buildCustomEndpointEntryWithOptionstakesOpenAIBase; the discovery bug class becomes a compile error. Buyer-side code keepsServiceRootand derives full URLs at the edge (it already follows this convention —buyprompts.ChatCompletionsURL).Runtime tripwire for what types can't reach (the live ConfigMap): the config-drift checker compares model names only, so a same-name entry with a divergent
api_base— exactly the poisoned model group in fix(model): register discovered endpoints with /v1 api_base #745's incident — is invisible to it. Add an assertion at reconcile: everyopenai/entry'sapi_baseends in/v1. This alone would have caught the incident in production.Rejection over rewriting: constructors error on ambiguous input. Silent normalization is how the
WarnAndStripV1Suffixdisaster happened — a helpful auto-fix encoding a wrong theory.Non-goals
No behavior change; pure enforcement of the invariant #745 documented. Not release-blocking for v0.13.0.