Home / Articles

Routing Claude Code Traffic by Header Instead of Reading the Prompt

Learn how Claude Code's opt-in gateway hint headers let an LLM gateway prioritize, budget and manage caches from request metadata without parsing prompt bodies.

Publisher: REACTAPP.TOP9 min read1990 words

This article is published in English.

Sources: Your LLM Gateway Reads Your Prompt to Decide Where to Send It. Claude Code Just Shipped Five Headers That Make That Unnecessary.

When a company sends Claude Code traffic through an LLM gateway, that gateway has to decide something about every request before forwarding it: the upstream to use, the queue to join, the rate-limit bucket to charge and the priority to assign. Until recently the only place to find the facts behind those decisions was the request body, which means your prompts, file contents, tool results and CLAUDE.md. A set of opt-in request headers added in a recent Claude Code release moves those facts onto the envelope. Below you will see what each header makes decidable, how a proxy changes when it can use them, and where you still must not trust them.

Why gateways end up reading your prompts

Body inspection is not a theoretical concern; it is how many routers work today. Semantic routers such as the vLLM Semantic Router extract the user prompt, embed it with a BERT-style model and compare the vector with reference task vectors to classify the request. Routers optimized for passthrough latency can sniff the model name from roughly the first kilobyte and stream the rest, but any router that needs to classify work has to buffer and inspect the entire body.

That inspection is the product feature, and it also makes the gateway a far more sensitive trust boundary than its modest name suggests. It sees, and potentially stores, everything a developer's agent sends.

Claude Code 2.1.273, dated September 15, 2026 in the changelog, added five headers aimed at gateway operators. Taken together they are less an observability feature than the minimum metadata a gateway needs to act as a scheduler without acting as a reader.

The limits of the original identifier headers

Prior to 2.1.273, the compatibility guide for gateways documented three Claude Code headers, all of them identifiers:

+--------------------------------+---------------------------------+
| HEADER                         | WHAT IT IS                      |
+--------------------------------+---------------------------------+
| x-claude-code-session-id       | which session                   |
| x-claude-code-agent-id         | which subagent (if any)         |
| x-claude-code-parent-agent-id  | which agent spawned it          |
+--------------------------------+---------------------------------+
                  |
                  v
        good for GROUPING after the fact
        useless for DECIDING before forwarding

The documentation positions them for aggregation: the session ID lets you group everything a session sent without parsing bodies, and the agent ID, combined with the session ID, lets you attribute cost to agents running in parallel. That is accounting. It is enough for a cost dashboard, but not for scheduling. Knowing that a request comes from session abc and agent def says nothing about how expensive it will be, how long it will take or whether it can afford to wait. Any gateway that wanted to be smarter than a counter therefore had to open the body.

The five new hint headers

According to the changelog, version 2.1.273 introduced five headers, all switched on together with CLAUDE_CODE_GATEWAY_HINT_HEADERS=1. Two describe the caller (x-claude-code-agent-type and x-claude-code-request-class), one describes recent timing (x-claude-code-prev-tool-durations), and a pair describes context rewrites (x-claude-code-context-compacted together with x-claude-code-compaction).

An important caveat: at the time of writing, the request header table in the gateway compatibility guide still shows just the three identifier headers, and the variable is missing from the environment variable reference. The header names come from the changelog, but their value formats are not documented yet. Everything below describes what each header lets a gateway decide, not a guaranteed wire format. Inspect your own traffic before depending on specific values, and check the current docs, which may have caught up.

Grouped by the decision each one enables:

+---------------------------+------------------------------------------+
| HEADER                    | DECISION IT UNBLOCKS AT THE EDGE         |
+---------------------------+------------------------------------------+
| request-class             | is this a real turn, or a small          |
|                           | internal call? -> priority, cheap pool   |
+---------------------------+------------------------------------------+
| agent-type                | which kind of subagent is asking         |
|                           | -> per-agent-type budgets and quotas     |
+---------------------------+------------------------------------------+
| prev-tool-durations       | how long the last tools took             |
|                           | -> timeout and retry window sizing       |
+---------------------------+------------------------------------------+
| compaction                | this turn IS the summarization call      |
|                           | -> never count it as user latency        |
+---------------------------+------------------------------------------+
| context-compacted         | the prefix just changed under you        |
|                           | -> do not expect a cache hit this turn   |
+---------------------------+------------------------------------------+

In short:

  • request-class separates real conversational turns from small internal calls, so you can prioritize the former and send the latter to a cheaper pool.
  • agent-type identifies which kind of subagent is calling, enabling budgets and quotas per agent type.
  • prev-tool-durations reports how long recent tool runs took, which helps size timeouts and retry windows.
  • compaction flags the request that performs the summarization itself, so it should never count toward user-facing latency.
  • context-compacted signals that the conversation prefix was just rewritten, so a cache hit is not expected.

None of this is new information. A gateway could already infer the request class by telling the complete Claude Code system prompt apart from a slimmed-down classifier prompt, infer the agent type from the agent section of the system prompt, and spot compaction from how the message array is structured. The headers reveal nothing additional about you. What changes is the price of the knowledge: instead of parsing up to a megabyte of source code, the gateway reads a short string from the headers.

The same routing rule, before and after

Take a concrete goal: route background summarization to a less expensive pool. Without hints, the proxy must buffer and parse the conversation, then guess from its content:

// BEFORE: the router has to read the conversation to classify it
const body = await readEntireBody(req);        // buffers the whole prompt
const parsed = JSON.parse(body);               // your files are now in gateway memory
const sys = flattenSystemBlocks(parsed.system);
const isBackground =
  sys.includes("summarize the conversation") ||
  parsed.messages.length > COMPACT_THRESHOLD;

and only then forward the buffered body:

route(isBackground ? "cheap-pool" : "interactive-pool", body);

This approach has three problems, from least to most serious. Buffering the whole body destroys streaming latency. Matching on prompt wording is brittle because that wording changes between Claude Code releases. Worst, the full text of your repository now lives in the routing layer's process memory, its heap dumps and quite possibly its logs, a place that appears on no architecture diagram and that no one thought to protect.

With the hint headers, the router reads two values from the envelope:

// AFTER: the router decides on the envelope and never touches the body
const cls  = req.headers["x-claude-code-request-class"];
const comp = req.headers["x-claude-code-compaction"];

and routes the untouched request stream. As the comment in the snippet notes, the literal value strings are illustrative, since the format is not yet documented:

// value strings below are illustrative: confirm them against your own
// traffic, since the wire format is not documented yet
const isBackground = Boolean(comp) || cls === "background";route(isBackground ? "cheap-pool" : "interactive-pool", req);  // streams through

There is no buffering, no JSON parsing and no prompt held in memory. The compatibility guide already allows this pattern: other than anthropic-workspace-id, anthropic-beta and anthropic-version, headers are described as inputs the gateway can use for tracing, attribution or routing and is not required to pass on. These hints are designed to stop at the gateway; since the model never sees them, they can stay crude and inexpensive.

The compaction pair is a caching signal

Two of the headers look redundant but answer different questions. The first, x-claude-code-compaction, is set on the very call that produces the summary. The second, x-claude-code-context-compacted, appears on later calls, after a compaction has already happened.

The distinction matters once you operate a cache. Cache keys for prompts are prefixes assembled from tools, then the system prompt, then messages, so rewriting anything near the front invalidates everything after it. Cache writes are also priced higher than cache reads, so unnecessary writes are pure cost. Compaction is the biggest invalidator in an ordinary session: it swaps the accumulated history for a summary, producing a brand-new prefix by definition.

turn N        turn N+1 (compaction)      turn N+2
+---------+     +------------------+     +------------------+
| prefix  |     | compaction: 1    |     | context-         |
| stable  | --> | rewrites history | --> | compacted: 1     |
| cache   |     | EXPECT A MISS    |     | new prefix is    |
| hits    |     | bill it as       |     | now the stable   |
+---------+     | background       |     | one, re-warm it  |
                +------------------+     +------------------+

A gateway that maintains its own cache but knows nothing about compaction behaves as badly as possible on the compaction turn: it runs a complete lookup, misses, stores an entry for a prefix that will be obsolete one turn later, and then reports a falling hit rate that gets investigated as a defect. With the headers, it can skip the lookup on the compaction request, bill that call as background work, and deliberately warm the new prefix on the following turn, when it becomes the stable one.

Hints are client assertions, not credentials

Both the changelog and the variable name call these hint headers, and the word matters. A hint is an unverified claim made by the client. The value of x-claude-code-request-class is whatever Claude Code chooses to send; no upstream component verifies it.

Suppose your gateway rewards requests labeled as background with discounted rates, bigger quotas or relaxed rate limits. Then a client-supplied string is quietly making an authorization decision. A developer who edits their shell environment, or anyone running a tiny local proxy in front of the gateway, can pick whichever label gets the most generous handling. Hints are fine inputs for queueing, cache decisions and measuring latency, but never for deciding what a caller is entitled to.

The second practical issue is adoption. The headers are disabled unless enabled explicitly, which is the right choice for metadata that exposes session structure to a middlebox. Combined with their absence from the environment variable reference at the time of writing, few installations are likely to have them enabled. In an organization, they will usually need to be rolled out through managed settings by an administrator who noticed a single changelog line.

A rollout plan for gateway operators

If your team operates a gateway for Claude Code traffic, a cautious adoption path looks like this:

  • Enable CLAUDE_CODE_GATEWAY_HINT_HEADERS=1 for a small pilot group.
  • Log all five headers for about a week without acting on them.
  • Compare their values with the decisions your current body-parsing classifier makes.
  • If they agree, switch routing to the headers, stop buffering bodies and remove the parser.
  • Keep entitlement decisions on authenticated identity, never on hint values.

The payoff is a meaningfully smaller blast radius: your developers' source code no longer has to pass through the routing layer's memory, and it costs one environment variable.

Wrapping up

The headers themselves are specific to Claude Code, but the design lesson applies to any agent platform. Any layer placed in front of an agent needs to classify requests before passing them along, and there are only two ways to do it: deduce the answer from the payload, or receive it as explicit metadata. Deduction always works, but it always leaves a copy of sensitive content in a component that had no reason to hold it. An explicit metadata channel takes more upfront design, but it is the only arrangement where the intermediary can route correctly while never being handed the data itself. For the broader picture of what a gateway provides, see the LLM gateway overview in the Claude Code docs.