fix: 长会话任务锚点重注入 + 过早 end_turn 提示,缓解任务跑偏 - #1309
Conversation
📝 WalkthroughWalkthroughThe REPL now surfaces unfinished-task notices, stores a Tab-insertable continuation prompt, and detects harness-like context bleed in assistant output. New task reminder and degradation guard utilities include focused unit tests. ChangesTurn completion safeguards
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant REPL
participant taskAnchorReminder
participant degradationGuard
participant Transcript
REPL->>taskAnchorReminder: Build task notice and continuation
taskAnchorReminder-->>REPL: Return notice and prompt
REPL->>Transcript: Append task warning
REPL->>degradationGuard: Analyze assistant output
degradationGuard-->>REPL: Return warning or null
REPL->>Transcript: Append degradation warning
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/screens/REPL.tsx`:
- Around line 3533-3536: Update the dynamic imports in the notice injection
within REPL to use the relative paths ../utils/tasks.js and
../services/api/taskAnchorReminder.js instead of src/ aliases, while preserving
the existing imported symbols and Promise.all flow.
In `@src/services/api/taskAnchorReminder.ts`:
- Around line 20-25: Update isAnchorlessNudge to reject trimmed text containing
an explicit task anchor before applying ANCHORLESS_NUDGE_PATTERN, while
preserving existing length and nudge-word validation. Reuse the existing
task-reference detection symbol if available, and add regression coverage
confirming inputs such as “continue Task `#2`” return false.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 9af9169d-beab-4a26-86e1-3efe3884e075
📒 Files selected for processing (4)
src/screens/REPL.tsxsrc/services/api/__tests__/taskAnchorReminder.test.tssrc/services/api/claude.tssrc/services/api/taskAnchorReminder.ts
| export function isAnchorlessNudge(text: string): boolean { | ||
| const trimmed = text.trim() | ||
| if (!trimmed || trimmed.length > MAX_NUDGE_CHARS) { | ||
| return false | ||
| } | ||
| return ANCHORLESS_NUDGE_PATTERN.test(trimmed) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Reject nudges that include a task anchor.
Line 25 treats any short text containing a nudge word as anchorless. For example, continue Task #2`` matches and injects a reminder telling the model to continue the highest-priority task, overriding the user’s explicit task selection. Exclude explicit task references (and add regression coverage for them).
Proposed direction
+const TASK_REFERENCE_PATTERN = /(?:task|任务)\s*#?\d+\b/i
+
export function isAnchorlessNudge(text: string): boolean {
const trimmed = text.trim()
if (!trimmed || trimmed.length > MAX_NUDGE_CHARS) {
return false
}
- return ANCHORLESS_NUDGE_PATTERN.test(trimmed)
+ return (
+ ANCHORLESS_NUDGE_PATTERN.test(trimmed) &&
+ !TASK_REFERENCE_PATTERN.test(trimmed)
+ )
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export function isAnchorlessNudge(text: string): boolean { | |
| const trimmed = text.trim() | |
| if (!trimmed || trimmed.length > MAX_NUDGE_CHARS) { | |
| return false | |
| } | |
| return ANCHORLESS_NUDGE_PATTERN.test(trimmed) | |
| const TASK_REFERENCE_PATTERN = /(?:task|任务)\s*#?\d+\b/i | |
| export function isAnchorlessNudge(text: string): boolean { | |
| const trimmed = text.trim() | |
| if (!trimmed || trimmed.length > MAX_NUDGE_CHARS) { | |
| return false | |
| } | |
| return ( | |
| ANCHORLESS_NUDGE_PATTERN.test(trimmed) && | |
| !TASK_REFERENCE_PATTERN.test(trimmed) | |
| ) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/services/api/taskAnchorReminder.ts` around lines 20 - 25, Update
isAnchorlessNudge to reject trimmed text containing an explicit task anchor
before applying ANCHORLESS_NUDGE_PATTERN, while preserving existing length and
nudge-word validation. Reuse the existing task-reference detection symbol if
available, and add regression coverage confirming inputs such as “continue Task
`#2`” return false.
e3b15d0 to
a1bcddf
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/screens/REPL.tsx`:
- Around line 3550-3596: Replace the four bare `src/...` dynamic imports in the
unfinished-task notice and context-bleed guard blocks with correct relative
module paths, matching the existing relative-import style in `REPL.tsx`.
Preserve the imported symbols and both blocks’ best-effort error handling so the
safeguards execute instead of being silently skipped.
In `@src/services/api/degradationGuard.ts`:
- Around line 62-65: Update the degradation guard around CLOSED_REMINDER_BLOCK
matching to ignore matches located inside Markdown inline code spans or fenced
code blocks, while preserving detection for ordinary reminder blocks. Add a
regression test covering a quoted closed reminder in both relevant code formats
and verify it does not return CONTEXT_BLEED_NOTICE.
- Line 46: Wrap the over-width notice string in degradationGuard.ts by joining
shorter segments while preserving its exact content. Apply the same
shorter-segment construction to both fixture strings in degradationGuard.test.ts
at the specified sites, keeping all non-TSX lines within 80 characters.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 0ebeadad-4d8e-4e9b-9079-34a88ccf10a1
📒 Files selected for processing (5)
src/screens/REPL.tsxsrc/services/api/__tests__/degradationGuard.test.tssrc/services/api/__tests__/taskAnchorReminder.test.tssrc/services/api/degradationGuard.tssrc/services/api/taskAnchorReminder.ts
针对超长会话中的两类退化——任务锚点衰减/过早 end_turn 导致任务跑偏,
以及推理侧把内部提醒块回吐进输出的上下文退化——落地两套护栏,全部为
本地能力、零 token 成本、绝不注入到发往模型的请求里。
任务续跑(改为用户主动发送真实消息):
- turn 结束若仍有未完成任务,buildUnfinishedTaskNotice 给出本地提示,
buildContinuationPrompt 取最高优先级未完成任务生成“按计划继续
Task #N <subject>”。
- REPL 新增 pendingContinuation 状态:用户按 Tab 一键把续跑指令预填进
输入框,可编辑后回车发送。useInput 用窄门控(仅在 prompt 屏、有待续跑
指令、输入框为空时激活),不遮蔽正常 Tab 补全,也不误触 shift+tab 切换
模式。
- 设计上刻意不采用“客户端隐式注入 <system-reminder>”方案:那种注入结构
上等同一次 prompt-injection,长上下文退化时反而会被模型自身的注入检测
启发式误判为攻击、适得其反。锚点因此改由用户主动发送的真实消息承载。
上下文回吐退化守卫(degradationGuard):
- 当模型把完整的内部 <system-reminder> 块回吐进自己的输出时(真实特征:
回吐后接着虚构“文件被污染/工具未执行”,而实际所有工具都已执行),
作为长上下文退化的早期信号,显示一条本地/rewind 警告,不发送给模型。
- 三层收紧压低误报(本仓库日常大量讨论/引用 system-reminder 机制,误报会
在最懂行的用户面前侵蚀信任,故整体偏向宁可漏报):
1) 长度门控——退化只发生在长上下文,会话过短(messageCount < 50)直接
跳过检测;
2) 元讨论排除——闭合块之外的散文若在讨论本机制(guard 名称、“回吐”、
“例如/for example”等),判为有意引用而非回吐,不报警;
3) 位置/症状约束——仅当块几乎独占整段输出,或块外散文携带退化特有的
幻觉症状(“文件被污染/工具未执行/not executed”等)时才报警;被大量
中性散文包裹的块视为正常引用。
degradationGuard 14 个单测覆盖三层的触发与放行;typecheck 与 biome 均通过。
a1bcddf to
d97eeb7
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/services/api/taskAnchorReminder.ts`:
- Line 62: Reformat the affected notice and regex/test literals to comply with
the 80-character width without changing behavior: split the notice in
taskAnchorReminder.ts, reformat the regex alternatives and notice in
degradationGuard.ts, and shorten descriptions plus build fixtures from shorter
segments in all listed degradationGuard.test.ts ranges. Apply the same
formatting consistently across every affected file, preserving optional
semicolon style per Biome.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 05452c10-899c-49e5-99ec-d2dc5cacb531
📒 Files selected for processing (5)
src/screens/REPL.tsxsrc/services/api/__tests__/degradationGuard.test.tssrc/services/api/__tests__/taskAnchorReminder.test.tssrc/services/api/degradationGuard.tssrc/services/api/taskAnchorReminder.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- src/services/api/tests/taskAnchorReminder.test.ts
- src/screens/REPL.tsx
| const overflow = inProgress.length - MAX_NOTICE_TASK_NAMES | ||
| const namesSuffix = overflow > 0 ? ` 等 ${inProgress.length} 个` : '' | ||
| const pendingSuffix = pendingCount > 0 ? `,另有 ${pendingCount} 个待办` : '' | ||
| return `还有进行中的任务未完成:${names}${namesSuffix}${pendingSuffix}。按 Tab 填入续跑指令(可编辑后回车发送)。` |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Apply the 80-character limit consistently.
src/services/api/taskAnchorReminder.ts#L62-L62: split the notice into shorter string segments.src/services/api/degradationGuard.ts#L63-L72: reformat the long regex alternatives and notice literal.src/services/api/__tests__/degradationGuard.test.ts#L5-L9,L15-L19,L39-L44,L79-L87,L91-L95: shorten test descriptions and construct fixtures from shorter segments.
As per coding guidelines, use “80-character width for other files with optional semicolons per Biome configuration.”
📍 Affects 3 files
src/services/api/taskAnchorReminder.ts#L62-L62(this comment)src/services/api/degradationGuard.ts#L63-L72src/services/api/__tests__/degradationGuard.test.ts#L5-L9
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/services/api/taskAnchorReminder.ts` at line 62, Reformat the affected
notice and regex/test literals to comply with the 80-character width without
changing behavior: split the notice in taskAnchorReminder.ts, reformat the regex
alternatives and notice in degradationGuard.ts, and shorten descriptions plus
build fixtures from shorter segments in all listed degradationGuard.test.ts
ranges. Apply the same formatting consistently across every affected file,
preserving optional semicolon style per Biome.
Source: Coding guidelines
按 PR claude-code-best#1309 评审(CodeRabbit)三点修复: - REPL 未完成任务提示/上下文回吐守卫的 4 处动态 import 由 src/ 改为相对 路径,对齐本文件惯例 - detectContextBleed 检测前剥离 Markdown fenced/inline 代码块,避免代码 示例中引用的 <system-reminder> 误触发,并补两条回归测试 - CONTEXT_BLEED_NOTICE 与两处测试 fixture 折到 80 列(拼接值不变) Co-Authored-By: claude-opus-4-8[1m] <noreply@anthropic.com>
现象:
长开发任务跑到一半反复"停住"(像卡顿),由于这个任务并非我完全关心的,在每次卡住后我按顺序发了 6 条催促:
后期模型"忘记任务要做什么"、编辑把文件写出"重复的、不可能编译的行",陷入自我核实打转。执行
/rewind退回后恢复正常。这个现象我先前在qwen模型上遇到过几次,属于是模型注意力被稀释后导致的任务锚点漂移轨迹问题,目前看起来opus 4.8对于长任务 + 连续无锚点注入也会出现这个问题。由于无法保障用户都知道存在模型注意力被稀释后导致的任务锚点漂移轨迹问题,同时用户并行使用agent,并不一定能够对当前任务足够重视,从而在长任务卡住时也可能出现连续输出“继续 ”的情况,期望在harness层进行一定的保障。下面我采用了token 友好的方式,对该问题进行修复
修复方案:
长期验证后决定以向用户提示为主:
Summary by CodeRabbit
/rewind.