> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tracepilotai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# How to Debug OpenAI Agent Infinite Loops

> Stop runaway OpenAI agent loops before they drain your API budget. Use tracepilot-openai to detect recursive tool calls, retry loops, and broken memory chains — then replay them.

OpenAI agent infinite loops are silent budget killers. Your agent retries, re-calls tools, or re-enters its own memory — and nothing in your logs tells you why. `npm install tracepilot-openai` gives you structured span traces that expose every loop iteration, every tool re-invocation, and every token spent — so you can replay the exact execution that went wrong.

## Why OpenAI agent infinite loops happen

Infinite loops in LLM agents aren't random. They fall into three predictable patterns:

### Recursive tool calls

The model calls a tool, receives output it doesn't know how to use, and calls the same tool again with a slightly different input. Without a hard exit condition, this repeats until you hit a rate limit or your budget runs out.

```typescript theme={null}
// Danger: no max_iterations guard
while (true) {
  const response = await openai.chat.completions.create({
    model: 'gpt-4o',
    messages,
    tools,
  });

  const toolCall = response.choices[0].message.tool_calls?.[0];
  if (!toolCall) break; // model never stops calling tools → loop

  const toolResult = await executeTool(toolCall);
  messages.push(toolResultMessage(toolCall.id, toolResult));
}
```

### Retry loops

Error-handling logic retries failed completions unconditionally. If the underlying cause (bad context, token overflow, invalid tool schema) isn't fixed, every retry triggers the same failure — and the same retry.

```typescript theme={null}
// Danger: retry without error classification
async function callWithRetry(messages) {
  try {
    return await openai.chat.completions.create({ model: 'gpt-4o', messages });
  } catch (e) {
    return callWithRetry(messages); // unconditional recursion
  }
}
```

### Memory corruption loops

The agent appends its own output back to the messages array on every iteration. After several turns, the context window fills with redundant or contradictory content. The model starts producing low-quality outputs, which trigger more retries.

```typescript theme={null}
// Danger: growing context with no pruning
const memory: Message[] = [];

for (const step of steps) {
  const res = await openai.chat.completions.create({ model: 'gpt-4o', messages: memory });
  memory.push(res.choices[0].message); // unbounded growth
  memory.push({ role: 'user', content: nextStep(res) });
}
```

## Installation

```bash npm theme={null}
npm install tracepilot-openai
```

```bash yarn theme={null}
yarn add tracepilot-openai
```

```bash pnpm theme={null}
pnpm add tracepilot-openai
```

## Instrument your OpenAI agent

Wrap every completion call with TracePilot. Each iteration of your agent loop becomes a numbered span — so you can see exactly when and why the loop started repeating.

```typescript theme={null}
import OpenAI from 'openai';
import { TracePilotOpenAI } from 'tracepilot-openai';

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const tp = new TracePilotOpenAI(process.env.TRACEPILOT_API_KEY!);

async function runAgent(userInput: string) {
  await tp.startTrace('tool-calling-agent');

  const messages: any[] = [
    { role: 'user', content: userInput }
  ];

  let stepOrder = 1;
  let parentSpanId: string | undefined;
  const MAX_ITERATIONS = 10; // always set a hard limit

  for (let i = 0; i < MAX_ITERATIONS; i++) {
    const { result, spanId } = await tp.wrapOpenAI(
      () => openai.chat.completions.create({ model: 'gpt-4o', messages, tools }),
      messages,
      parentSpanId,
      stepOrder++
    );

    parentSpanId = spanId;
    const message = result.choices[0].message;
    messages.push(message);

    if (!message.tool_calls?.length) break; // clean exit

    for (const toolCall of message.tool_calls) {
      const { result: toolResult, spanId: toolSpanId } = await tp.wrapToolCall(
        toolCall.function.name,
        () => executeTool(toolCall),
        spanId,
        stepOrder++
      );

      messages.push({
        role: 'tool',
        tool_call_id: toolCall.id,
        content: JSON.stringify(toolResult),
      });

      parentSpanId = toolSpanId;
    }
  }
}
```

## Loop detection with span tracing

With TracePilot active, open your dashboard after a suspicious run. Look for:

| Signal                                   | What it means              |
| ---------------------------------------- | -------------------------- |
| Same `toolName` repeated 3+ times        | Recursive tool call loop   |
| Rising token count per span, same prompt | Memory bloat loop          |
| Identical `stepOrder` groups repeating   | Retry loop                 |
| Span count > `MAX_ITERATIONS`            | Guard not firing correctly |

```typescript theme={null}
// Tracing example — detect repeated tool calls
import { TracePilotOpenAI } from 'tracepilot-openai';

const tp = new TracePilotOpenAI(process.env.TRACEPILOT_API_KEY!);

// After your run, the dashboard shows every tool invocation in order.
// Filter by toolName to see repetitions instantly.
// Example dashboard view:
//
// Span 1 — gpt-4o           [320 tokens]
//   Span 2 — search-web     [tool call]
//   Span 3 — gpt-4o         [390 tokens]
//     Span 4 — search-web   [tool call ← repeated!]
//     Span 5 — gpt-4o       [450 tokens]
//       Span 6 — search-web [tool call ← loop confirmed]
```

## How to replay an infinite loop execution

Once you've captured the looping trace, you don't need to reproduce it locally. Fork the span where the loop started and rerun with a modified prompt or context.

<Steps>
  <Step title="Find the looping trace">
    Open [tracepilotai.com/dashboard](https://tracepilotai.com/dashboard). Filter by high span count or high token usage — loops produce both. Select the trace.
  </Step>

  <Step title="Identify the entry point">
    Expand the span tree. Find the first span where the repeated tool call appears. That's your entry point for the fix.
  </Step>

  <Step title="Fork the span">
    Click **Fork & Rerun** on the entry-point span. You'll see the exact messages array and tool definitions the model received.
  </Step>

  <Step title="Apply the fix">
    Edit the prompt to add an explicit stopping instruction, remove the ambiguous tool, or reduce the context size. Click **Run**.
  </Step>

  <Step title="Confirm the loop is gone">
    The replayed trace will show a clean exit with fewer spans. Ship the fix when confirmed.
  </Step>
</Steps>

<Tip>
  Screenshot pending: TracePilot dashboard showing a looping agent trace with repeated tool call spans highlighted.
</Tip>

## Prevent burning your OpenAI API budget

Every iteration of an infinite loop costs tokens. A 10-step loop on `gpt-4o` with 1k input tokens per step costs roughly $0.25–$**0.50 per runaway execution**. At scale, that's a real incident.

TracePilot gives you three layers of protection:

1. **Span count alerts** — set a threshold in the dashboard. Get notified when any trace exceeds N spans.
2. **Cost-per-trace visibility** — see the total token spend for every run before it compounds.
3. **Replay without re-execution** — fix the bug in the dashboard instead of running the agent again.

```typescript theme={null}
// Always instrument cost-sensitive loops
const { result, spanId } = await tp.wrapOpenAI(
  () => openai.chat.completions.create({ model: 'gpt-4o', messages }),
  messages,
  parentSpanId,
  stepOrder
);

// result.usage is captured automatically in the span
// Dashboard shows: prompt_tokens, completion_tokens, estimated_cost
```

<Warning>
  Never run an uninstrumented agent loop in production. Without span tracing, the first sign of a loop is your OpenAI invoice.
</Warning>

## Common loop fixes

<AccordionGroup>
  <Accordion title="Recursive tool calls">
    Add a `MAX_ITERATIONS` guard and pass `{ parallel_tool_calls: false }` to force sequential tool execution. In the Fork & Rerun view, add an explicit instruction like `"If you have already called search-web, do not call it again."` to the system prompt.
  </Accordion>

  <Accordion title="Unconditional retry loops">
    Classify errors before retrying. Only retry on transient network errors (`529`, `503`). For context-window overflows (`400`) or invalid tool schemas, fix the root cause — retrying will always fail.
  </Accordion>

  <Accordion title="Memory corruption / growing context">
    Implement a sliding window: keep only the last N messages plus the system prompt. Use `tp.wrapToolCall` to trace your memory pruning step so you can inspect what was dropped.
  </Accordion>

  <Accordion title="Tool output causing re-invocation">
    The model re-calls a tool when the output is ambiguous or empty. Use TracePilot to inspect the exact tool output in the span. Add output validation before appending to `messages`.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Quickstart: tracepilot-openai" icon="bolt" href="/quickstart-openai">
    Get OpenAI agents tracing in under 5 minutes.
  </Card>

  <Card title="Time-travel debugging" icon="clock-rotate-left" href="/concepts/time-travel-debugging">
    Fork any failing span and replay it with edited inputs.
  </Card>

  <Card title="Tracing tool calls" icon="wrench" href="/guides/tracing-tool-calls">
    Instrument every tool your agent invokes.
  </Card>

  <Card title="Cost tracking" icon="circle-dollar-to-slot" href="/concepts/cost-tracking">
    Monitor token spend per span to catch runaway costs early.
  </Card>
</CardGroup>
