---
title: How do you stop Claude Code from expanding untrusted prompt text?
url: https://deepthinkingai.org/claude-agent-sdk-verbatim-prompts/
published: 2026-09-23
author: Shekhar Singh
topic: AI Engineering
tags: claude-code, claude-agent-sdk, prompt-injection, permissions, sdk
site: DeepThinking AI
---

# How do you stop Claude Code from expanding untrusted prompt text?

**Summary:** Anthropic added `verbatim_prompts` to the Python Agent SDK on 23 September 2026. It marks each user message `client_composed: true`, which Claude Code 2.1.248 and later delivers as written, so `@/path` tokens do not expand into file contents and slash commands do not dispatch. For apps that inline untrusted text, this closes a pre-permission local-read gap.

## Key takeaways
- Claude Agent SDK 0.2.158 adds `verbatim_prompts`, and the implementation stamps each outgoing user message `client_composed: true`.
- Claude Code expands an `@/absolute/path` token in prompt text into local file contents by default, outside the working directory and without a tool call.
- `verbatim_prompts` works in `query()`, `ClaudeSDKClient.connect()`, and `ClaudeSDKClient.query()`, including streamed prompts.
- The option requires Claude Code 2.1.248 or later, and older CLIs warn instead of honoring the flag.
- Tool permission rules still matter because `verbatim_prompts` changes message delivery, not what tools can run after the message reaches Claude.

Anthropic shipped `verbatim_prompts` in `claude-agent-sdk-python` v0.2.158 on
23 September 2026. The release is worth attention because the patch states
plainly that Claude Code expands an `@/absolute/path` token anywhere in prompt
text into file contents, outside the working directory and without a tool call,
and the new option closes that path by making the CLI deliver the prompt as
written.

## What does `verbatim_prompts` change in Claude Agent SDK?

It adds one option with a narrow and important scope. Anthropic's release note
for `claude-agent-sdk-python` v0.2.158 says `ClaudeAgentOptions.verbatim_prompts`
defaults to `False`, and when you switch it on, user messages are "delivered to
the CLI exactly as written". The patch shows how: every outgoing user message is
copied with `client_composed: true`, and Claude Code 2.1.248 and later treats
that flag as the instruction to skip `@path` expansion and slash-command
dispatch. Anthropic also wired the same behavior into all three SDK entry
points it names publicly: `query()`, `ClaudeSDKClient.connect()`, and
`ClaudeSDKClient.query()`, including streamed prompts. That breadth matters,
because the unsafe case is rarely a single static string. It is usually a prompt
your app assembled from prior turns, retrieved text, or user content just before
submission, the same territory where [session prompt snapshots](/claude-agent-sdk-system-prompt-snapshot/)
already made precise transport details matter.

**Where `verbatim_prompts` changes the Claude Code prompt path**

1. App composes a user message
   Untrusted text may already be inlined here.
2. SDK stamps `client_composed: true`
   Only when `verbatim_prompts` is enabled.
3. Claude Code delivers the text as written
   No `@path` expansion or slash commands.
--- transform boundary ---
4. `@/absolute/path` expansion
   Default behavior when the flag is off.
5. Slash-command dispatch
   Default behavior when the flag is off.

The important detail is position. `verbatim_prompts` changes how Claude Code interprets the user message before any tool permission policy could matter, so it closes a path that permissions do not see.

## Why do Claude Code permissions miss `@path` expansion?

Because the file read happens before the permission surface the SDK documents.
Anthropic's patch message says Claude Code expands an `@/absolute/path` token
"outside the working directory and without a tool call", and the end-to-end test
proves the point by setting `tools=[]` and then checking whether the model can
still read a planted marker from a temp file. It can, until `verbatim_prompts`
is enabled. Read that next to the Python SDK docs: `allowed_tools` auto-approves
tools, and `disallowed_tools` blocks them, but those controls only apply once
the interaction is already a tool request. The local file expansion path never
becomes one. That is the real finding here, and it is the part most launch-post
style summaries would skip. If your defense model starts at permission prompts,
you are already one stage too late, the same kind of boundary mistake that
[Anthropic's auto permission policy](/auto-permission-policy-intent-channel/)
forces teams to think about on the server side.

**What changes when the same prompt is sent verbatim**

```mermaid
sequenceDiagram
    participant Yourapp as Your app
    participant ClaudeCodeCLI as Claude Code CLI
    Yourapp->>ClaudeCodeCLI: Prompt contains `@/tmp/secret.txt`
    ClaudeCodeCLI->>ClaudeCodeCLI: Default mode expands the local file
    ClaudeCodeCLI->>Yourapp: Model sees file contents
    Yourapp->>ClaudeCodeCLI: Same prompt with `client_composed: true`
    ClaudeCodeCLI->>Yourapp: Model sees the literal `@/tmp/secret.txt`
```

- Prompt contains `@/tmp/secret.txt`: The text could have come from a prior turn or a third party.
- Default mode expands the local file: The patch says this happens without a tool call.
- Model sees file contents: Even if tools are empty.
- Same prompt with `client_composed: true`: Set by `verbatim_prompts`.
- Model sees the literal `@/tmp/secret.txt`: No hidden file read and no slash-command dispatch.

Anthropic's own end-to-end test plants a random marker in a temp file and checks whether the model can see it. With the flag off, it can. With the flag on, the marker no longer reaches the model.

## How does `client_composed` block file expansion and slash commands?

By changing the message Claude Code receives before Claude Code interprets it.
The implementation is short enough to read in one pass. A helper
called `stamp_user_message` returns the message unchanged when
`verbatim_prompts` is off, and otherwise returns a copy with
`client_composed: true`, overwriting any caller-supplied value. The transport
layer names the contract too: `VERBATIM_PROMPTS_MINIMUM_CLAUDE_CODE_VERSION =
"2.1.248"`, and the surrounding comment says that is the first Claude Code
version that honors the flag. That pairing matters operationally. This feature
is a client transport behavior keyed to a specific CLI capability. Anthropic's opinion in the code is
clearer than the release note alone: if the CLI is too old, you do not have the
control, however recent the Python package is. Treat this as a versioned wire
contract between SDK and CLI, closer to a protocol flag than to a prompt tweak.

## Should you enable `verbatim_prompts` by default in production?

Yes for any application that relays or reuses text it did not author itself.
That includes chat products, coding assistants that quote tool output back to
the model, and review flows that fold a prior answer into the next prompt.
Those systems are already composing prompts dynamically, and the patch shows
that one literal substring can turn that composition step into a local file
read. Turning `verbatim_prompts` on by default is the right posture because it
shrinks the implicit behavior surface and forces intentional behavior back into
explicit code. The one strong case for leaving it off is local automation that
deliberately relies on Claude Code affordances such as `@path` shortcuts or
slash-command dispatch inside SDK-submitted prompts. Even there, I would treat
the default as backwards. Apps that want hidden expansion should opt into it
deliberately. Production controls age better
when convenience features have to opt in.

## What still needs guarding after you enable `verbatim_prompts`?

Everything that happens after the message reaches Claude. The implementation
only stamps outgoing user messages, so it does not change system prompts,
`CLAUDE.md`, tool permission policy, sandbox scope, or the behavior of any tool
Claude later decides to call. It also does nothing for the basic discipline of
separating instructions from quoted data. If your application mixes operator
intent and relayed user text into one natural-language blob, the prompt is still
hard to reason about, even when the `@path` token stays literal. That is why I
see `verbatim_prompts` as a boundary repair rather than a full prompt-injection
story. It closes one precise transport-layer gap. You still need the rest of
the stack: careful prompt composition, explicit file access, and the review of
which controls are descriptive versus enforced that this site has already
[worked through elsewhere](/agent-controls-read-vs-write/). Good security work
often looks exactly like this: one quiet feature flag that removes an entire
class of accidental behavior.

<ReadNext
  href="/auto-permission-policy-intent-channel/"
  kicker="Related"
  title="Can Claude's auto permission policy stop a prompt injection?"
  note="The next boundary after transport: which inputs Anthropic's server-side evaluator treats as your intent, and which it only assesses."
/>

## Ship Claude Agent SDK prompts without hidden local-file reads

The steps are ordered so you close the hidden read path first, then preserve the intentional behavior you still need.

1. **Enable `verbatim_prompts` anywhere a prompt can contain outside text**: Turn it on for chat products, review agents, ticket flows, or any pipeline that inlines prior model output, tool output, or user text into a new prompt. That removes the pre-permission expansion path before you tune anything else.
2. **Pin compatible versions before rolling the change out**: Anthropic requires `claude-agent-sdk` 0.2.158 and Claude Code 2.1.248 or later. Older CLIs warn instead of honoring the flag, so version drift can leave one environment protected and another exposed.
3. **Replace any intentional `@path` or slash-command shortcuts with explicit code**: If your app relied on Claude Code expanding a file reference inside prompt text, move that behavior into a real file read, an SDK-managed prompt file, or a tool your application calls on purpose.
4. **Keep permission rules and hooks in place after the change**: `verbatim_prompts` only changes message delivery. Once Claude receives the text, normal tool execution and approval logic still determine what the agent may do next.
5. **Separate operator intent from relayed user text**: The safer prompt path still needs a clean instruction boundary. If your application carries end-user text forward, quote it as data rather than blending it into the instruction that tells the agent what to do.
6. **Re-test the exact prompts your production app generates**: Anthropic's tests prove the transport mechanism. They do not prove your composition logic. Capture one real prompt from your app, send it with the flag on, and confirm the literal `@...` text stays literal all the way through.


## Frequently asked questions

### Does `verbatim_prompts` disable tools or permission prompts?

No. It changes how the SDK sends user messages to Claude Code, so prompt text is delivered literally. Tool execution, approval rules, hooks, and sandbox settings still run the same way after the message reaches Claude.

### Does `verbatim_prompts` work with streamed prompts?

Yes. The patch adds the same stamping behavior to streamed user messages and to one-shot string prompts. Anthropic's release note also names `query()`, `ClaudeSDKClient.connect()`, and `ClaudeSDKClient.query()` as covered surfaces.

### What breaks when I turn `verbatim_prompts` on?

Any SDK prompt flow that intentionally relied on Claude Code's `@path` expansion or slash-command dispatch now stays literal. If your app used those as shortcuts, move that behavior into explicit file reads, tools, or application code you control.

### Should every production app enable `verbatim_prompts`?

Any app that mixes model prompts with text a user, tool, or third party supplied should. A single-user local automation flow that intentionally uses `@path` shortcuts can leave it off, but it should be an explicit choice rather than a default.


## Sources
- [claude-agent-sdk-python v0.2.158](https://github.com/anthropics/claude-agent-sdk-python/releases/tag/v0.2.158). Anthropic, 2026-09-23
- [Add verbatim_prompts option to deliver prompts as written](https://github.com/anthropics/claude-agent-sdk-python/pull/1269.patch). Anthropic, 2026-09-17
- [Python Agent SDK](https://code.claude.com/docs/en/agent-sdk/python). Anthropic
- [subprocess_cli.py](https://raw.githubusercontent.com/anthropics/claude-agent-sdk-python/main/src/claude_agent_sdk/_internal/transport/subprocess_cli.py). Anthropic

---
Canonical HTML: https://deepthinkingai.org/claude-agent-sdk-verbatim-prompts/