Skip to content

Commit fe1a5ea

Browse files
committed
fix(claude): elide blocked-skill bundles on Windows paths — normalize backslashes before basename split
The text-block carrier's skill-dir basename used dir.split("/"), so Windows clients (C:\Users\...\claude-api) never matched the blocklist and ~790K-char bundles rode through to routed models (live incident 2026-07-15, ~297K-token inputs). Normalize separators first (repo precedent: isOpencodexCatalogPath). Tests: Windows/mixed/UNC elided, drive-relative pinned pass-through. devlog: 260715_cross_platform_audit/010
1 parent d3f299f commit fe1a5ea

2 files changed

Lines changed: 27 additions & 3 deletions

File tree

src/claude/inbound.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,9 @@ function maybeElideSkillText(text: string, names: readonly string[]): string {
180180
if (!text.startsWith(SKILL_TEXT_MARKER)) return text;
181181
const firstLineEnd = text.indexOf("\n");
182182
const dir = text.slice(SKILL_TEXT_MARKER.length, firstLineEnd === -1 ? text.length : firstLineEnd).trim();
183-
const base = dir.split("/").filter(Boolean).pop()?.toLowerCase() ?? "";
183+
// Windows clients send `C:\Users\...\claude-api`; normalize separators before
184+
// basenaming (repo precedent: src/codex/inject.ts isOpencodexCatalogPath).
185+
const base = dir.replace(/\\/g, "/").split("/").filter(Boolean).pop()?.toLowerCase() ?? "";
184186
if (!names.includes(base)) return text;
185187
return `[opencodex] '${base}' skill document bundle (${text.length} chars) elided for routed models `
186188
+ "(claudeCode.blockedSkills). The skill is loaded; answer from general knowledge instead of citing the bundle.";

tests/claude-inbound.test.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,8 +310,9 @@ describe("bundled-skill elision for routed models (devlog 260712 060)", () => {
310310

311311
// Live-capture carrier (2.1.207): the bundle rides a sibling TEXT block whose first
312312
// line is "Base directory for this skill: <dir>/<name>" — not the tool_result.
313-
function requestWithSkillTextBlock(skillDirName: string, textLen: number, cc?: { blockedSkills?: string[] }) {
314-
const bundle = `Base directory for this skill: /private/tmp/claude-501/bundled-skills/2.1.207/abc/${skillDirName}\n\n` + "DOCS ".repeat(Math.ceil(textLen / 5));
313+
function requestWithSkillTextBlock(skillDirName: string, textLen: number, cc?: { blockedSkills?: string[] }, baseDir?: string) {
314+
const dir = baseDir ?? `/private/tmp/claude-501/bundled-skills/2.1.207/abc/${skillDirName}`;
315+
const bundle = `Base directory for this skill: ${dir}\n\n` + "DOCS ".repeat(Math.ceil(textLen / 5));
315316
return anthropicToResponsesTranslation({
316317
model: "gemini/gemini-3-pro",
317318
max_tokens: 100,
@@ -344,6 +345,27 @@ describe("bundled-skill elision for routed models (devlog 260712 060)", () => {
344345
const off = userTexts(requestWithSkillTextBlock("claude-api", 500_000, { blockedSkills: [] }));
345346
expect(off.some(t => t.length > 400_000)).toBe(true);
346347
});
348+
349+
test("text-block carrier: Windows backslash base dir is elided (live incident 2026-07-15)", () => {
350+
const texts = userTexts(requestWithSkillTextBlock("claude-api", 500_000, undefined,
351+
"C:\\Users\\user\\AppData\\Roaming\\npm\\node_modules\\bundled-skills\\claude-api"));
352+
expect(texts.some(t => t.includes("elided") && t.includes("claude-api"))).toBe(true);
353+
expect(texts.every(t => t.length < 10_000)).toBe(true);
354+
});
355+
356+
test("text-block carrier: mixed separators and UNC paths are elided", () => {
357+
const mixed = userTexts(requestWithSkillTextBlock("claude-api", 500_000, undefined,
358+
"C:\\Users\\u\\skills/2.1.207\\claude-api"));
359+
expect(mixed.some(t => t.includes("elided"))).toBe(true);
360+
const unc = userTexts(requestWithSkillTextBlock("claude-api", 500_000, undefined,
361+
"\\\\server\\share\\skills\\claude-api"));
362+
expect(unc.some(t => t.includes("elided"))).toBe(true);
363+
});
364+
365+
test("text-block carrier: drive-relative dir (no separator) stays pass-through", () => {
366+
const texts = userTexts(requestWithSkillTextBlock("claude-api", 500_000, undefined, "C:claude-api"));
367+
expect(texts.some(t => t.length > 400_000)).toBe(true);
368+
});
347369
});
348370

349371
describe("ocx-route directive (devlog 072)", () => {

0 commit comments

Comments
 (0)