How to Fix 'chain execution stuck in production' in LlamaIndex (TypeScript)

By Cyprian AaronsUpdated 2026-04-21
chain-execution-stuck-in-productionllamaindextypescript

When a LlamaIndex TypeScript chain gets “stuck” in production, it usually means the execution never reaches a terminal response or keeps waiting on a promise that never resolves. In practice, this shows up as a request that hangs indefinitely, a worker that never returns, or logs that stop after QueryEngine.query() / chatEngine.chat() starts.

The root cause is usually not LlamaIndex itself. It’s almost always an async bug, a bad callback handler, a tool call that never completes, or a timeout/retry issue around the chain.

The Most Common Cause

The #1 cause I see is forgetting to await an async LlamaIndex call or swallowing the returned promise in a handler. In TypeScript, this often happens when you wrap query(), chat(), or run() inside Express/NestJS/Fastify code and return before the chain finishes.

Here’s the broken pattern:

BrokenFixed
```ts
import express from "express";
import { OpenAI } from "@llamaindex/openai";
import { VectorStoreIndex } from "llamaindex";

const app = express();

app.get("/ask", (req, res) => { const query = String(req.query.q ?? "");

const llm = new OpenAI({ model: "gpt-4o-mini" });

VectorStoreIndex.fromDocuments([]).then((index) => { const engine = index.asQueryEngine({ llm });

// BUG: not awaited, response may never be sent correctly
engine.query({ query }).then((result) => {
  res.json({ answer: result.toString() });
});

});

// Request lifecycle continues here; easy to hang or double-handle }); |ts import express from "express"; import { OpenAI } from "@llamaindex/openai"; import { VectorStoreIndex } from "llamaindex";

const app = express();

app.get("/ask", async (req, res, next) => { try { const query = String(req.query.q ?? ""); const llm = new OpenAI({ model: "gpt-4o-mini" });

const index = await VectorStoreIndex.fromDocuments([]);
const engine = index.asQueryEngine({ llm });

const result = await engine.query({ query });

res.json({ answer: result.toString() });

} catch (err) { next(err); } });


What happens in production is simple:

- the request handler exits early
- no one awaits the chain result
- your app appears “stuck” because the response path is broken

If you’re using `Workflow`, `AgentRunner`, or any custom callback layer, the same rule applies: **every async boundary must be awaited and every branch must resolve**.

## Other Possible Causes

### 1) A tool call never returns

If your agent uses tools and one tool hangs on an external API call, the whole chain waits forever. This is common with HTTP clients missing timeouts.

```ts
const tools = [
  {
    name: "lookupPolicy",
    description: "Fetch policy details",
    fn: async ({ policyId }: { policyId: string }) => {
      const res = await fetch(`https://internal-api/policies/${policyId}`);
      return await res.text();
    },
  },
];

Fix it with explicit timeouts:

const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10_000);

const res = await fetch(`https://internal-api/policies/${policyId}`, {
  signal: controller.signal,
});

clearTimeout(timeout);

2) Callback handlers throw or never call completion hooks

A bad callback manager can block execution if it throws inside onLLMStart, onToolStart, or similar hooks.

const settings = {
  callbackManager: {
    onEvent: () => {
      throw new Error("metrics pipeline down");
    },
  },
};

Keep callbacks non-blocking and fail-open:

const settings = {
  callbackManager: {
    onEvent: async () => {
      try {
        await sendMetric();
      } catch (e) {
        console.warn("callback failed", e);
      }
    },
  },
};

3) Recursive agent loops with no exit condition

An agent can keep calling tools forever if your prompt allows repeated retries without termination criteria. You’ll often see repeated AgentRunner steps with no final answer.

// Bad prompt behavior:
// "Keep trying until you are certain"
// No max iterations configured

Set hard limits:

const agent = await createReActAgent({
  tools,
  maxIterations: 5,
});

4) Streaming consumer is not drained

If you use streaming APIs and never consume the stream fully, some runtimes keep waiting for completion. This shows up when using responseStream but forgetting to iterate it.

const stream = await engine.chat({ message: "Summarize claim" });

// BUG: stream is created but not consumed

Consume it properly:

const stream = await engine.chat({ message: "Summarize claim" });

for await (const chunk of stream.responseGen) {
  process.stdout.write(chunk);
}

How to Debug It

  1. Add timing logs around each async boundary

    • Log before and after fromDocuments(), query(), tool calls, and callback hooks.
    • If you only see the “before” log, that’s your hang point.
  2. Disable tools and callbacks

    • Run the same query with plain retrieval only.
    • If it stops hanging, your issue is in a tool runner or callback handler.
  3. Set explicit timeouts

    • Wrap external HTTP calls with AbortController.
    • If the stuck request now fails fast, you’ve found a downstream dependency problem.
  4. Inspect stack traces and runtime warnings

    • Look for messages like:
      • Error [AbortError]: The operation was aborted
      • UnhandledPromiseRejectionWarning
      • QueryEngine.query(...) returned unresolved promise
      • AgentRunner exceeded maxIterations
    • These are strong signals about whether you have a network hang, promise leak, or infinite loop.

Prevention

  • Always make request handlers async and always await LlamaIndex calls.
  • Put timeouts on every external dependency used by tools, retrievers, and post-processors.
  • Set hard limits on agents:
    • max iterations
    • max tool calls
    • max token budgets
  • Keep callbacks side-effect safe:
    • no throwing inside metrics/logging hooks
    • no blocking I/O in event handlers

If you’re seeing “chain execution stuck in production” in LlamaIndex TypeScript, start by checking whether the chain actually has an unresolved promise path. In most cases, fixing one missing await or one hung tool call clears the issue immediately.


Keep learning

By Cyprian Aarons, AI Consultant at Topiax.

Want the complete 8-step roadmap?

Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.

Get the Starter Kit

Related Guides