langflow-ai/langflow · error · HTTPException

Checkpoint expired or not found; cannot resume this run.

Error message

Checkpoint expired or not found; cannot resume this run.

What it means

Raised as HTTP 404 when a HITL (human-in-the-loop) resume request references a run whose checkpoint no longer exists in the checkpoint store. The comment in the source explains why: re-dispatching with resume/job_id still set would recurse to RecursionError, so a missing or expired checkpoint is treated as unrecoverable and surfaced as a clean 404.

Source

Thrown at src/backend/base/langflow/api/build.py:481

    async def build_resumed_graph_and_get_order() -> tuple[list[str], list[str], Graph]:
        """Resume a suspended HITL run from its durable checkpoint instead of building fresh.

        Hydrates the graph, injects the human decision keyed by request_id, and un-builds
        the paused node so it re-runs and routes (its first-run output was a placeholder).
        """
        from lfx.graph.graph.base import Graph as LfxGraph
        from lfx.run.hitl import request_id_targets_vertex
        from lfx.services.deps import get_checkpoint_service

        from langflow.api.v2.hitl import reroute_decision_on_timeout

        run_id = str(job_id)
        store = get_checkpoint_service()
        checkpoint = await store.load_by_run_id(run_id)
        if checkpoint is None:
            # Why: re-dispatching here (resume/job_id still set) recurses to RecursionError; a missing
            # or expired checkpoint is unrecoverable, so surface a clean 404 instead.
            raise HTTPException(status_code=404, detail="Checkpoint expired or not found; cannot resume this run.")
        graph = LfxGraph.resume_from_checkpoint(checkpoint, checkpoint_store=store)
        if not graph.user_id:
            graph.user_id = str(current_user.id)
        # Resume skips the initial run's trace setup (trace_context_var stays unset → post-pause
        # vertices like Chat Output never trace); re-init so the resumed vertices trace.
        graph.flow_name = graph.flow_name or flow_name
        await graph.initialize_run()
        pending = await get_job_service().get_pending_human_request(job_id)
        decision = reroute_decision_on_timeout(pending, resume["decision"])
        # Merge with checkpoint-restored decisions so a re-run HITL keeps its answer (no multi-HITL loop).
        graph.human_input_decisions = {
            **(getattr(graph, "human_input_decisions", {}) or {}),
            resume["request_id"]: decision,
        }
        action_id = str((decision or {}).get("action_id", ""))
        gate_label = _hitl_gate_label(action_id, (pending or {}).get("options"))
        if graph.tracing_service:
            graph.tracing_service.record_event_span(

View on GitHub (pinned to 976ec789d2)

Solutions

  1. Check the run's checkpoint exists before offering a resume button (GET pending human request / checkpoint lookup)
  2. If checkpoints are in-memory, switch to a persistent checkpoint service (database-backed) so restarts do not lose state
  3. Resume promptly or raise checkpoint TTL so paused runs survive
  4. If already unrecoverable, start a new run instead of resuming; the 404 is intentional to avoid infinite re-dispatch recursion

Example fix

// before
await client.resumeRun(jobId, { decision: 'approve' }); // 404 after restart
// after
const pending = await client.getPendingRequest(jobId);
if (!pending) {
  // checkpoint gone: start a fresh run
  await client.runFlow(flowId);
} else {
  await client.resumeRun(jobId, { decision: 'approve' });
}
Defensive patterns

Strategy: validation

Validate before calling

const pending = await client.getPendingHumanRequest(jobId);
const cp = await checkpointService.loadByRunId(jobId);
if (!pending || !cp) { /* start a new run, do not resume */ }

Try / catch

try { await resumeRun(jobId, decision) } catch (e) { if (e.status === 404 && /Checkpoint expired/.test(e.detail)) { await startNewRun(); } else throw e; }

Prevention

When it happens

Trigger: Calling the resume endpoint (POST with a resume payload and job_id) for a run whose checkpoint was evicted, expired, or never persisted; restarting the server with an in-memory checkpoint store; resuming after the checkpoint TTL elapsed.

Common situations: Long-paused HITL runs, server restarts wiping in-memory checkpoints, multiple replicas with non-shared checkpoint storage, or retrying a resume after the underlying job was already completed or cleaned up.

Related errors


AI-assisted analysis of langflow-ai/langflow@976ec789d2 (2026-08-14). Data as JSON: /api/errors/800ed09df563db4d. Report an issue: GitHub.