3.3 Message History Management
Claude Code doesn't simply send all historical messages to the API. It manages the message list through a series of mechanisms, ensuring that messages sent to the API are properly formatted and content is lean.
Compact Boundary
After autocompact occurs, a compact_boundary marker is inserted into the message list. Subsequent API calls only send messages after the boundary:
let messagesForQuery = [...getMessagesAfterCompactBoundary(messages)]When the HISTORY_SNIP Feature is enabled, it further projects a "trimmed view" on top of this — hiding messages marked as snipped from the API request.
Message Normalization (normalizeMessagesForAPI)
normalizeMessagesForAPI() (src/utils/messages.ts, approximately 380 lines) is a critical processing step before messages are sent. It solves a core problem: the internal message format of Claude Code and the message format required by the API are not fully consistent.
// src/utils/messages.ts
export function normalizeMessagesForAPI(
messages: Message[],
tools: Tools = [],
): (UserMessage | AssistantMessage)[] {
const availableToolNames = new Set(tools.map(t => t.name))
// 1. Attachment reordering + filter virtual messages
const reorderedMessages = reorderAttachmentsForAPI(messages)
.filter(m => !((m.type === 'user' || m.type === 'assistant') && m.isVirtual))
// 2. Build error→block type mapping (PDF too large, image too large, etc.)
const errorToBlockTypes: Record<string, Set<string>> = { ... }
const stripTargets = new Map<string, Set<string>>() // userUUID → block types to strip
// 3-7. Iterate through messages, processing each one:
// - Strip tool_reference, advisor blocks, error media items
// - Handle thinking/signature blocks
// - Merge split AssistantMessages with same ID
// - Validate and repair tool_use/tool_result pairing
...
}Below is each processing step and the problem it solves:
1. Attachment reordering (reorderAttachmentsForAPI): Attachment messages may appear at arbitrary positions internally, but the API requires them to be before the semantically associated message. This step bubbles attachment messages upward until they encounter a tool_result or assistant message. Without this step, the API might see an isolated image block without knowing which message it's associated with.
2. Filtering virtual messages: Messages marked as isVirtual (such as temporary messages from REPL internal tool calls) are removed. These messages exist solely for UI display — for example, automatically triggered internal operations need to show progress in the interface, but they should not enter the API request.
3. Building error→block type mapping: Certain API errors (such as "PDF too large", "image too large") require stripping the corresponding media blocks from subsequent messages. The system builds a mapping table errorToBlockTypes, mapping error text to block types that need to be stripped (document, image). Without this step, the same oversized PDF would be sent with every request, triggering the same error each time.
4. Stripping internal elements: Removes tool_reference (tool reference markers), advisor blocks, and media items that need to be stripped due to API errors from messages. tool_reference is an internal tracking marker for the lazy tool loading system (Tool Search), and the API has no concept of it — their presence would cause the API to return format errors.
5. thinking/signature block handling: Handles thinking blocks according to model requirements. Some models don't support thinking or redacted_thinking blocks — sending them would directly cause the API to return a 400 error. Signature blocks are used to verify the integrity of thinking blocks and also need to be stripped on unsupported models.
6. Merging split messages: The streaming parser may split a single API response into multiple AssistantMessages with the same message.id (when parallel tool calls produce multiple content blocks). The API expects one response to correspond to one message, and multiple messages with the same ID would violate the message alternation rule.
7. Validating and repairing pairs: The API requires every tool_use block to have a corresponding tool_result, and vice versa. Session crashes, compression, and mid-session interruptions can all break this pairing relationship. This step detects and repairs orphan blocks — generating error-type tool_results for tool_uses missing results, and injecting synthetic tool_uses for tool_results missing requests. Without this step, recovering a crashed session would almost certainly error out due to incomplete pairing.
Why so complex? Because the Claude API has strict requirements for message format: user/assistant messages must alternate, tool_use/tool_result must be paired, and thinking blocks cannot appear in unsupported positions. Yet Claude Code's internal message list may violate these constraints due to session crash recovery, compression operations, user interruptions, and other reasons. normalizeMessagesForAPI is the defense layer — ensuring that no matter how chaotic the internal state, the API always receives a valid message sequence.