Skip to content

feat: 内存碎片及死代码清理#412

Merged
claude-code-best merged 9 commits intomainfrom
feature/20260504/improve
May 5, 2026
Merged

feat: 内存碎片及死代码清理#412
claude-code-best merged 9 commits intomainfrom
feature/20260504/improve

Conversation

@claude-code-best
Copy link
Copy Markdown
Owner

@claude-code-best claude-code-best commented May 5, 2026


View in Codesmith
Need help on this PR? Tag @codesmith with what you need.

  • Let Codesmith autofix CI failures and bot reviews

Summary by CodeRabbit

  • New Features

    • Added actionable guidance to error messages for budget limits, turn counts, and session management.
    • Enhanced help documentation with clearer getting-started instructions.
  • Bug Fixes

    • Improved memory efficiency and performance optimization for large-scale operations.
  • Documentation

    • Updated onboarding messaging with simplified security guidance.
    • Refined permission dialog copy and shortcuts.
  • Refactor

    • Removed auto-update functionality.
    • Removed unused MCP adapter implementations.

claude-code-best and others added 9 commits May 5, 2026 00:05
- 为 budget/turns/structured-output 三种错误消息添加 Tip 提示,指导用户如何继续
- Onboarding 安全步骤标题从 "Security notes" 改为更友好的 "Before you start, keep in mind"
- Trust Dialog 精简为两句核心信息,降低认知负荷
- 新增 7 个测试验证消息内容包含关键引导信息

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Help General 页添加 3 步 Getting started 引导,替代单段描述
- 权限对话框底部 "Esc to cancel" → "Esc to reject","Tab to amend" → "Tab to add feedback"
- .claude/ 文件夹权限选项标签从 60 字符缩至 49 字符,避免窄终端截断
- 新增 10 个测试覆盖权限提示文案和帮助页引导内容

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- ModelPicker 副标题从技术说明改为操作提示(effort 调整、1M 切换)
- /resume 错误提示添加 "Run /resume to browse" 操作引导
- 新增 6 个测试覆盖模型选择器、会话恢复和 cost 消息文案

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- "Not enough messages" 添加 "Send a few more messages first" 引导
- "Conversation too long" 提示从模糊的 "Press esc twice" 改为建议 /compact 或 /clear
- 自动压缩标题从 "Compact summary" 改为 "Conversation summarized to free up context"
- 快捷键提示从 "expand" 改为 "view summary"
- 新增 7 个测试覆盖压缩相关消息

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…closures with pre-computed strings

The buildDiffableContent and buildPrevDiffableContent fields were closures
capturing full system prompt and tool schema arrays (~300KB each). With 10
map entries × 2 closures, this held ~6MB of GC-unreachable memory.

Since recordPromptState already serializes the same data for hashing,
pre-computing the diffable content string has negligible marginal cost.
删除未使用的文件/目录(mcp/adapter、cli/update.ts 等)、
未使用的重导出文件(design-system/color.ts 等 12 个)、
7 个零引用的导出函数、修复 5 处 as any 为精确类型。
净减少 ~1194 行代码,precheck 4077 测试全部通过。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@mintlify
Copy link
Copy Markdown

mintlify Bot commented May 5, 2026

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
ccb-863780bf 🟢 Ready View Preview May 5, 2026, 11:39 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 5, 2026

📝 Walkthrough

Walkthrough

This PR bundles four distinct improvement categories: enhanced user-facing copy across CLI and UI components with corresponding test coverage; removal of @anthropic/ink re-exports from the design-system layer; memory and performance optimizations via frozen request payloads and async chunking; and cleanup of legacy features (MCP adapters, update system, OCR utilities). The changes are documented in a progress file tracking design reviews.

Changes

User-Facing Copy & Error Messaging

Layer / File(s) Summary
Error Message Constants
src/QueryEngine.ts, src/cli/print.ts, src/services/compact/compact.ts
Budget, turn, compaction, and context-overflow error messages are rewritten to include actionable guidance (e.g., --max-budget-usd flag, /compact command, "start a new session").
UI Copy Updates
src/components/Onboarding.tsx, src/components/TrustDialog/TrustDialog.tsx, src/components/ModelPicker.tsx, src/components/CompactSummary.tsx, src/components/HelpV2/General.tsx, src/commands/resume/resume.tsx
Onboarding security wording shifted to "Before you start, keep in mind"; trust dialog simplified to concise language; model picker and compact summary descriptions clarified; help getting-started section restructured with numbered steps; resume errors now guide users to browse/select sessions.
Permission Dialog Copy
src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx, src/components/permissions/FilePermissionDialog/FilePermissionDialog.tsx, src/components/permissions/FilePermissionDialog/permissionOptions.tsx
Footer hints changed from "cancel/amend" to "reject/add feedback"; .claude/ config option label shortened for clarity.
Test Coverage
src/cli/__tests__/userFacingErrorMessages.test.ts, src/commands/resume/__tests__/resume.test.ts, src/components/HelpV2/__tests__/General.test.ts, src/components/__tests__/compactMessages.test.ts
New Bun tests assert error strings contain expected keywords/flags, model-picker subtitle meets length/content constraints, help guide includes required steps, and compact UI text uses correct wording.
Progress Documentation
progress.md
Design review notes spanning four rounds: error guidance, permissions/help, model selection/resume, and compaction—with file change tracking and test coverage notes.

Design System & Component Layer Cleanup

Layer / File(s) Summary
Re-export Removals
src/components/design-system/Divider.tsx, src/components/design-system/FuzzyPicker.tsx, src/components/design-system/LoadingState.tsx, src/components/design-system/Pane.tsx, src/components/design-system/ProgressBar.tsx, src/components/design-system/Ratchet.tsx, src/components/design-system/StatusIcon.tsx, src/components/design-system/Tabs.tsx, src/components/design-system/ThemeProvider.tsx, src/components/design-system/ThemedBox.tsx
All @anthropic/ink component re-exports (and one default export) are removed from the design-system facade layer.
Utility Function Removal
src/components/design-system/color.ts
color() function and its colorize/theme imports removed; consumers must use direct colorization elsewhere.
Keybinding Re-exports
src/keybindings/match.ts
Re-exports of getKeyName, matchesKeystroke, matchesBinding from @anthropic/ink removed.
Task Layer Stubs
src/components/tasks/src/tasks/DreamTask/DreamTask.ts, src/components/tasks/src/tasks/LocalWorkflowTask/LocalWorkflowTask.ts, src/components/tasks/src/types/utils.ts
Type and value re-exports from task registry, plus empty placeholder stubs, removed.

Memory & Performance Optimizations

Layer / File(s) Summary
Frozen Request Payloads
src/services/api/claude.ts
Request building now precomputes and deep-clones messagesForAPI, system, and allTools into frozen copies before streaming; originals nulled for GC to break closure chains during long-lived retry/stream callbacks. Langfuse logging uses precomputed message/token counts.
Langfuse Trace Cleanup
src/query.ts
Query finalization nulls langfuse trace refs (langfuseTrace, langfuseRootTrace, langfuseBatchSpan) when the params object is a derived copy, breaking closure memory leaks.
ToolUseResult Payload Stripping
src/query.ts, src/services/compact/compact.ts
Before API calls in queryLoop and after compaction in buildPostCompactMessages, toolUseResult mutable payloads are deleted from user messages; subsequent tool_result content blocks used instead.
Cache-Break Diff Eagerness
src/services/api/promptCacheBreakDetection.ts
Diffable prompt/tool content switched from lazy () => string functions to eagerly computed strings, eliminating function-indirection overhead during cache-break analysis.
Async Chunked Directory Collection
src/hooks/fileSuggestions.ts
getDirectoryNames() replaced with getDirectoryNamesAsync() that yields to event loop every CHUNK_MS ms to prevent main-thread blocking during large file-list processing.

Feature & Code Removals

Layer / File(s) Summary
Update System
src/cli/update.ts
Entire update() workflow removed: version checks, package-manager detection, native updater logic, installation, and status handling.
MCP Adapter Layer
src/services/mcp/adapter/logger.ts, src/services/mcp/adapter/auth.ts, src/services/mcp/adapter/featureGate.ts, src/services/mcp/adapter/httpConfig.ts, src/services/mcp/adapter/imageProcessor.ts, src/services/mcp/adapter/storage.ts, src/services/mcp/adapter/subprocessEnv.ts, src/services/mcp/adapter/proxy.ts, src/services/mcp/adapter/analytics.ts, src/services/mcp/adapter/index.ts
Entire host-to-MCP dependency-adapter layer removed (logger, auth, feature gate, HTTP config, image processor, storage, subprocess env, proxy, analytics factories).
Notification & Migration Hooks
src/hooks/notifs/useAutoModeUnavailableNotification.ts, src/migrations/migrateAutoUpdatesToSettings.ts
Auto-mode unavailability notification hook and auto-update disable migration removed.
Utility Exports
src/utils/agentId.ts, src/utils/asciicast.ts, src/utils/warningHandler.ts, src/utils/systemThemeWatcher.ts, src/cli/bg/engines/tmux.ts, src/constants/outputStyles.ts
parseRequestId, getSessionRecordingPaths, flushAsciicastRecorder, resetWarningHandler, watchSystemTheme stub, getTmuxInstallHint, and hasCustomOutputStyle exports removed.
Computer-Use OCR
src/utils/computerUse/win32/ocr.ts
Win32 OCR region/window screenshot and recognition logic removed; 250 lines of PowerShell script generation and WinRT OCR integration eliminated.

Type Safety & Utility Improvements

Layer / File(s) Summary
Error Type Refinement
packages/@ant/ink/src/components/App.tsx, packages/@ant/ink/src/components/ErrorOverview.tsx
Error state in App changed to store lightweight ErrorLike (message, optional stack) instead of full Error instance; ErrorOverview accepts ErrorLike instead of Error.
Stricter Casting
src/utils/dxt/helpers.ts, src/utils/sessionTitle.ts, src/utils/mcpInstructionsDelta.ts
Manifest validation, session-title extraction, and MCP instruction delta reading replace as any casts with explicit type guards or narrower casts (e.g., `string[]
Custom Performance Implementation
src/utils/profilerBase.ts
Replaced lazy perf_hooks.performance with custom LightweightPerf backed by process.hrtime.bigint(), storing marks in a Map and supporting getEntriesByType('mark') and clearMarks().

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • claude-code-best/claude-code#308: Both PRs manage Langfuse trace lifecycle; this PR nulls trace refs in query finalization while the related PR adds trace creation and parent-span threading.
  • claude-code-best/claude-code#383: Both PRs touch compaction logic; this PR strips toolUseResult payloads and updates error messages, while the related PR implements snip-based compaction and post-compact cleanup.
  • claude-code-best/claude-code#307: Both PRs remove or update the useAutoModeUnavailableNotification hook and its related permission/mode gating logic.

🐰 Whiskers twitch with delight as I nibble through code—
Copy flows clearer, memories leak no more,
Components unshackled from ink's grand facade,
Performance hoppes forward, errors now kind—
This refactor's a feast, hooray for the bind! 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.83% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title describes the main objective of the changeset: memory fragmentation and dead code cleanup.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/20260504/improve

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter
Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 19.56522% with 111 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/services/api/claude.ts 1.58% 62 Missing ⚠️
src/services/compact/compact.ts 16.66% 15 Missing ⚠️
src/utils/profilerBase.ts 43.47% 13 Missing ⚠️
src/services/api/promptCacheBreakDetection.ts 0.00% 6 Missing ⚠️
src/QueryEngine.ts 0.00% 3 Missing ⚠️
src/query.ts 78.57% 3 Missing ⚠️
src/utils/mcpInstructionsDelta.ts 0.00% 3 Missing ⚠️
src/components/CompactSummary.tsx 0.00% 2 Missing ⚠️
src/constants/outputStyles.ts 0.00% 2 Missing ⚠️
src/utils/dxt/helpers.ts 0.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (9)
src/utils/sessionTitle.ts (2)

39-42: ⚡ Quick win

Replace repeated double-casts on origin with a type guard.

Line 40 and Line 41 rely on repeated as unknown as ... assertions, which bypass narrowing and make this branch fragile to payload shape drift. Please narrow origin once with a runtime guard and then read kind.

As per coding guidelines, "Use type guards (type narrowing) with union types instead of forced type casting".

🤖 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/utils/sessionTitle.ts` around lines 39 - 42, The condition uses repeated
double-casts on msg to access origin.kind; add a runtime type guard (e.g.,
isMsgWithOrigin(msg): msg is { origin?: { kind?: string } }) in
src/utils/sessionTitle.ts, use that guard in the conditional to narrow msg once,
and then read msg.origin.kind instead of casting twice; replace the two
occurrences of (msg as unknown as { origin... }) with a single guard call so
subsequent checks (like comparing kind !== 'human') are safely narrowed.

119-121: ⚡ Quick win

Avoid forcing result.message.content into an array shape before extraction.

At Line 120, the cast to readonly { readonly type: string }[] can hide content-shape mismatches. Prefer a guarded branch (string vs block-array) before calling extractTextContent.

Suggested change
-    const text = extractTextContent(
-      result.message.content as readonly { readonly type: string }[],
-    )
+    const content = result.message.content
+    const text =
+      typeof content === 'string' ? content : extractTextContent(content)

As per coding guidelines, "Use type guards (type narrowing) with union types instead of forced type casting".

🤖 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/utils/sessionTitle.ts` around lines 119 - 121, Change the forced cast of
result.message.content before passing to extractTextContent into a type-guard
branch: check if result.message.content is a string and use it directly, else if
Array.isArray(result.message.content) call extractTextContent with that array,
and handle null/undefined or unexpected shapes (fallback to empty string or
log/error). Update the code around the extractTextContent call (the variable
"text" and references to result.message.content) to use these guards rather than
the cast so runtime shape mismatches are avoided.
src/utils/profilerBase.ts (1)

40-47: 💤 Low value

getEntriesByType type guard is dead code at the TypeScript level.

The parameter type is typed as the literal 'mark', so the if (type !== 'mark') return [] branch is unreachable. It adds minor noise without runtime benefit.

♻️ Suggested cleanup
-  getEntriesByType(type: 'mark'): CheckpointEntry[] {
-    if (type !== 'mark') return []
-    const entries: CheckpointEntry[] = []
+  getEntriesByType(_type: 'mark'): CheckpointEntry[] {
+    const entries: CheckpointEntry[] = []
     for (const [name, startTime] of this.marks) {
       entries.push({ name, startTime, entryType: 'mark' })
     }
     return entries
   }
🤖 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/utils/profilerBase.ts` around lines 40 - 47, The function
getEntriesByType has a parameter typed as the literal 'mark', so the runtime
guard if (type !== 'mark') return [] is unreachable and should be removed;
simplify getEntriesByType by deleting the type-check branch and just
build/return the entries from this.marks (preserving returned type
CheckpointEntry[] and the payload shape { name, startTime, entryType: 'mark' })
or, if broader behavior is intended, change the parameter type to allow other
entry types and implement proper branching—refer to getEntriesByType,
CheckpointEntry, and this.marks to locate the code.
src/cli/__tests__/userFacingErrorMessages.test.ts (2)

9-43: ⚡ Quick win

These tests are tautological and won’t catch real regressions.

On Line 11, Line 21, Line 30, and Line 38, the message under test is hardcoded in the test itself. That means the assertions can still pass even if the actual production message changes incorrectly. Please source messages from the runtime path (or a shared formatter) so the tests validate real behavior.

🤖 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/cli/__tests__/userFacingErrorMessages.test.ts` around lines 9 - 43, The
tests currently hardcode expected strings, making them tautological; replace the
inline message literals with the actual formatter/exports used at runtime (e.g.
use the module functions/constants that generate messages such as
formatBudgetExceededMessage, formatMaxTurnsMessage,
formatStructuredOutputRetryMessage, and formatQueryEngineBudgetError) so each
test calls the real message producer and asserts on its output (still checking
for the same substrings like 'Exceeded USD budget', '--max-budget-usd', 'new
session', 'Simplify your schema', etc.). Ensure each test imports and invokes
the corresponding runtime message function rather than using a hardcoded
template.

47-58: ⚡ Quick win

Onboarding/trust copy tests have the same false-positive risk.

On Line 48 and Line 55, the tests assert literals declared in the test file instead of rendered output from the components, so they won’t detect component copy drift. Consider rendering the component and asserting on its text output.

🤖 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/cli/__tests__/userFacingErrorMessages.test.ts` around lines 47 - 58, Both
tests ("security heading uses friendly tone" and "trust dialog copy is concise")
assert against literals in the test file rather than the rendered component
output, making them blind to UI copy changes; update each test to render the
actual component (use render from `@testing-library/react` or the project's render
helper) and assert on the rendered text (e.g., use getByText or queryByText /
screen.getByText) instead of the local constants; locate the two tests in
userFacingErrorMessages.test.ts and replace the heading/body literal assertions
with assertions against the rendered component instances (render(...) and
expect(...).toContain(...) or expect(...).not.toContain(...)) so the tests catch
copy drift.
src/query.ts (1)

497-504: ⚡ Quick win

Remove redundant type cast when deleting toolUseResult

toolUseResult is already part of the Message type definition. After the type narrowing checks (msg.type === 'user' and 'toolUseResult' in msg), the forced cast is unnecessary. Simplify to:

delete msg.toolUseResult
🤖 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/query.ts` around lines 497 - 504, The deletion uses an unnecessary forced
cast for msg; after the narrowing checks on messagesForQuery (msg.type ===
'user' and 'toolUseResult' in msg) you can remove the cast and delete the
property directly from msg — update the block handling messagesForQuery to
remove the (msg as Message & { toolUseResult?: unknown }) cast and perform the
delete on msg.toolUseResult instead, referring to the message variable msg, the
messagesForQuery loop, and the toolUseResult property.
src/services/api/promptCacheBreakDetection.ts (1)

322-322: ⚡ Quick win

Eager buildDiffableContent runs on every request, even without a cache break.

Previously this content was lazily built only when checkResponseForCacheBreak detected an actual break and needed to write the diff file. With this refactor, every call to recordPromptState (i.e. every API request) now serializes the full system prompt + all tool schemas into a ~300KB+ string (per the comment on Line 104), regardless of whether a break ever occurs. Cache breaks are the rare case, so this is wasted CPU and allocation pressure on the hot path.

Consider keeping the eager string only for the previous turn (so the diff is available without re-serializing stale state) but skipping the rebuild on Line 424 when none of the change flags fired, or restoring the lazy closure shape if the GC motivation behind the change wasn't about this field specifically.

Also applies to: 424-424

🤖 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/promptCacheBreakDetection.ts` at line 322, The current
refactor eagerly calls buildDiffableContent for every recordPromptState
invocation, causing heavy serialization even when no cache break occurs; change
recordPromptState to avoid rebuilding the large diff string unless
checkResponseForCacheBreak indicates a break (or one of the change flags is
set). Specifically, preserve/build the eager diffable content only for the
previous turn (so it's available if a break occurs) and skip invoking
buildDiffableContent when none of the change flags fired, or restore the
previous lazy closure shape used by buildDiffableContent; update the logic
around buildDiffableContent, recordPromptState, and checkResponseForCacheBreak
to compute the large string only on-demand when a cache-break write is required.
src/services/api/claude.ts (2)

1619-1621: ⚡ Quick win

null! non-null assertions silently disable type safety on these references.

messagesForAPI = null! etc. set the variables to null at runtime while keeping their non-null types. Any future maintainer who adds a downstream read of messagesForAPI, system, or allTools after this boundary will compile cleanly and crash with an NPE during streaming. Since the goal is purely GC release, consider one of:

  • Widening the declaration once: let messagesForAPI: MessageParam[] | null = ... so subsequent reads are forced through a null check.
  • Scoping the originals into a block and letting them fall out of scope after building frozen*, instead of nulling them.

Either keeps the GC benefit while preserving type safety.

🤖 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/claude.ts` around lines 1619 - 1621, The code uses non-null
assertion (`null!`) on messagesForAPI, system, and allTools which silences TS
type checks and can cause runtime NPEs; change their declarations to allow null
(e.g., let messagesForAPI: MessageParam[] | null = ...; let system: SystemType |
null = ...; let allTools: ToolType[] | null = ...) and then set them to null
when you want GC release, or alternatively move the mutable variables into a
narrower block so you build the immutable frozen* values (e.g.,
frozenMessages/system/tools) and let the originals fall out of scope—ensure
downstream reads of messagesForAPI, system, or allTools are guarded by null
checks or use the frozen* versions.

1608-1609: ⚡ Quick win

Remove or replace cloneDeep of system and allTools with shallow array copies.

The cloneDeep adds non-trivial per-request overhead (especially for tool schemas in MCP-heavy setups) to achieve isolation that isn't necessary: recordPromptState (Line 1527) and other pre-boundary calls use these arrays only synchronously to compute hashes and diffs, then release all references. Once the originals are nulled at Lines 1619-1621, the closure holds the sole references—there is no external holder to isolate from.

If the intent is to enable GC of intermediate allocations (e.g., transient schema builders), clarify that in the comment. Otherwise, consider shallow-copying only the outer arrays ([...system] and [...allTools]) if structural independence is truly needed; this preserves any GC intent without the deep-clone cost.

🤖 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/claude.ts` around lines 1608 - 1609, Replace the expensive
deep clones for frozenSystem and frozenTools (currently using cloneDeep) with
shallow copies of the outer arrays (e.g., [...system] and [...allTools]) because
the arrays are only used synchronously by recordPromptState and released before
the boundary; this preserves any isolation/GC intent at far lower cost. Update
the comment near the creation of frozenSystem/frozenTools to state that only a
shallow copy is required since originals are nulled before the closure escapes,
and remove cloneDeep references to eliminate per-request overhead.
🤖 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/commands/resume/__tests__/resume.test.ts`:
- Around line 8-55: The tests are asserting hardcoded strings instead of the
real runtime text; update each test to import and assert against the actual
sources: render the ModelPicker (or import its exported subtitle constant) and
assert its subtitle contains 'effort', '1M context', and is under 120 chars;
replace the hardcoded resume messages with the exported message
builders/constants (e.g., resumeNotFoundMessage, resumeMultipleMatchesMessage)
and assert their contents; likewise import the cost subscriber messages (e.g.,
costOverageMessage, costSubscriptionMessage) from the production module and
assert their substrings/lengths instead of using inline literals. Ensure you
reference ModelPicker and the exported message names so the tests fail if
production copy changes.

In `@src/components/__tests__/compactMessages.test.ts`:
- Around line 8-54: Replace local literal strings in compactMessages.test.ts
with the actual exported compaction message constants: import the exported
constants (e.g. NOT_ENOUGH_MESSAGES, PROMPT_TOO_LONG, INCOMPLETE_RESPONSE,
USER_ABORT, AUTO_COMPACT_TITLE, MANUAL_COMPACT_TITLE, EXPAND_HINT) and assert
against those symbols instead of the inline strings so tests fail when
production text changes; update each test case (the ones checking "not enough
messages", "prompt too long", "incomplete response", "user abort", and the three
summary title/hint tests) to reference the appropriate exported constant in
their expect() calls.

In `@src/components/HelpV2/__tests__/General.test.ts`:
- Around line 8-74: The tests in General.test.ts assert against hard-coded
strings instead of real UI/strings; update each test to import or render the
actual source instead of using literals—e.g., render the PermissionDialog/HelpV2
components (use render from `@testing-library/react`) and query for the footer
text instead of the local footer variable, or import the shared label/constants
(permissionFooterText, allowEditsLabel, acceptOnceLabel, rejectLabel,
gettingStartedSteps) used by the components and assert against those values;
replace direct literal checks (like footer, label, step1) with assertions
against the rendered DOM or imported constants so changes to production copy
will fail tests.

In `@src/services/api/claude.ts`:
- Around line 1604-1605: Replace the two "as any" casts on consumedCacheEdits
and consumedPinnedEdits with proper typed assertions or narrowing: inspect the
return types of consumePendingCacheEdits and getPinnedCacheEdits and either
adjust those functions' return types to match addCacheBreakpoints' parameters
(CachedMCEditsBlock | null and CachedMCPinnedEdits[]), or use a safe double-cast
like "as unknown as CachedMCEditsBlock | null" and "as unknown as
CachedMCPinnedEdits[]" so the values passed to addCacheBreakpoints are correctly
typed; update the declarations for consumedCacheEdits/consumedPinnedEdits
accordingly and remove the "as any" usages.
- Around line 2887-2893: The code passes frozenMessages (type MessageParam[])
into getAssistantMessageFromError by force-casting to (UserMessage |
AssistantMessage)[], which hides a runtime mismatch:
logToolUseToolResultMismatch expects messages with .message.role and
.message.content but MessageParam items only have top-level role/content; to
fix, stop the double-cast and instead pass the original pre-cache-breakpoint
array (the (UserMessage | AssistantMessage)[] produced before
addCacheBreakpoints) into getAssistantMessageFromError (or else modify
getAssistantMessageFromError/logToolUseToolResultMismatch to handle MessageParam
shape), locating references to frozenMessages, addCacheBreakpoints,
getAssistantMessageFromError, and logToolUseToolResultMismatch to implement the
correct value or adjust the consumer to read role/content directly.

In `@src/utils/dxt/helpers.ts`:
- Around line 22-25: The current composition of the "Invalid manifest" detail
iterates Object.entries(errors.fieldErrors) and interpolates errs directly,
which can produce "field: undefined" or empty entries; update the logic that
builds the message (the mapping over Object.entries(errors.fieldErrors)) to
first filter entries where errs is an array with length > 0 (or otherwise
truthy) and only then map to the "field: joinedErrors" string, ensuring
undefined or empty err arrays are skipped from the final composed message.

In `@src/utils/mcpInstructionsDelta.ts`:
- Around line 68-70: The code casts msg.attachment to McpInstructionsDelta and
iterates delta.addedNames/removedNames without validation; add a type guard
(e.g., isMcpInstructionsDelta(msg.attachment)) or runtime checks before using
them: ensure msg.attachment exists and that addedNames and removedNames are
arrays (Array.isArray) and fallback to empty arrays if not, then iterate those
safe arrays and update the announced set; replace the forced cast of const delta
= msg.attachment! as unknown as McpInstructionsDelta with the guarded access and
use the validated variable (delta or safeAdded/safeRemoved) in the for...of
loops.

In `@src/utils/profilerBase.ts`:
- Around line 36-38: The mark(name: string) method currently overwrites existing
timestamps instead of appending like Performance.mark; add a concise JSDoc on
the mark() method (or top-level JSDoc in profilerBase.ts) stating that mark()
replaces any previous mark with the same name, that startQueryProfile() clears
marks at the start of each query session so duplicates are not expected within a
session, and warn callers that repeated marks across sessions will overwrite
prior timestamps rather than append; reference the mark(), now(), and
startQueryProfile() symbols in the comment so future maintainers understand the
session-based isolation and overwrite semantics.

---

Nitpick comments:
In `@src/cli/__tests__/userFacingErrorMessages.test.ts`:
- Around line 9-43: The tests currently hardcode expected strings, making them
tautological; replace the inline message literals with the actual
formatter/exports used at runtime (e.g. use the module functions/constants that
generate messages such as formatBudgetExceededMessage, formatMaxTurnsMessage,
formatStructuredOutputRetryMessage, and formatQueryEngineBudgetError) so each
test calls the real message producer and asserts on its output (still checking
for the same substrings like 'Exceeded USD budget', '--max-budget-usd', 'new
session', 'Simplify your schema', etc.). Ensure each test imports and invokes
the corresponding runtime message function rather than using a hardcoded
template.
- Around line 47-58: Both tests ("security heading uses friendly tone" and
"trust dialog copy is concise") assert against literals in the test file rather
than the rendered component output, making them blind to UI copy changes; update
each test to render the actual component (use render from `@testing-library/react`
or the project's render helper) and assert on the rendered text (e.g., use
getByText or queryByText / screen.getByText) instead of the local constants;
locate the two tests in userFacingErrorMessages.test.ts and replace the
heading/body literal assertions with assertions against the rendered component
instances (render(...) and expect(...).toContain(...) or
expect(...).not.toContain(...)) so the tests catch copy drift.

In `@src/query.ts`:
- Around line 497-504: The deletion uses an unnecessary forced cast for msg;
after the narrowing checks on messagesForQuery (msg.type === 'user' and
'toolUseResult' in msg) you can remove the cast and delete the property directly
from msg — update the block handling messagesForQuery to remove the (msg as
Message & { toolUseResult?: unknown }) cast and perform the delete on
msg.toolUseResult instead, referring to the message variable msg, the
messagesForQuery loop, and the toolUseResult property.

In `@src/services/api/claude.ts`:
- Around line 1619-1621: The code uses non-null assertion (`null!`) on
messagesForAPI, system, and allTools which silences TS type checks and can cause
runtime NPEs; change their declarations to allow null (e.g., let messagesForAPI:
MessageParam[] | null = ...; let system: SystemType | null = ...; let allTools:
ToolType[] | null = ...) and then set them to null when you want GC release, or
alternatively move the mutable variables into a narrower block so you build the
immutable frozen* values (e.g., frozenMessages/system/tools) and let the
originals fall out of scope—ensure downstream reads of messagesForAPI, system,
or allTools are guarded by null checks or use the frozen* versions.
- Around line 1608-1609: Replace the expensive deep clones for frozenSystem and
frozenTools (currently using cloneDeep) with shallow copies of the outer arrays
(e.g., [...system] and [...allTools]) because the arrays are only used
synchronously by recordPromptState and released before the boundary; this
preserves any isolation/GC intent at far lower cost. Update the comment near the
creation of frozenSystem/frozenTools to state that only a shallow copy is
required since originals are nulled before the closure escapes, and remove
cloneDeep references to eliminate per-request overhead.

In `@src/services/api/promptCacheBreakDetection.ts`:
- Line 322: The current refactor eagerly calls buildDiffableContent for every
recordPromptState invocation, causing heavy serialization even when no cache
break occurs; change recordPromptState to avoid rebuilding the large diff string
unless checkResponseForCacheBreak indicates a break (or one of the change flags
is set). Specifically, preserve/build the eager diffable content only for the
previous turn (so it's available if a break occurs) and skip invoking
buildDiffableContent when none of the change flags fired, or restore the
previous lazy closure shape used by buildDiffableContent; update the logic
around buildDiffableContent, recordPromptState, and checkResponseForCacheBreak
to compute the large string only on-demand when a cache-break write is required.

In `@src/utils/profilerBase.ts`:
- Around line 40-47: The function getEntriesByType has a parameter typed as the
literal 'mark', so the runtime guard if (type !== 'mark') return [] is
unreachable and should be removed; simplify getEntriesByType by deleting the
type-check branch and just build/return the entries from this.marks (preserving
returned type CheckpointEntry[] and the payload shape { name, startTime,
entryType: 'mark' }) or, if broader behavior is intended, change the parameter
type to allow other entry types and implement proper branching—refer to
getEntriesByType, CheckpointEntry, and this.marks to locate the code.

In `@src/utils/sessionTitle.ts`:
- Around line 39-42: The condition uses repeated double-casts on msg to access
origin.kind; add a runtime type guard (e.g., isMsgWithOrigin(msg): msg is {
origin?: { kind?: string } }) in src/utils/sessionTitle.ts, use that guard in
the conditional to narrow msg once, and then read msg.origin.kind instead of
casting twice; replace the two occurrences of (msg as unknown as { origin... })
with a single guard call so subsequent checks (like comparing kind !== 'human')
are safely narrowed.
- Around line 119-121: Change the forced cast of result.message.content before
passing to extractTextContent into a type-guard branch: check if
result.message.content is a string and use it directly, else if
Array.isArray(result.message.content) call extractTextContent with that array,
and handle null/undefined or unexpected shapes (fallback to empty string or
log/error). Update the code around the extractTextContent call (the variable
"text" and references to result.message.content) to use these guards rather than
the cast so runtime shape mismatches are avoided.
🪄 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

Run ID: 649f0582-13ae-4744-9296-3074f1c1be73

📥 Commits

Reviewing files that changed from the base of the PR and between f8a289b and 18d6656.

📒 Files selected for processing (62)
  • packages/@ant/ink/src/components/App.tsx
  • packages/@ant/ink/src/components/ErrorOverview.tsx
  • progress.md
  • src/QueryEngine.ts
  • src/cli/__tests__/userFacingErrorMessages.test.ts
  • src/cli/bg/engines/tmux.ts
  • src/cli/print.ts
  • src/cli/update.ts
  • src/commands/resume/__tests__/resume.test.ts
  • src/commands/resume/resume.tsx
  • src/components/CompactSummary.tsx
  • src/components/HelpV2/General.tsx
  • src/components/HelpV2/__tests__/General.test.ts
  • src/components/ModelPicker.tsx
  • src/components/Onboarding.tsx
  • src/components/TrustDialog/TrustDialog.tsx
  • src/components/__tests__/compactMessages.test.ts
  • src/components/design-system/Divider.tsx
  • src/components/design-system/FuzzyPicker.tsx
  • src/components/design-system/LoadingState.tsx
  • src/components/design-system/Pane.tsx
  • src/components/design-system/ProgressBar.tsx
  • src/components/design-system/Ratchet.tsx
  • src/components/design-system/StatusIcon.tsx
  • src/components/design-system/Tabs.tsx
  • src/components/design-system/ThemeProvider.tsx
  • src/components/design-system/ThemedBox.tsx
  • src/components/design-system/color.ts
  • src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx
  • src/components/permissions/FilePermissionDialog/FilePermissionDialog.tsx
  • src/components/permissions/FilePermissionDialog/permissionOptions.tsx
  • src/components/tasks/src/tasks/DreamTask/DreamTask.ts
  • src/components/tasks/src/tasks/LocalWorkflowTask/LocalWorkflowTask.ts
  • src/components/tasks/src/types/utils.ts
  • src/constants/outputStyles.ts
  • src/hooks/fileSuggestions.ts
  • src/hooks/notifs/useAutoModeUnavailableNotification.ts
  • src/keybindings/match.ts
  • src/migrations/migrateAutoUpdatesToSettings.ts
  • src/query.ts
  • src/services/api/claude.ts
  • src/services/api/promptCacheBreakDetection.ts
  • src/services/compact/compact.ts
  • src/services/mcp/adapter/analytics.ts
  • src/services/mcp/adapter/auth.ts
  • src/services/mcp/adapter/featureGate.ts
  • src/services/mcp/adapter/httpConfig.ts
  • src/services/mcp/adapter/imageProcessor.ts
  • src/services/mcp/adapter/index.ts
  • src/services/mcp/adapter/logger.ts
  • src/services/mcp/adapter/proxy.ts
  • src/services/mcp/adapter/storage.ts
  • src/services/mcp/adapter/subprocessEnv.ts
  • src/utils/agentId.ts
  • src/utils/asciicast.ts
  • src/utils/computerUse/win32/ocr.ts
  • src/utils/dxt/helpers.ts
  • src/utils/mcpInstructionsDelta.ts
  • src/utils/profilerBase.ts
  • src/utils/sessionTitle.ts
  • src/utils/systemThemeWatcher.ts
  • src/utils/warningHandler.ts
💤 Files with no reviewable changes (35)
  • src/components/design-system/Ratchet.tsx
  • src/components/design-system/StatusIcon.tsx
  • src/components/design-system/Tabs.tsx
  • src/components/tasks/src/tasks/LocalWorkflowTask/LocalWorkflowTask.ts
  • src/components/design-system/Divider.tsx
  • src/utils/warningHandler.ts
  • src/services/mcp/adapter/imageProcessor.ts
  • src/components/tasks/src/tasks/DreamTask/DreamTask.ts
  • src/components/tasks/src/types/utils.ts
  • src/components/design-system/Pane.tsx
  • src/services/mcp/adapter/featureGate.ts
  • src/components/design-system/color.ts
  • src/keybindings/match.ts
  • src/components/design-system/FuzzyPicker.tsx
  • src/services/mcp/adapter/subprocessEnv.ts
  • src/components/design-system/LoadingState.tsx
  • src/services/mcp/adapter/index.ts
  • src/services/mcp/adapter/proxy.ts
  • src/services/mcp/adapter/httpConfig.ts
  • src/cli/bg/engines/tmux.ts
  • src/services/mcp/adapter/storage.ts
  • src/services/mcp/adapter/analytics.ts
  • src/cli/update.ts
  • src/components/design-system/ThemedBox.tsx
  • src/migrations/migrateAutoUpdatesToSettings.ts
  • src/utils/agentId.ts
  • src/components/design-system/ThemeProvider.tsx
  • src/services/mcp/adapter/logger.ts
  • src/services/mcp/adapter/auth.ts
  • src/utils/computerUse/win32/ocr.ts
  • src/hooks/notifs/useAutoModeUnavailableNotification.ts
  • src/utils/systemThemeWatcher.ts
  • src/components/design-system/ProgressBar.tsx
  • src/hooks/fileSuggestions.ts
  • src/utils/asciicast.ts

Comment on lines +8 to +55
describe('ModelPicker subtitle', () => {
test('subtitle mentions effort and context controls', () => {
const subtitle =
'Choose a model for this and future sessions. Use ← → to adjust effort, Space to toggle 1M context.'
expect(subtitle).toContain('effort')
expect(subtitle).toContain('1M context')
expect(subtitle).toContain('sessions')
})

test('subtitle is under 120 characters', () => {
const subtitle =
'Choose a model for this and future sessions. Use ← → to adjust effort, Space to toggle 1M context.'
expect(subtitle.length).toBeLessThan(120)
})
})

describe('Resume error messages', () => {
test('session not found suggests /resume to browse', () => {
const message =
'Session my-session was not found. Run /resume without arguments to browse all sessions.'
expect(message).toContain('not found')
expect(message).toContain('/resume')
expect(message).toContain('browse')
})

test('multiple matches suggests /resume to pick', () => {
const message =
'Found 3 sessions matching test. Run /resume to pick one from the list.'
expect(message).toContain('3 sessions')
expect(message).toContain('/resume')
expect(message).toContain('pick')
})
})

describe('Cost command subscriber messages', () => {
test('overage message mentions the key behavior', () => {
const msg =
'You are currently using your overages to power your Claude Code usage. We will automatically switch you back to your subscription rate limits when they reset'
expect(msg).toContain('overages')
expect(msg).toContain('automatically switch')
})

test('subscription message is concise', () => {
const msg =
'You are currently using your subscription to power your Claude Code usage'
expect(msg.length).toBeLessThan(100)
})
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Current assertions don’t validate runtime resume/model-picker copy

These checks only assert hardcoded strings created inside the tests (Line 10-Line 53). They won’t fail if the real /resume and ModelPicker copy changes.

Please assert against actual outputs from the production path (component render or exported message constructors/constants).

🤖 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/commands/resume/__tests__/resume.test.ts` around lines 8 - 55, The tests
are asserting hardcoded strings instead of the real runtime text; update each
test to import and assert against the actual sources: render the ModelPicker (or
import its exported subtitle constant) and assert its subtitle contains
'effort', '1M context', and is under 120 chars; replace the hardcoded resume
messages with the exported message builders/constants (e.g.,
resumeNotFoundMessage, resumeMultipleMatchesMessage) and assert their contents;
likewise import the cost subscriber messages (e.g., costOverageMessage,
costSubscriptionMessage) from the production module and assert their
substrings/lengths instead of using inline literals. Ensure you reference
ModelPicker and the exported message names so the tests fail if production copy
changes.

Comment on lines +8 to +54
describe('Compaction error messages', () => {
test('not enough messages includes guidance', () => {
const msg =
'Not enough messages to compact. Send a few more messages first, then try again.'
expect(msg).toContain('Not enough messages')
expect(msg).toContain('try again')
})

test('prompt too long suggests actions', () => {
const msg =
'Conversation too long to summarize. Try /compact to manually clear conversation history, or start a new session with /clear.'
expect(msg).toContain('/compact')
expect(msg).toContain('/clear')
expect(msg).toContain('too long')
})

test('incomplete response mentions network', () => {
const msg =
'Compaction interrupted · This may be due to network issues — please try again.'
expect(msg).toContain('interrupted')
expect(msg).toContain('try again')
})

test('user abort is clear', () => {
const msg = 'API Error: Request was aborted.'
expect(msg).toContain('aborted')
})
})

describe('CompactSummary display text', () => {
test('auto-compact title explains what happened', () => {
const title = 'Conversation summarized to free up context'
expect(title).toContain('summarized')
expect(title).toContain('context')
expect(title).not.toContain('Compact summary')
})

test('manual compact title mentions message count', () => {
const line1 = 'Summarized conversation'
expect(line1).toContain('Summarized')
})

test('expand hint says "view summary" not "expand"', () => {
const hint = 'view summary'
expect(hint).toContain('summary')
})
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Tests should assert production strings, not local literals

Most checks here assert values defined in the same test scope, so they won’t fail when source copy changes.

At minimum, import and assert exported compaction error constants directly from src/services/compact/compact.ts.

Suggested fix (bind tests to source constants)
 import { describe, expect, test } from 'bun:test'
+import {
+  ERROR_MESSAGE_INCOMPLETE_RESPONSE,
+  ERROR_MESSAGE_NOT_ENOUGH_MESSAGES,
+  ERROR_MESSAGE_PROMPT_TOO_LONG,
+  ERROR_MESSAGE_USER_ABORT,
+} from 'src/services/compact/compact.js'

 describe('Compaction error messages', () => {
   test('not enough messages includes guidance', () => {
-    const msg =
-      'Not enough messages to compact. Send a few more messages first, then try again.'
+    const msg = ERROR_MESSAGE_NOT_ENOUGH_MESSAGES
     expect(msg).toContain('Not enough messages')
     expect(msg).toContain('try again')
   })

   test('prompt too long suggests actions', () => {
-    const msg =
-      'Conversation too long to summarize. Try /compact to manually clear conversation history, or start a new session with /clear.'
+    const msg = ERROR_MESSAGE_PROMPT_TOO_LONG
     expect(msg).toContain('/compact')
     expect(msg).toContain('/clear')
     expect(msg).toContain('too long')
   })

   test('incomplete response mentions network', () => {
-    const msg =
-      'Compaction interrupted · This may be due to network issues — please try again.'
+    const msg = ERROR_MESSAGE_INCOMPLETE_RESPONSE
     expect(msg).toContain('interrupted')
     expect(msg).toContain('try again')
   })

   test('user abort is clear', () => {
-    const msg = 'API Error: Request was aborted.'
+    const msg = ERROR_MESSAGE_USER_ABORT
     expect(msg).toContain('aborted')
   })
 })
📝 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.

Suggested change
describe('Compaction error messages', () => {
test('not enough messages includes guidance', () => {
const msg =
'Not enough messages to compact. Send a few more messages first, then try again.'
expect(msg).toContain('Not enough messages')
expect(msg).toContain('try again')
})
test('prompt too long suggests actions', () => {
const msg =
'Conversation too long to summarize. Try /compact to manually clear conversation history, or start a new session with /clear.'
expect(msg).toContain('/compact')
expect(msg).toContain('/clear')
expect(msg).toContain('too long')
})
test('incomplete response mentions network', () => {
const msg =
'Compaction interrupted · This may be due to network issues — please try again.'
expect(msg).toContain('interrupted')
expect(msg).toContain('try again')
})
test('user abort is clear', () => {
const msg = 'API Error: Request was aborted.'
expect(msg).toContain('aborted')
})
})
describe('CompactSummary display text', () => {
test('auto-compact title explains what happened', () => {
const title = 'Conversation summarized to free up context'
expect(title).toContain('summarized')
expect(title).toContain('context')
expect(title).not.toContain('Compact summary')
})
test('manual compact title mentions message count', () => {
const line1 = 'Summarized conversation'
expect(line1).toContain('Summarized')
})
test('expand hint says "view summary" not "expand"', () => {
const hint = 'view summary'
expect(hint).toContain('summary')
})
})
import { describe, expect, test } from 'bun:test'
import {
ERROR_MESSAGE_INCOMPLETE_RESPONSE,
ERROR_MESSAGE_NOT_ENOUGH_MESSAGES,
ERROR_MESSAGE_PROMPT_TOO_LONG,
ERROR_MESSAGE_USER_ABORT,
} from 'src/services/compact/compact.js'
describe('Compaction error messages', () => {
test('not enough messages includes guidance', () => {
const msg = ERROR_MESSAGE_NOT_ENOUGH_MESSAGES
expect(msg).toContain('Not enough messages')
expect(msg).toContain('try again')
})
test('prompt too long suggests actions', () => {
const msg = ERROR_MESSAGE_PROMPT_TOO_LONG
expect(msg).toContain('/compact')
expect(msg).toContain('/clear')
expect(msg).toContain('too long')
})
test('incomplete response mentions network', () => {
const msg = ERROR_MESSAGE_INCOMPLETE_RESPONSE
expect(msg).toContain('interrupted')
expect(msg).toContain('try again')
})
test('user abort is clear', () => {
const msg = ERROR_MESSAGE_USER_ABORT
expect(msg).toContain('aborted')
})
})
describe('CompactSummary display text', () => {
test('auto-compact title explains what happened', () => {
const title = 'Conversation summarized to free up context'
expect(title).toContain('summarized')
expect(title).toContain('context')
expect(title).not.toContain('Compact summary')
})
test('manual compact title mentions message count', () => {
const line1 = 'Summarized conversation'
expect(line1).toContain('Summarized')
})
test('expand hint says "view summary" not "expand"', () => {
const hint = 'view summary'
expect(hint).toContain('summary')
})
})
🤖 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/components/__tests__/compactMessages.test.ts` around lines 8 - 54,
Replace local literal strings in compactMessages.test.ts with the actual
exported compaction message constants: import the exported constants (e.g.
NOT_ENOUGH_MESSAGES, PROMPT_TOO_LONG, INCOMPLETE_RESPONSE, USER_ABORT,
AUTO_COMPACT_TITLE, MANUAL_COMPACT_TITLE, EXPAND_HINT) and assert against those
symbols instead of the inline strings so tests fail when production text
changes; update each test case (the ones checking "not enough messages", "prompt
too long", "incomplete response", "user abort", and the three summary title/hint
tests) to reference the appropriate exported constant in their expect() calls.

Comment on lines +8 to +74
describe('Permission dialog footer hints', () => {
test('bash permission footer says "reject" instead of "cancel"', () => {
const footer = 'Esc to reject'
expect(footer).toContain('reject')
expect(footer).not.toContain('cancel')
})

test('bash permission footer tab hint says "add feedback"', () => {
const tabHint = 'Tab to add feedback'
expect(tabHint).toContain('feedback')
expect(tabHint).not.toContain('amend')
})

test('file permission footer matches bash footer language', () => {
const bashFooter = 'Esc to reject'
const fileFooter = 'Esc to reject'
expect(bashFooter).toBe(fileFooter)
})
})

describe('Permission option labels', () => {
test('.claude/ folder option is under 60 chars', () => {
const label = 'Yes, allow edits to .claude/ config for this session'
expect(label.length).toBeLessThan(60)
expect(label).toContain('.claude/')
})

test('accept-once option has simple label', () => {
const label = 'Yes'
expect(label).toBe('Yes')
})

test('reject option has simple label', () => {
const label = 'No'
expect(label).toBe('No')
})
})

describe('Help General page getting started guide', () => {
test('step 1 mentions exploring code', () => {
const step1 =
'Ask a question or describe a task — Claude will explore your code and respond.'
expect(step1).toContain('explore')
expect(step1).toContain('question')
})

test('step 2 mentions reviewing actions', () => {
const step2 =
'When Claude wants to edit files or run commands, you review and approve each action.'
expect(step2).toContain('review')
expect(step2).toContain('approve')
})

test('step 3 mentions key commands', () => {
const step3 = '/commit'
const step3b = '/help'
const step3c = '?'
expect(step3).toBe('/commit')
expect(step3b).toBe('/help')
expect(step3c).toBe('?')
})

test('heading says "Getting started"', () => {
const heading = 'Getting started'
expect(heading).toBe('Getting started')
})
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

These tests are self-referential and won’t catch real copy regressions

The assertions only validate literals declared inside the test file (for example, Line 10-Line 73). Since no production source is read/rendered, these tests still pass if actual component text changes.

Please bind assertions to real outputs (e.g., render the target components or import the source label builders/constants used by them).

🤖 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/components/HelpV2/__tests__/General.test.ts` around lines 8 - 74, The
tests in General.test.ts assert against hard-coded strings instead of real
UI/strings; update each test to import or render the actual source instead of
using literals—e.g., render the PermissionDialog/HelpV2 components (use render
from `@testing-library/react`) and query for the footer text instead of the local
footer variable, or import the shared label/constants (permissionFooterText,
allowEditsLabel, acceptOnceLabel, rejectLabel, gettingStartedSteps) used by the
components and assert against those values; replace direct literal checks (like
footer, label, step1) with assertions against the rendered DOM or imported
constants so changes to production copy will fail tests.

Comment on lines +1604 to +1605
consumedCacheEdits as any,
consumedPinnedEdits as any,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid as any in production code.

Both casts violate the project rule prohibiting as any outside of test files. Given that addCacheBreakpoints has explicit CachedMCEditsBlock | null / CachedMCPinnedEdits[] parameter types (Lines 3249-3250) and consumePendingCacheEdits/getPinnedCacheEdits return values that flow into them, the casts are likely unnecessary or should be as unknown as <SpecificType>.

♻️ Proposed fix
-    consumedCacheEdits as any,
-    consumedPinnedEdits as any,
+    consumedCacheEdits,
+    consumedPinnedEdits,

If the underlying return types genuinely don't line up, narrow them with proper interfaces or as unknown as CachedMCEditsBlock | null / as unknown as CachedMCPinnedEdits[] instead.

As per coding guidelines: "Prohibit as any type assertions in production code; in test files, as any is permitted for mock data. Use as unknown as SpecificType double assertion or interface supplementation when type mismatches occur".

🤖 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/claude.ts` around lines 1604 - 1605, Replace the two "as
any" casts on consumedCacheEdits and consumedPinnedEdits with proper typed
assertions or narrowing: inspect the return types of consumePendingCacheEdits
and getPinnedCacheEdits and either adjust those functions' return types to match
addCacheBreakpoints' parameters (CachedMCEditsBlock | null and
CachedMCPinnedEdits[]), or use a safe double-cast like "as unknown as
CachedMCEditsBlock | null" and "as unknown as CachedMCPinnedEdits[]" so the
values passed to addCacheBreakpoints are correctly typed; update the
declarations for consumedCacheEdits/consumedPinnedEdits accordingly and remove
the "as any" usages.

Comment on lines 2887 to 2893
yield getAssistantMessageFromError(error, errorModel, {
messages,
messagesForAPI,
messagesForAPI: frozenMessages as unknown as (
| UserMessage
| AssistantMessage
)[],
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm what fields getAssistantMessageFromError actually consumes from messagesForAPI.
ast-grep --pattern $'function getAssistantMessageFromError($$$) {
  $$$
}'
rg -nP -C5 --type=ts '\bgetAssistantMessageFromError\b' src/services/api/errors.ts
rg -nP -C5 --type=ts 'messagesForAPI' src/services/api/errors.ts

Repository: claude-code-best/claude-code

Length of output: 4093


🏁 Script executed:

# Search for type definitions of MessageParam, UserMessage, AssistantMessage
rg -n "type\s+MessageParam|interface\s+MessageParam" --type=ts
rg -n "type\s+UserMessage|interface\s+UserMessage" --type=ts
rg -n "type\s+AssistantMessage|interface\s+AssistantMessage" --type=ts
rg -n "function\s+addCacheBreakpoints" --type=ts

Repository: claude-code-best/claude-code

Length of output: 1554


🏁 Script executed:

# Get context around lines 2887-2893 in claude.ts
sed -n '2880,2900p' src/services/api/claude.ts

Repository: claude-code-best/claude-code

Length of output: 647


🏁 Script executed:

# Also check lines mentioned in "Also applies to" comment
sed -n '2948,2960p' src/services/api/claude.ts
sed -n '3018,3030p' src/services/api/claude.ts

Repository: claude-code-best/claude-code

Length of output: 951


🏁 Script executed:

# Check imports in claude.ts for UserMessage, AssistantMessage, MessageParam
head -100 src/services/api/claude.ts | rg -n "import|from" --type=ts
rg -n "^import.*UserMessage|AssistantMessage|MessageParam" src/services/api/claude.ts | head -20

Repository: claude-code-best/claude-code

Length of output: 2585


🏁 Script executed:

# Get the addCacheBreakpoints function signature and return type
sed -n '3244,3270p' src/services/api/claude.ts

Repository: claude-code-best/claude-code

Length of output: 1391


🏁 Script executed:

# Find where frozenMessages is assigned before line 2887
sed -n '2850,2890p' src/services/api/claude.ts

Repository: claude-code-best/claude-code

Length of output: 1356


🏁 Script executed:

# Check the actual type definitions in src/types/message.js
cat src/types/message.js

Repository: claude-code-best/claude-code

Length of output: 125


🏁 Script executed:

# Also check if there's detailed info about UserMessage structure
rg -n "UserMessage\|AssistantMessage" src/types/message.js -A 5

Repository: claude-code-best/claude-code

Length of output: 133


🏁 Script executed:

# Find message type files
fd -n "message" --type=f src/types/

Repository: claude-code-best/claude-code

Length of output: 303


🏁 Script executed:

# Search for UserMessage and AssistantMessage type definitions in src/
rg -n "^export type UserMessage|^export interface UserMessage" src/ --type=ts -A 3

Repository: claude-code-best/claude-code

Length of output: 54


🏁 Script executed:

# Check the import statement to see which file provides UserMessage/AssistantMessage
sed -n '45,52p' src/services/api/claude.ts

Repository: claude-code-best/claude-code

Length of output: 222


🏁 Script executed:

# Find the message.js or message.ts file in types/
find src -name "message.ts" -o -name "message.js" | head -10

Repository: claude-code-best/claude-code

Length of output: 93


🏁 Script executed:

# Get the full path and content
cat src/types/message.ts | head -150

Repository: claude-code-best/claude-code

Length of output: 3150


🏁 Script executed:

# Search for UserMessage type definition in `@ant/model-provider`
rg "export type UserMessage|export interface UserMessage" --type=ts | grep -v node_modules | head -5

Repository: claude-code-best/claude-code

Length of output: 539


🏁 Script executed:

# Look at where frozenMessages is created and what it actually contains
# Find all occurrences of frozenMessages assignment in claude.ts
rg -n "frozenMessages\s*=" src/services/api/claude.ts

Repository: claude-code-best/claude-code

Length of output: 123


🏁 Script executed:

# Check the context where frozenMessages is first assigned
sed -n '2800,2850p' src/services/api/claude.ts

Repository: claude-code-best/claude-code

Length of output: 1775


🏁 Script executed:

# Get the line where frozenMessages is assigned (line 1597) with context
sed -n '1595,1610p' src/services/api/claude.ts

Repository: claude-code-best/claude-code

Length of output: 613


🏁 Script executed:

# Now let's check what `@ant/model-provider` exports for UserMessage and AssistantMessage
# Look for these types in packages/@ant/model-provider
find packages/@ant/model-provider -name "*.ts" -type f -exec grep -l "export.*UserMessage\|export.*AssistantMessage" {} \;

Repository: claude-code-best/claude-code

Length of output: 122


🏁 Script executed:

# Get the UserMessage and AssistantMessage type definitions
sed -n '1,300p' packages/@ant/model-provider/src/types/message.ts | head -200

Repository: claude-code-best/claude-code

Length of output: 4780


🏁 Script executed:

# Also check what MessageParam is exactly (from Anthropic SDK)
rg -n "MessageParam\|BetaMessageParam" src/services/api/claude.ts | head -10

Repository: claude-code-best/claude-code

Length of output: 54


🏁 Script executed:

# Check what MessageParam actually is from Anthropic SDK
# Let's see the import and verify it's the wire format
rg -n "BetaMessageParam" src/services/api/claude.ts -A 2

Repository: claude-code-best/claude-code

Length of output: 257


🏁 Script executed:

# Now verify if logToolUseToolResultMismatch is actually called with frozenMessages
# Check all usages of messagesForAPI parameter in getAssistantMessageFromError
sed -n '425,700p' src/services/api/errors.ts | grep -n "messagesForAPI\|msg.message"

Repository: claude-code-best/claude-code

Length of output: 228


🏁 Script executed:

# Let's also verify: does frozenMessages actually get used anywhere else after the cast?
rg -n "frozenMessages" src/services/api/claude.ts

Repository: claude-code-best/claude-code

Length of output: 438


🏁 Script executed:

# Verify the structure of BetaMessageParam - is it just {role, content} or does it have .message?
# Search in `@anthropic-ai/sdk` types or check if there's any conversion
rg -n "BetaMessageParam\|MessageParam" packages/@ant/model-provider/src/types/message.ts

Repository: claude-code-best/claude-code

Length of output: 54


🏁 Script executed:

# Final check: look at userMessageToMessageParam and assistantMessageToMessageParam 
# to understand what structure is being converted
sed -n '582,640p' src/services/api/claude.ts

Repository: claude-code-best/claude-code

Length of output: 1834


🏁 Script executed:

# Verify one more time: check line 1790 where frozenMessages is used with convertMessagesToLangfuse
# What does this function expect?
rg -n "function convertMessagesToLangfuse|export.*convertMessagesToLangfuse" --type=ts

Repository: claude-code-best/claude-code

Length of output: 152


🏁 Script executed:

# Check convertMessagesToLangfuse signature
sed -n '226,250p' src/services/langfuse/convert.ts

Repository: claude-code-best/claude-code

Length of output: 996


🏁 Script executed:

# One more critical check: verify what happens when logToolUseToolResultMismatch tries to access msg.message
# on a MessageParam object. Let me trace through the actual call path more carefully.
# At lines 2889-2893, frozenMessages is passed. What exactly does getAssistantMessageFromError do with it?
sed -n '670,690p' src/services/api/errors.ts

Repository: claude-code-best/claude-code

Length of output: 853


frozenMessages is MessageParam[] (wire format), not (UserMessage | AssistantMessage)[] — the double-cast hides a runtime error.

addCacheBreakpoints converts the higher-level (UserMessage | AssistantMessage)[] to API wire format MessageParam[] with just {role, content}. When this frozenMessages is passed to getAssistantMessageFromError as messagesForAPI, the subsequent call to logToolUseToolResultMismatch (at line 679–682) will attempt to access msg.message!.content and msg.message!.role on items that only have role and content properties at the top level. The .message property does not exist on MessageParam objects.

This will cause runtime errors when the tool_use/tool_result mismatch error path is triggered in production, despite the type check passing due to the as unknown as cast.

Either preserve a reference to the pre-cache-breakpoint (UserMessage | AssistantMessage)[] to pass instead, or audit the consumer code to confirm it only accesses role and content fields directly.

🤖 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/claude.ts` around lines 2887 - 2893, The code passes
frozenMessages (type MessageParam[]) into getAssistantMessageFromError by
force-casting to (UserMessage | AssistantMessage)[], which hides a runtime
mismatch: logToolUseToolResultMismatch expects messages with .message.role and
.message.content but MessageParam items only have top-level role/content; to
fix, stop the double-cast and instead pass the original pre-cache-breakpoint
array (the (UserMessage | AssistantMessage)[] produced before
addCacheBreakpoints) into getAssistantMessageFromError (or else modify
getAssistantMessageFromError/logToolUseToolResultMismatch to handle MessageParam
shape), locating references to frozenMessages, addCacheBreakpoints,
getAssistantMessageFromError, and logToolUseToolResultMismatch to implement the
correct value or adjust the consumer to read role/content directly.

Comment thread src/utils/dxt/helpers.ts
Comment on lines 22 to 25
...Object.entries(errors.fieldErrors).map(
([field, errs]) => `${field}: ${(errs as any)?.join(', ')}`,
([field, errs]) =>
`${field}: ${(errs as string[] | undefined)?.join(', ')}`,
),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Skip undefined/empty field errors when composing Invalid manifest details.

At Line 24, interpolating errs directly can emit field: undefined (or empty trailing values), which degrades error clarity.

Suggested change
-    const errorMessages = [
-      ...Object.entries(errors.fieldErrors).map(
-        ([field, errs]) =>
-          `${field}: ${(errs as string[] | undefined)?.join(', ')}`,
-      ),
+    const errorMessages = [
+      ...Object.entries(errors.fieldErrors).flatMap(([field, errs]) =>
+        errs && errs.length > 0 ? [`${field}: ${errs.join(', ')}`] : [],
+      ),
       ...(errors.formErrors || []),
     ]
🤖 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/utils/dxt/helpers.ts` around lines 22 - 25, The current composition of
the "Invalid manifest" detail iterates Object.entries(errors.fieldErrors) and
interpolates errs directly, which can produce "field: undefined" or empty
entries; update the logic that builds the message (the mapping over
Object.entries(errors.fieldErrors)) to first filter entries where errs is an
array with length > 0 (or otherwise truthy) and only then map to the "field:
joinedErrors" string, ensuring undefined or empty err arrays are skipped from
the final composed message.

Comment on lines +68 to +70
const delta = msg.attachment! as unknown as McpInstructionsDelta
for (const n of delta.addedNames) announced.add(n)
for (const n of delta.removedNames) announced.delete(n)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate mcp_instructions_delta attachment shape before iterating arrays.

Line 68 trusts attachment shape via cast; if a persisted/malformed attachment lacks addedNames or removedNames, the for...of loops at Line 69-70 will throw and break delta computation.

Suggested change
-    const delta = msg.attachment! as unknown as McpInstructionsDelta
-    for (const n of delta.addedNames) announced.add(n)
-    for (const n of delta.removedNames) announced.delete(n)
+    const raw = msg.attachment as unknown as Record<string, unknown>
+    const addedNames = raw.addedNames
+    const removedNames = raw.removedNames
+    if (!Array.isArray(addedNames) || !Array.isArray(removedNames)) continue
+    for (const n of addedNames) {
+      if (typeof n === 'string') announced.add(n)
+    }
+    for (const n of removedNames) {
+      if (typeof n === 'string') announced.delete(n)
+    }

As per coding guidelines, "Use type guards (type narrowing) with union types instead of forced type casting".

📝 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.

Suggested change
const delta = msg.attachment! as unknown as McpInstructionsDelta
for (const n of delta.addedNames) announced.add(n)
for (const n of delta.removedNames) announced.delete(n)
const raw = msg.attachment as unknown as Record<string, unknown>
const addedNames = raw.addedNames
const removedNames = raw.removedNames
if (!Array.isArray(addedNames) || !Array.isArray(removedNames)) continue
for (const n of addedNames) {
if (typeof n === 'string') announced.add(n)
}
for (const n of removedNames) {
if (typeof n === 'string') announced.delete(n)
}
🤖 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/utils/mcpInstructionsDelta.ts` around lines 68 - 70, The code casts
msg.attachment to McpInstructionsDelta and iterates
delta.addedNames/removedNames without validation; add a type guard (e.g.,
isMcpInstructionsDelta(msg.attachment)) or runtime checks before using them:
ensure msg.attachment exists and that addedNames and removedNames are arrays
(Array.isArray) and fallback to empty arrays if not, then iterate those safe
arrays and update the announced set; replace the forced cast of const delta =
msg.attachment! as unknown as McpInstructionsDelta with the guarded access and
use the validated variable (delta or safeAdded/safeRemoved) in the for...of
loops.

Comment thread src/utils/profilerBase.ts
Comment on lines +36 to 38
mark(name: string): void {
this.marks.set(name, this.now())
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether any caller invokes mark() with the same name more than once
# within a profiling session (i.e., without an intervening clearMarks()).
rg -n "queryCheckpoint|headlessProfilerCheckpoint|profileCheckpoint" --type ts -A2

Repository: claude-code-best/claude-code

Length of output: 32948


🏁 Script executed:

cat -n src/utils/queryProfiler.ts

Repository: claude-code-best/claude-code

Length of output: 11051


API design divergence: document the mark-overwrite behavior for future maintainers.

Map.set silently replaces the previous timestamp for duplicate mark names, whereas the native Performance.mark() API appends multiple entries. Current code mitigates this risk through startQueryProfile() clearing all marks at the beginning of each query session, so no duplicate marks occur within a single session. However, the API design divergence should be documented in the mark() method or profilerBase.ts JSDoc to prevent accidental misuse by future callers unfamiliar with the session-based isolation pattern.

🤖 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/utils/profilerBase.ts` around lines 36 - 38, The mark(name: string)
method currently overwrites existing timestamps instead of appending like
Performance.mark; add a concise JSDoc on the mark() method (or top-level JSDoc
in profilerBase.ts) stating that mark() replaces any previous mark with the same
name, that startQueryProfile() clears marks at the start of each query session
so duplicates are not expected within a session, and warn callers that repeated
marks across sessions will overwrite prior timestamps rather than append;
reference the mark(), now(), and startQueryProfile() symbols in the comment so
future maintainers understand the session-based isolation and overwrite
semantics.

@claude-code-best claude-code-best merged commit a110887 into main May 5, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants