databendlabs/databend · error
login_handler expect session id in ctx
Error message
login_handler expect session id in ctx
What it means
The HTTP login handler requires the client to present a session id (`client_session_id`) in the request context. `expect("login_handler expect session id in ctx")` panics when the extractor that populates `ctx.client_session_id` did not run or the request omitted the session-id cookie/header, instead of returning a clean 400.
Solutions
- Send the required session id (cookie or header) with the login request.
- Verify middleware/extractors that set client_session_id are attached to the login route.
- Replace the expect with a proper poem error: return 400 bad_request when client_session_id is missing.
- Check reverse-proxy config isn't dropping cookies/custom headers.
Example fix
// before
let session_id = ctx.client_session_id.as_ref().expect("login_handler expect session id in ctx").clone();
// after
let session_id = ctx.client_session_id.clone().ok_or_else(||
poem::Error::from_string("session id is required", poem::http::StatusCode::BAD_REQUEST))?; Defensive patterns
Strategy: validation
Validate before calling
// client: ensure session id is attached
if (!headers.has('x-databend-session-id') && !cookies.has('session_id')) {
throw new Error('login requires a session id cookie/header');
} Type guard
fn has_session_id(ctx: &HttpContext) -> bool { ctx.client_session_id.is_some() } Try / catch
// server-side fix
match ctx.client_session_id.as_ref() {
Some(id) => id.clone(),
None => return Err(poem::Error::from_string("missing session id", poem::http::StatusCode::BAD_REQUEST)),
} Prevention
- Always send the session-id cookie/header on /v1/session/login
- Verify reverse proxies don't strip cookies or custom headers
- Keep session middleware attached to login routes
- Prefer returning 4xx over expect in HTTP handlers
When it happens
Trigger: POST /v1/session/login without the session-id cookie or header that the session middleware/extractor normally injects; calling the handler outside the configured router (e.g. tests or proxied requests stripping headers).
Common situations: See trigger scenarios.
Related errors
- mysql handler should be authed when call
- Temp table id used up
- heartbeat request should contain auth header
- {}
- Invalid temp table desc
AI-assisted analysis of databendlabs/databend@288d84d76e (2026-09-11).
Data as JSON: /api/errors/5dc93c3c0e8e139b.
Report an issue: GitHub.
Appendix: source
Thrown at src/query/service/src/servers/http/v1/session/login_handler.rs:103
#[derive(Deserialize)]
struct LoginQuery {
disable_session_token: Option<bool>,
}
/// # For client/driver developer:
/// - It is encouraged to call `/v1/session/login` when establishing connection, not mandatory for now.
/// - May get 404 when talk to old server, may check `/health` (no `/v1` prefix) to ensure the host:port is not wrong.
#[poem::handler]
#[async_backtrace::framed]
pub async fn login_handler(
ctx: &HttpQueryContext,
Json(req): Json<LoginRequest>,
Query(query): Query<LoginQuery>,
) -> PoemResult<impl IntoResponse> {
let session_id = ctx
.client_session_id
.as_ref()
.expect("login_handler expect session id in ctx")
.clone();
check_login(ctx, &req)
.await
.map_err(HttpErrorCode::bad_request)?;
let version = &ctx.version.semantic;
let server_arrow_features = (SERVER_MAX_ARROW_RESULT_VERSION
>= ARROW_FEATURE_NEGOTIATION_VERSION)
.then_some(ArrowFeatures::decimal64_enabled());
let id_only = || {
Ok(Json(LoginResponse {
version: version.to_string(),
session_id: session_id.clone(),
server_max_arrow_result_version: SERVER_MAX_ARROW_RESULT_VERSION,
server_arrow_features: server_arrow_features.clone(),
tokens: None,
}))
};
View on GitHub (pinned to 288d84d76e)