Skip to content

[Bug] /ponytail never sets a mode in Claude Code: the UserPromptSubmit hook receives the SKILL.md body, not the command text #584

Closed as not planned

Description

@Zoz92

[Bug] /ponytail never sets a mode in Claude Code: the UserPromptSubmit hook receives the SKILL.md body, not the command text

Summary

In Claude Code, /ponytail is dispatched as a skill, not as a commands/*.md slash command. The harness therefore hands UserPromptSubmit a data.prompt that begins with <command-message> / <command-name> tags and then contains the entire body of skills/ponytail/SKILL.md — not the literal string /ponytail:ponytail.

hooks/ponytail-mode-tracker.js:23 tests:

if (/^[/@$]ponytail/.test(prompt)) {

That anchor never matches, so setMode() is never called, ~/.claude/.ponytail-active is never written, and everything that reads it (ponytail-instructions.js, ponytail-subagent.js, the statusline) sees no active mode.

This is the same root cause as the (now fixed) #161, seen from the other side. Before #162 landed, the old deactivation regex

if (/\b(stop ponytail|normal mode)\b/i.test(prompt)) {

matched ponytail's own SKILL.md, which says on line 29:

unsure. Off only: "stop ponytail" / "normal mode". Default: **full**.

So invoking /ponytail:ponytail printed PONYTAIL MODE OFF. The skill disabled itself by reading itself. #162's isDeactivationCommand fixed that half; the command-detection half is still open.

Environment

  • ponytail 4.8.4 (marketplace DietrichGebert/ponytail, user scope)
  • Claude Code CLI 2.1.206, Linux
  • Hook wired via hooks/claude-codex-hooks.json → UserPromptSubmit → ponytail-mode-tracker.js

Reproduction

  1. Install ponytail 4.8.4 in Claude Code, start a session.
  2. Invoke /ponytail:ponytail (or /ponytail full) from the slash menu.
  3. Observe: no PONYTAIL MODE CHANGED output from the hook.
  4. ls ~/.claude/.ponytail-active → No such file or directory.

On 4.2.0, step 3 instead prints PONYTAIL MODE OFF, every single time.

Typing ponytail ultra as ordinary prose still works, because then data.prompt really is the literal text. Only the slash/skill path is affected — which is the documented way to use it.

Why this wasn't caught

#269 reported the commands/*.toml vs commands/*.md mismatch and was closed unmerged, with:

the UserPromptSubmit hook (ponytail-mode-tracker.js) parses the prompt text, so /ponytail ultra persists the mode whether it came from a skill or was typed raw. It even handles the /ponytail:ponytail form.

The code does handle that form. The harness just never delivers it: when the command comes from a skill, the prompt is the skill body. The skills do show up in the / menu with their arg hints, as you observed — that part is orthogonal, and it's what makes the failure invisible. The menu entry works; the hook behind it doesn't.

Suggested fix

Prefer the <command-name> tag when present, and fall back to the raw prompt otherwise. Claude Code emits a <command-args> tag for at least some commands (empty when there is no argument); the snippet below tolerates its absence:

const raw = data.prompt || '';
const name = raw.match(/<command-name>\s*([^<]+?)\s*<\/command-name>/)?.[1];
const args = raw.match(/<command-args>\s*([^<]*?)\s*<\/command-args>/)?.[1];
const prompt = (name ? `${name} ${args ?? ''}` : raw).trim().toLowerCase();

Everything downstream (^[/@$]ponytail, the parts[0] / parts[1] split, isDeactivationCommand) then works unchanged, and isDeactivationCommand keeps protecting the raw-prose path.

Worth a regression test that feeds the tracker a data.prompt built from the real skill body: it fails on both counts today (no activation; and on any build predating #162, a spurious deactivation).

Activity

  1. added a commit that references this issue on Jul 22, 2026
  2. DietrichGebert commented on Oct 3, 2026

    @DietrichGebert
    Owner

    I could not reproduce this on current Claude Code. I logged the raw UserPromptSubmit payload on 2.1.186 and on 2.1.288 (print mode): for /ponytail ultra and /ponytail:ponytail lite the hook gets the literal command text, and the mode flag switches correctly. The <command-message> tags plus the SKILL.md body are what the model sees after dispatch, not what the hook gets. Closing; if it still happens for you on a current version, please reopen with the version and whether you picked the command from the slash menu or typed it. Thanks @Zoz92 for the detailed report.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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