facebook/flow · critical

handler existed during typed validation

Error message

handler existed during typed validation

What it means

An internal invariant of the LSP request/response machinery: when a response to a locally-issued request arrives, its id must still be present in i_outstanding_local_handlers so the typed (non-mistyped) response can be dispatched. The expect fires when the map no longer holds the id — the handler was already removed (duplicate response, earlier cancellation cleanup) or never existed (foreign/echoed response id).

Source

Thrown at rust_port/crates/flow_lsp_server/src/flow_lsp.rs:2408

        ) | (
            lsp::LspResult::ConfigurationResult(_),
            LspResultHandler::ConfigurationHandler(_),
        ) | (
            lsp::LspResult::RegisterCapabilityResult,
            LspResultHandler::VoidHandler,
        ) | (lsp::LspResult::ErrorResult(_, _), _)
    );
    if is_mistyped {
        // | _ ->
        return Err(internal_error_exception(format!(
            "Response {} has mistyped handler",
            lsp_fmt::result_name_to_string(&result)
        )));
    }
    let handler = ienv
        .i_outstanding_local_handlers
        .remove(id)
        .expect("handler existed during typed validation");
    ienv.i_outstanding_local_requests.remove(id);
    let LspHandler {
        on_response,
        on_error,
    } = handler;
    let handler: Box<dyn FnOnce(&mut ServerState) -> Result<(), FlowLspError>> =
        match (result, on_response) {
            (
                lsp::LspResult::ShowMessageRequestResult(result),
                LspResultHandler::ShowMessageHandler(handle),
            ) => Box::new(move |state| handle(result, state)),
            (
                lsp::LspResult::ShowStatusResult(result),
                LspResultHandler::ShowStatusHandler(handle),
            ) => Box::new(move |state| handle(result, state)),
            (
                lsp::LspResult::ApplyWorkspaceEditResult(result),
                LspResultHandler::ApplyWorkspaceEditHandler(handle),

View on GitHub (pinned to 5c86586199)

Solutions

  1. Make removal tolerant: replace expect with if-let-Some and treat a missing handler as a late/duplicate response (log and return)
  2. Reproduce with LSP message tracing enabled and check whether the offending response id was sent twice
  3. Audit cancellation/removal ordering so a handler is removed exactly once per request lifecycle

Example fix

// before
let handler = ienv.i_outstanding_local_handlers.remove(id)
    .expect("handler existed during typed validation");

// after — tolerate a duplicate or late response
let Some(handler) = ienv.i_outstanding_local_handlers.remove(id) else {
    log::warn!("duplicate/cancelled response for local request id {id:?}");
    ienv.i_outstanding_local_requests.remove(id);
    return Ok(());
};
Defensive patterns

Strategy: validation

Validate before calling

// Validate the response id against outstanding requests before removal
if !ienv.i_outstanding_local_handlers.contains_key(id) {
    log::warn!("late/duplicate response for local request {id:?}");
    return Ok(());
}
let handler = ienv.i_outstanding_local_handlers.remove(id).expect("checked above");

Prevention

When it happens

Trigger: An LSP client sends the same response twice; a response arrives after the cancellation path already removed the handler; two server components reuse overlapping request ids; an editor bug echoes server-issued request ids back as responses.

Common situations: Flaky editor plugins answering showMessageRequest twice; races between cancellation (client) and slow responses (server); regressions in the port's id bookkeeping after refactoring the handler map.

Related errors


AI-assisted analysis of facebook/flow@5c86586199 (2026-08-20). Data as JSON: /api/errors/fe6f3bde30922b76. Report an issue: GitHub.