Summary
hooks/PromptProcessing.hook.ts runs the Sonnet mode/tier classifier on every UserPromptSubmit via the claude --print subprocess in PAI/TOOLS/Inference.ts. When the prompt starts with / (any slash-command — /interview, /loop, /migrate, etc.), claude --print reads stdin starting with / as a CLI slash-command and returns Unknown command: /<X>. The classifier's JSON parse fails and the fail-safe path writes MODE: ALGORITHM | TIER: E3 | REASON: inference failed | SOURCE: fail-safe into additionalContext.
Symptom: every /-prefixed prompt that reaches the classifier escalates to ALGORITHM E3, regardless of whether the actual workflow is trivial (/interview) or substantial. Cosmetic noise + telemetry pollution + occasional unwanted ceremony.
This is deterministic — every leading-/ prompt hits this. Closest existing report is #809 ("WORKFLOW depth — slash commands bypass Algorithm classification"), which proposes detecting <command-name>/... tags and short-circuiting classification entirely. This report is complementary: it addresses prompts that DO reach the classifier (e.g. direct skill/workflow invocations, manual /-prefixed messages, anything #809's bypass doesn't catch).
Reproduction
# Synthetic — calls inference() with a `/`-prefixed prompt
cd ~/.claude && bun -e '
const { inference } = await import("./PAI/TOOLS/Inference.ts");
const r = await inference({
systemPrompt: "Return JSON: {\"mode\":\"NATIVE\",\"reason\":\"x\"}",
userPrompt: "/interview",
expectJson: true,
timeout: 25000,
level: "standard",
});
console.log(r.success, r.error || r.parsed);
'
Result: success: false, error: "Failed to parse JSON response" — because claude --print returned Unknown command: /interview\n rather than the JSON object.
Fix (3 lines + 2 comment lines)
hooks/PromptProcessing.hook.ts around line 926:
const cleanPrompt = prompt.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 1000);
+ // Wrap leading-`/` so `claude --print` doesn't read it as a CLI slash-command
+ // (which would return "Unknown command: /X" and fail JSON parse → E3 fail-safe).
+ const safePrompt = cleanPrompt.startsWith('/')
+ ? `User invoked: ${cleanPrompt}`
+ : cleanPrompt;
// Naming is permanent and first-prompt-only; exclude Assistant turns so Algorithm scaffolding
// (phase headers, agent names, SUMMARY lines) cannot contaminate the session name. Tab-title
// inference on later prompts keeps Assistant context for "continue with X" style follow-ups.
const context = getRecentContext(data.transcript_path, 6, !isFirstPrompt);
- const userPrompt = context ? `CONTEXT:\n${context}\n\nCURRENT MESSAGE:\n${cleanPrompt}` : cleanPrompt;
+ const userPrompt = context ? `CONTEXT:\n${context}\n\nCURRENT MESSAGE:\n${safePrompt}` : safePrompt;
The cleanPrompt regex chain is unchanged; this only adds a thin wrapper. The classifier still sees the actual prompt text after the User invoked: prefix and produces the right MODE/TIER for the underlying intent.
Alternative location — PAI/TOOLS/Inference.ts
The same defensive escape could live in Inference.ts (right before proc.stdin.write(userPromptWithImages) at line 143) so every caller is protected. This issue scopes the fix to the named site to keep the change minimal; happy to widen the patch to Inference.ts if that's the better home.
Tested locally
Patch applied to my v5.0.0 install at ~/.claude/hooks/PromptProcessing.hook.ts:929. bun build --target bun --no-bundle PromptProcessing.hook.ts clean. The safePrompt ternary verified via bun -e synthetic invocation: safePrompt === "User invoked: /interview".
Optional PR
Happy to open a PR if useful — reply with the branch naming convention you'd like.
Summary
hooks/PromptProcessing.hook.tsruns the Sonnet mode/tier classifier on everyUserPromptSubmitvia theclaude --printsubprocess inPAI/TOOLS/Inference.ts. When the prompt starts with/(any slash-command —/interview,/loop,/migrate, etc.),claude --printreads stdin starting with/as a CLI slash-command and returnsUnknown command: /<X>. The classifier's JSON parse fails and the fail-safe path writesMODE: ALGORITHM | TIER: E3 | REASON: inference failed | SOURCE: fail-safeinto additionalContext.Symptom: every
/-prefixed prompt that reaches the classifier escalates to ALGORITHM E3, regardless of whether the actual workflow is trivial (/interview) or substantial. Cosmetic noise + telemetry pollution + occasional unwanted ceremony.This is deterministic — every leading-
/prompt hits this. Closest existing report is #809 ("WORKFLOW depth — slash commands bypass Algorithm classification"), which proposes detecting<command-name>/...tags and short-circuiting classification entirely. This report is complementary: it addresses prompts that DO reach the classifier (e.g. direct skill/workflow invocations, manual/-prefixed messages, anything #809's bypass doesn't catch).Reproduction
Result:
success: false, error: "Failed to parse JSON response"— becauseclaude --printreturnedUnknown command: /interview\nrather than the JSON object.Fix (3 lines + 2 comment lines)
hooks/PromptProcessing.hook.tsaround line 926:The
cleanPromptregex chain is unchanged; this only adds a thin wrapper. The classifier still sees the actual prompt text after theUser invoked:prefix and produces the right MODE/TIER for the underlying intent.Alternative location —
PAI/TOOLS/Inference.tsThe same defensive escape could live in
Inference.ts(right beforeproc.stdin.write(userPromptWithImages)at line 143) so every caller is protected. This issue scopes the fix to the named site to keep the change minimal; happy to widen the patch toInference.tsif that's the better home.Tested locally
Patch applied to my v5.0.0 install at
~/.claude/hooks/PromptProcessing.hook.ts:929.bun build --target bun --no-bundle PromptProcessing.hook.tsclean. ThesafePromptternary verified viabun -esynthetic invocation:safePrompt === "User invoked: /interview".Optional PR
Happy to open a PR if useful — reply with the branch naming convention you'd like.