From 1c3b280c6a0e21cce6f21264d0087a7f8d9ff14b Mon Sep 17 00:00:00 2001 From: claude-code-best Date: Sat, 25 Apr 2026 14:31:32 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=B0=9D=E8=AF=95=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=A4=9A=E8=BD=AE=E5=AF=B9=E8=AF=9D=E7=BC=93=E5=AD=98=E5=A4=B1?= =?UTF-8?q?=E6=95=88=20skill=20=E6=8F=90=E5=8D=87=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/attachments.ts | 17 +++++++++++++++++ src/utils/conversationRecovery.ts | 12 +++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/utils/attachments.ts b/src/utils/attachments.ts index 4085c42b9..7eb449474 100644 --- a/src/utils/attachments.ts +++ b/src/utils/attachments.ts @@ -819,6 +819,10 @@ export async function getAttachments( !options?.skipSkillDiscovery ? [ maybe('skill_discovery', async () => { + if (suppressNextDiscovery) { + suppressNextDiscovery = false + return [] + } const result = await skillSearchModules.prefetch.getTurnZeroSkillDiscovery( input, messages ?? [], @@ -2638,6 +2642,7 @@ const sentSkillNames = new Map>() export function resetSentSkillNames(): void { sentSkillNames.clear() suppressNext = false + suppressNextDiscovery = false } /** @@ -2661,6 +2666,18 @@ export function suppressNextSkillListing(): void { } let suppressNext = false +/** + * Suppress the next skill-discovery injection on resume. Same rationale as + * suppressNextSkillListing: skill_discovery attachments are not persisted to + * transcript for non-ant users, so the prior process's discovery result is + * already in the conversation history the model sees. Re-generating it would + * inject duplicate content and bust the prompt cache prefix. + */ +export function suppressNextSkillDiscovery(): void { + suppressNextDiscovery = true +} +let suppressNextDiscovery = false + // When skill-search is enabled and the filtered (bundled + MCP) listing exceeds // this count, fall back to bundled-only. Protects MCP-heavy users (100+ servers) // from truncation while keeping the turn-0 guarantee for typical setups. diff --git a/src/utils/conversationRecovery.ts b/src/utils/conversationRecovery.ts index 51c36520c..3379e9dd0 100644 --- a/src/utils/conversationRecovery.ts +++ b/src/utils/conversationRecovery.ts @@ -18,7 +18,7 @@ import type { NormalizedUserMessage, } from '../types/message.js' import { PERMISSION_MODES } from '../types/permissions.js' -import { suppressNextSkillListing } from './attachments.js' +import { suppressNextSkillDiscovery, suppressNextSkillListing } from './attachments.js' import { copyFileHistoryForResume, type FileHistorySnapshot, @@ -403,6 +403,16 @@ export function restoreSkillStateFromMessages(messages: Message[]): void { suppressNextSkillListing() } } + + // Unconditionally suppress skill_listing and skill_discovery on resume. + // Attachments are not persisted to transcript for non-ant users + // (isLoggableMessage filters them out), so the per-type checks above may + // never find them even though the prior process already injected the content + // into the conversation via blocks. Without this, every + // resume re-injects ~1K tokens of duplicate content and busts the Anthropic + // prompt cache prefix (which requires 100% byte-identical segments). + suppressNextSkillListing() + suppressNextSkillDiscovery() } /**