Skip to content

v5.0.0: PromptProcessing classifier fails on slash-prefixed prompts (Unknown command: /X) → E3 fail-safe on every slash command #1158

Description

@NodarDavituri

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions