Skip to main content

Pydantic AI + Anthropic: "Container identifier can only be provided when using the code execution tool" (400)

A follow-up run used to reuse the code execution container from message history on requests without code execution. Upgrade to 2.65.0+, or on older releases set anthropic_container=False / strip the id.

Updated 8 min readMarkdown

Disclosure: I work on MemorySync (I'm Mohammed, the founder). I drafted this page with AI assistance and checked it against the source issue, the merged fix PR, the current pydantic-ai 2.66.0 source and the Pydantic AI Anthropic docs listed under Sources on Nov 6, 2026. The original two workarounds ran offline on 2.63.0 on Oct 29. I re-ran that request-body check offline on 2.66.0 on Nov 6 with a mocked HTTP client (no Anthropic API calls); the ordinary follow-up sent no container, and the other two paths sent none. The snippets below are adapted from that check; I didn't run them against the live API.

The question

In pydantic/pydantic-ai#11761 (opened Oct 28, 2026), a run that used Anthropic code execution works, but the next run on the same message history, without code execution, fails:

400: Container identifier can only be provided when using the code execution tool

The repro from the issue, trimmed:

from pydantic_ai import Agent
from pydantic_ai.capabilities import NativeTool
from pydantic_ai.native_tools import CodeExecutionTool

model = 'anthropic:claude-sonnet-5-5'
first = Agent(model, capabilities=[NativeTool(CodeExecutionTool())]).run_sync(
    'Use the code execution tool to compute 3 * 12390.'
)
# 400: Container identifier can only be provided when using the code execution tool
Agent(model).run_sync('Say the result in one sentence.', message_history=first.all_messages())

The report is on main just after v2.63.0. The fix shipped in 2.65.0; 2.66.0 is the latest release on PyPI as of Nov 6, 2026. This affects an app on 2.64.0 or earlier that saves all_messages() and replays it later: the stored history keeps the container, and the next request may not have code execution.

Status (checked Nov 6, 2026): resolved. #11761 closed on Nov 3 at 00:14 IST, 1 comment, when #11764 merged on Nov 3 at 00:14 IST. The fix ships in 2.65.0; the latest release is 2.66.0. In my current 2.66.0 offline request check, the default toolless follow-up sent no container.

Skip the memory plumbing around this fix. Once you've upgraded to 2.65.0 or later, the MemorySync Pydantic AI guide shows how to add per-user memory with MemorySync. MemorySync doesn't fix this error itself. When you're ready, see MemorySync. Founding offer: use code FOUNDING30 for 30% off Starter for your first 3 months (Settings → Billing).

Why it happens

When Anthropic returns a container, AnthropicModel saves its ID on the response as provider_details['container_id']. Before 2.65.0, _get_container() scans the history backwards on every later request and sends the most recent container_id it finds, whatever tools the new request has. From 2.65.0, the ordinary request without code execution returns before that history scan. The Pydantic AI Anthropic docs describe this: "When you continue a run from message history, Pydantic AI automatically reuses the Anthropic code execution container recorded in that history on requests that have code execution: CodeExecutionTool, or WebSearchTool or WebFetchTool where code execution may call them… The Anthropic API rejects a container on any other request, such as one with no tools, so Pydantic AI doesn't send it there."

Per the issue, the Anthropic API only accepts a container on a request with code execution: CodeExecutionTool, or the 20260209 web tools, which Anthropic calls from code execution by default. A plain follow-up gets the 400.

Fallback 1 (for 2.64.0 or earlier): set anthropic_container=False on the follow-up

AnthropicModelSettings.anthropic_container already has an off switch. Its docstring says: "Set to False to force a fresh container (ignore any container_id from history)." In 2.63.0, False makes _get_container() return no container, so the request goes out without one:

from pydantic_ai import Agent
from pydantic_ai.models.anthropic import AnthropicModelSettings

follow_up = Agent('anthropic:claude-sonnet-5-5')  # no code execution here
result = follow_up.run_sync(
    'Say the result in one sentence.',
    message_history=first.all_messages(),
    model_settings=AnthropicModelSettings(anthropic_container=False),
)

This only changes that request. The history still holds the old container_id, so a later run that does use code execution (without the setting) can pick it up again.

In my Oct 29 offline check on 2.63.0, the default follow-up sent container_abc from history, and the same call with anthropic_container=False sent no container field. On 2.66.0 the default toolless follow-up already sends no container.

Fallback 2 (for 2.64.0 or earlier): strip container_id from stored history before replaying it

If you load history from your own store and don't know in advance which agent will run next, remove the ID when you load it:

from dataclasses import replace
from pydantic_ai.messages import ModelResponse

def strip_container_ids(messages):
    out = []
    for m in messages:
        if isinstance(m, ModelResponse) and m.provider_details and 'container_id' in m.provider_details:
            details = {k: v for k, v in m.provider_details.items() if k != 'container_id'}
            m = replace(m, provider_details=details or None)
        out.append(m)
    return out

result = follow_up.run_sync('Say the result in one sentence.',
                            message_history=strip_container_ids(history))

replace() returns new objects, so the original list isn't changed (my check confirmed that). The cost: a later code execution run on this history won't reconnect to the old container, so files or state from it are gone. Only strip when the next request has no code execution, or when you don't need the container.

Upgrade or fallback

If the follow-up agent also has CodeExecutionTool, the container is accepted. Upgrade to 2.65.0 or later for the merged #11764 fix: _get_container() returns no container when the request has no code execution tool and its other tool setup provides none. There is no new AnthropicModelProfile switch; that proposed flag was removed before merge. If you pass extra_body['tools'], the released code does not inspect that list and still sends the history container. For that case, remove the ID from history or explicitly set anthropic_container=False as appropriate for your request.

Checklist

  1. Did an earlier run in this history use Anthropic code execution? Look for provider_details['container_id'] on a ModelResponse.
  2. Does the next request lack CodeExecutionTool and the 20260209 web tools? Then you're on the path that fails.
  3. On 2.64.0 or earlier, pass AnthropicModelSettings(anthropic_container=False) on that request, or strip container_id when you load history.
  4. On 2.65.0 or later, check the default request body once with a mock. The upstream fix removes the need for these history workarounds in the ordinary case.

Common questions

Does this happen with no tools at all? On 2.64.0 or earlier, yes. On 2.65.0 or later, the ordinary toolless follow-up no longer sends it, which my offline check confirms. The issue's follow-up agent has no tools, and per the PR the API rejects a container "on any other request, such as one with no tools".

Is this the same as the expired-container error? No. The docs say containers expire after 30 days and a request that names an expired one returns an error. This 400 is about sending a valid container on a request without code execution.

Does the PR change Bedrock or Vertex? Per the merged 2.65.0/2.66.0 source, client profiles that do not accept file and container IDs still return no container. The shared guard then checks whether the request provisions code execution. I didn't run Bedrock or Vertex for this page.

Skip the plumbing with MemorySync

Disclosure: I build MemorySync.

It doesn't fix this bug. Upgrade first. On 2.64.0 or earlier you still need Fallback 1 or Fallback 2. The MemorySync Pydantic AI guide documents MemorySyncCapability, which adds recall through Pydantic AI's instructions channel (ModelRequest.instructions) rather than as a conversation part. After each successful run it sends the user's prompt to fact extraction and stores the durable facts; model replies, tool calls and thinking parts aren't sent. So a user's facts can come back in later runs even if you trim or rebuild the stored history, but code execution output and containers are not part of memory. Read the guide first. For an overview of MemorySync, see the MemorySync homepage.

From the guide:

Unverified (not run):

pip install pydantic-ai-memorysync
from pydantic_ai import Agent
from pydantic_ai_memorysync import MemorySyncCapability

agent = Agent(
    "openai:your-model",
    instructions="You are a helpful assistant.",
    capabilities=[MemorySyncCapability(user_id="customer-7")],
)

result = await agent.run("What should I cook tonight?")
# The model saw:
#   You are a helpful assistant.
#
#   Relevant memories about this user from previous conversations:
#   - Is vegetarian
#   - Flies aisle
#
# ...and the durable facts in this prompt are remembered for
# every future run.

Snippet adapted from the MemorySync Pydantic AI guide; I haven't run it for this page.

Try it: MemorySync is optional here: the steps above work without it. If you want the memory layer from the snippet, see MemorySync, create an API key in the dashboard and drop the snippet into your agent.

Sources

  • github.com/pydantic/pydantic-ai/issues/11761 (opened 2026-10-28; closed Nov 3, 2026 00:14 IST, 1 comment; read 2026-11-06)
  • github.com/pydantic/pydantic-ai/pull/11764 (merged Nov 3, 2026 00:14 IST; description and diff read 2026-11-06); fix included in v2.65.0 release notes
  • pydantic-ai source, v2.66.0: pydantic_ai_slim/pydantic_ai/models/anthropic/init.py (_get_container now checks provisions_code_execution(tools) and returns no container in the ordinary no-code-execution case; read 2026-11-06)
  • pydantic.dev/docs/ai/models/anthropic/ ("Code Execution Containers"; ai.pydantic.dev/models/anthropic/ redirects here; read 2026-11-06)
  • Offline wire check against pydantic-ai-slim[anthropic] 2.63.0 with a mocked HTTP transport (2026-10-29); same check re-run on 2.66.0 (Nov 6, 2026): default toolless follow-up <absent>, anthropic_container=False <absent>, stripped history <absent>; original history retained container_abc
  • MemorySync docs: docs.memorysync.io/guides/pydantic-ai

Next step: read the MemorySync Pydantic AI guide, then see MemorySync. Founding offer: use code FOUNDING30 for 30% off Starter for your first 3 months (Settings → Billing).

About the author

  • Building MemorySync (memorysync.io) — persistent memory infrastructure for AI agents, Cursor, and Claude Code.

No comments yet. Be the first to comment.