Bug: cache_control TTL ordering error when hooks/MCP inject additionalContext into long conversations

Resolved 💬 3 comments Opened Mar 25, 2026 by stevehockey Closed Apr 28, 2026

Bug Description

Claude Code intermittently returns a 400 API error when hooks or MCP servers inject additionalContext into conversations, particularly in longer sessions (100+ messages):

API Error: 400 {"type":"error","error":{"type":"invalid_request_error",
"message":"messages.126.content.2.cache_control.ttl: a ttl='1h' cache_control block 
must not come after a ttl='5m' cache_control block. Note that blocks are processed 
in the following order: `tools`, `system`, `messages`."}}

Steps to Reproduce

  1. Configure a project with hooks that inject additionalContext (e.g., a PreToolUse hook that returns {"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":"some context"}})
  2. Also configure an MCP server for the same project
  3. Have a long conversation (100+ tool calls) with the agent
  4. The error occurs intermittently — more likely in longer conversations

Expected Behavior

Hooks and MCP servers should be able to inject additionalContext without triggering cache_control TTL ordering violations. The hooks themselves do not set any cache_control metadata — they return plain strings.

Actual Behavior

Claude Code's internal message construction appears to mix ttl='1h' and ttl='5m' cache_control blocks when building the API request. When hook-injected additionalContext shifts content block positions, a 1-hour TTL block can end up after a 5-minute TTL block, violating the API's ordering requirement.

Environment

  • Claude Code version: 2.1.81
  • OS: macOS Sequoia (Darwin 25.3.0, arm64)
  • Hooks: PreToolUse (additionalContext injection), SessionStart, Stop, SessionEnd
  • MCP: Streamable HTTP transport, single server
  • The error appears across multiple projects using the same hook pattern

Analysis

The prompt caching documentation states: "Cache entries with longer TTL must appear before shorter TTLs."

The root cause appears to be in Claude Code's internal request construction, not in user hooks or MCP servers. The hooks only return plain additionalContext strings — they never set cache_control, ttl, or any caching metadata. The TTL blocks are added by Claude Code itself.

Related Issues

  • #25597 — Invalid Ephemeral Cache Control Inputs (similar domain, different error)
  • #30989 — defer_loading + cache_control conflict
  • anthropics/claude-agent-sdk-typescript#188 — Agent SDK defaults to 1-hour cache TTL instead of 5-minute

Workaround

None currently. The error is recoverable by retrying the conversation, but it disrupts active sessions. Reducing hook frequency (e.g., throttling PreToolUse to every 10+ seconds) may reduce occurrence but does not eliminate it.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗