On a cold start, thv llm proxy opens the browser for the OIDC login but the
callback listener is gone by the time the user finishes authenticating, so the
login always fails.
What I saw
I set up the gateway with thv llm setup --lazy, started thv llm proxy, and
pointed a tool at it. The browser opened on the Okta login page as expected. By
the time I had finished entering credentials and MFA, the local callback
listener had already shut down — the redirect landed on nothing.
Why it happens
The proxy fetches a token per request, and on a cold start that fetch runs the
interactive browser flow:
handler() pkg/llm/proxy/proxy.go
tokenCtx := context.WithTimeout(r.Context(), tokenFetchTimeout) <-- here
tokenSource.Token(tokenCtx)
performBrowserFlow(ctx) pkg/auth/tokensource/tokensource.go:389
oauth.Flow.Start(ctx) pkg/auth/oauth/flow.go:191
go ListenAndServe(":8666") flow.go:208
browser.OpenURL(authURL) flow.go:234
select {
case <-tokenChan flow.go:250
case <-ctx.Done() flow.go:255 <-- fires
}
defer server.Shutdown() flow.go:216-226
The token-fetch context was derived from the inbound HTTP request's context, so
the login's lifetime was bounded by the calling client's patience. A human takes
30-90 seconds to get through an IdP login; no HTTP client waits that long. When
the client gives up and closes the connection, net/http cancels
r.Context(), flow.go:255 returns, and the deferred Shutdown tears down the
listener while the user is still on the login page.
There are two ways this surfaces:
- Single request. The listener is gone when the redirect arrives —
connection refused.
- A retrying client (an agent loop, or a tool that reconnects). The first
flow dies on disconnect; the retry misses the cache and starts a fresh
flow on the same pinned callback port with a new state. The user's
browser tab still carries the old state, so the callback hits the new
listener and flow.go:322-327 rejects it as invalid state parameter — a
fatal error that shuts the listener down again.
Either way the user cannot complete a login, and --lazy setup is effectively
unusable through the proxy: the first request is exactly the one that has to
drive a login, and it is also the one guaranteed to outlive its client.
Expected
A client disconnecting should not abort an interactive login that is already in
progress. The login should stay alive long enough for a person to complete it,
and cancelling the proxy itself (Ctrl+C) should still cancel it.
Not covered by this issue
thv llm token invoked as an apiKeyHelper has the same symptom from a
different cause — the client kills the child process, so the listener dies with
it. That needs a separate fix and is tracked separately.
On a cold start,
thv llm proxyopens the browser for the OIDC login but thecallback listener is gone by the time the user finishes authenticating, so the
login always fails.
What I saw
I set up the gateway with
thv llm setup --lazy, startedthv llm proxy, andpointed a tool at it. The browser opened on the Okta login page as expected. By
the time I had finished entering credentials and MFA, the local callback
listener had already shut down — the redirect landed on nothing.
Why it happens
The proxy fetches a token per request, and on a cold start that fetch runs the
interactive browser flow:
The token-fetch context was derived from the inbound HTTP request's context, so
the login's lifetime was bounded by the calling client's patience. A human takes
30-90 seconds to get through an IdP login; no HTTP client waits that long. When
the client gives up and closes the connection,
net/httpcancelsr.Context(),flow.go:255returns, and the deferredShutdowntears down thelistener while the user is still on the login page.
There are two ways this surfaces:
connection refused.
flow dies on disconnect; the retry misses the cache and starts a fresh
flow on the same pinned callback port with a new
state. The user'sbrowser tab still carries the old state, so the callback hits the new
listener and
flow.go:322-327rejects it asinvalid state parameter— afatal error that shuts the listener down again.
Either way the user cannot complete a login, and
--lazysetup is effectivelyunusable through the proxy: the first request is exactly the one that has to
drive a login, and it is also the one guaranteed to outlive its client.
Expected
A client disconnecting should not abort an interactive login that is already in
progress. The login should stay alive long enough for a person to complete it,
and cancelling the proxy itself (Ctrl+C) should still cancel it.
Not covered by this issue
thv llm tokeninvoked as anapiKeyHelperhas the same symptom from adifferent cause — the client kills the child process, so the listener dies with
it. That needs a separate fix and is tracked separately.