[Bug] reasoning_extraction misreads a user-defined analysis section in the response as an attempt to extract internal reasoning (Opus 5, Claude Code and claude.ai)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
With Opus 5, instructions that define an analysis section in the response are
refused with Details: [reasoning_extraction]. The classifier appears to read
a user-defined way of structuring the response as an attempt to extract the
model's internal reasoning. The full test matrix below gives identical results
in three setups — Claude Code with the text in CLAUDE.md, claude.ai project
instructions, and a fresh claude.ai chat with no project or prior context — so
the trigger is the text itself. Two independent minimal repros follow; in one,
two lines that each pass alone are blocked together, and neither contains any
instruction to produce reasoning.
What Should Happen?
These instructions describe how the response should be structured. They do not
ask for the model's internal reasoning and should not be refused. At minimum,
two lines that each pass on their own should not be refused when combined.
Error Messages/Logs
Setup A (Claude Code):
● Opus 5's safeguards stopped the response above · continuing once with that noted
● API Error: Opus 5's safeguards flagged this message (https://www.anthropic.com/legal/aup). This sometimes happens with safe, normal conversations. Claude Code can't respond to this message with Opus 5.
Details: `[reasoning_extraction]`
Request ID: req_011CfHfyqe8NjyCEmE8jfcvS
Setup B (claude.ai):
Paused
Identify issue
Edit and retry with Opus 5
Opus 5's safeguards flagged this message. This sometimes happens with safe,
normal conversations. Continue with Sonnet 4.6, send feedback, or learn more.
Details: [reasoning_extraction]
Steps to Reproduce
Setup
Every test was run three ways, with identical results:
- A — Claude Code: new empty directory containing only a
CLAUDE.mdwith
the text under test. Start claude (v2.1.278, Opus 5), send 123.
- B — claude.ai project: new project, no files or other customization.
Text under test in the project instructions; start a chat, send 123.
- C — claude.ai fresh chat: no project, no prior context. Send the text
under test as the message.
Repro 1 — heading + closing line
**回应前的分析区**
正文只执行目标,不复述分析区的内容。对就说对,不对就说哪里不对。
The heading names an analysis section that comes before the response. The
closing line says the body carries out the goal without restating the analysis
section, and that the model should say plainly what is right and where
something is wrong. Each line alone passes. Together they are blocked.
Repro 2 — format rule
每轮回应以 `<分析>` 开始,`</分析>` 结束,之后才是正文。区内依次执行五步,每步的产出直接写出来,不要写成对该步的说明。
This line says each response opens with a tagged analysis block, followed by
the body; inside the block, the five steps are carried out in order, and each
step's output is written directly rather than as a description of the step.
Blocked on its own.
Results
| Text under test | Result (A, B, C) |
|---|---|
| Heading only | Passes |
| Closing line only | Passes |
| Heading + closing line | Blocked |
| Format rule only | Blocked |
| Heading + format rule + closing line | Blocked |
| Five-step procedure only | Passes |
Claude Model
Opus
Is this a regression?
Yes, this worked in a previous version
Last Working Version
2.1.278 (Claude Code)
Claude Code Version
2.1.278 (Claude Code)
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
Windows Terminal
Additional Information
What the text is
The full text has four parts, each with one job:
- Heading — a section title. In the full project instructions this block
is one of several sections; the heading marks where it starts.
- Format rule — opens each response with a tagged block for the written
analysis, followed by the answer. The tags mark where the analysis ends and
the answer begins, so the answer can be read and checked on its own.
- Five-step procedure — how the analysis is done: decompose the message
into atomic units; interpret each unit (speech-act function, Gricean
implicature); reconstruct the handling, with every item traced to its source
unit and missing information named rather than guessed; state one goal
sentence; trace back from the goal and revise if the chain breaks.
- Closing line — keeps the answer to the goal: it carries out what the
analysis settled on, without restating the analysis, and says plainly what is
right and where something is wrong.
The reason for writing this into the response: on claude.ai the user can
neither see the model's thinking in full nor control its depth. The response is
the only place a user can put structure on reasoning and check it.
Without such instructions, the working ends up inside the answer, and the cost
falls on the reader: every proposal has to be followed to the end to find out
whether it survives. In the example below, the response opens by saying there
is one ordering error. It then examines each candidate and clears every one,
proposes two changes, and withdraws both. No error is ever identified, yet the
verdict in the first line is never corrected. A reader who trusts the opening,
or acts on a proposal before reaching its retraction, is misled. Separating
what stands from what was withdrawn is work the reader has to do on every
response.
With the analysis in its own section and the answer limited to what the
analysis settled on, the answer can be taken as stated, and the analysis can be
checked separately.
Why this is not reasoning extraction
The classifier is documented as guarding against prompts that elicit the
model's internal reasoning as part of the response text. These instructions do
not ask for the internal reasoning. They ask the model to write new analysis,
to a procedure the user specifies, in a section the user can read — the same
thing as asking for a worked solution. The model's actual thinking is
untouched.
The results point the same way. The procedure itself passes. What is blocked
is any instruction that places an analysis section in the response — stated
outright (repro 2) or only implied by two lines that are harmless apart
(repro 1). The classifier appears to treat a user-specified analysis section in
the output as extraction of internal reasoning.
Other observations
- These instructions worked normally for one to two days before the refusals
began.
- In setup B, the offered remedy is
Edit and retry with Opus 5. The message
to edit is 123.
- On claude.ai the fallback offered is Opus 5 → Sonnet 4.6, not the Opus 4.8
path described for flagged requests elsewhere.
- The documentation describes this category for Fable 5 / 5.1. This is Opus 5.
- #95670 argues the same detail string is specific to Claude Code because the
same account works in a browser. This case reproduces in both.
- Request ID (setup A):
req_011CfHfyqe8NjyCEmE8jfcvS. claude.ai does not
surface request IDs.
Related
- #95561 — 23 false positives on benign content, across claude-opus-5 and
claude-fable-5 / 5-1
- #87640 — fires on
Hiwith a large loaded context - #95670 — same detail string, all requests blocked in Claude Code
- #95782 — earlier filing of this report. It was auto-labelled
invalid
before the Claude Code repro was added; once that label was removed, the
bug label was not restored. Superseded by this issue.
<details>
<summary>Full instruction text (Chinese original)</summary>
回应前的分析区
每轮回应以 <分析> 开始,</分析> 结束,之后才是正文。区内依次执行五步,每步的产出直接写出来,不要写成对该步的说明。
拆解 — 把这一轮消息拆成原子单元。一个单元 = 一件独立的事。不合并、不概括,句子的归属和限定词随单元保留。
理解 — 在每个单元上执行两问。第一问(言语行为):字面含义不等于实际含义,这个单元在这条消息里承担什么功能。第二问(会话含义):说话人默认合作,任何该说没说、说多了、说偏了的地方都带信息,它隐含了什么没明说的约束。产出是逐单元的,不是整体印象。
重建 — 从上一步的单元和约束推出这一轮实际成立的处置。起点必须是拆解出的单元,每条处置指得出它来自哪个单元。同时点明:哪些依据在手,哪些缺,缺的说缺什么,不用推测补齐。
目标 — 写出一句明确的"我需要做什么"。它必须落在重建的约束之内。
回溯 — 从目标倒推到理解产物,中间每一环是一个可检查的条件。链子接不上,说明目标定错了,回到重建。
正文只执行目标,不复述分析区的内容。对就说对,不对就说哪里不对。
</details>
<details>
<summary>Example: default response without the instructions (code review, Chinese)</summary>
结构对,一处顺序错。
model 那个判断在 pending 赋值之后、循环回头之前 return,所以 Model 确实先于这一帧的 Piece 出去——这个没问题。但如果这一帧同时带 model 和 pieces,你 return 之后下一次进 loop,pending 有东西,正常吐。也对。
问题在 stream.next()? 那个 ?。流结束时 pending 一定已经空了(先检查的),所以返回 None 没错。但 End 之后 Response 要持有终止状态给 Watch 读——现在 StreamBody 的 next 返回 None 就没了,WatchEnd 的依据(finish_reason / stop_reason / tool_calls)没有任何字段接。那是下一步的事,不是这段的 bug。
真正该改的是这个:
for piece in &pieces { ... }
self.pending = pieces.into_iter();
借用再消费能过,但白遍历一次加一堆 push_str(&String) 的解引用。累积和铺是同一份数据的两个去处,& 那一遍是多余的:
self.pending = pieces
.into_iter()
.inspect(|piece| match piece { .. })
.collect::<Vec<_>>()
.into_iter();
——这更丑。就留 for piece in &pieces,一帧两三个元素,没有可省的东西。
model.clone() 那里可以去掉:
if self.model.is_none() && let Some(model) = model {
self.model = Some(model.clone());
return Some(Ok(Delta::Model(model)));
}
还是要 clone,因为两处都要。改成 Delta::Model 里放 String、self.model 存同一份的话就得 Rc 或者让 Delta 借。不值得,clone 一个模型名字符串。
留着。
</details>
This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗