Skip to content

/ponytail-review latches the session mode flag, replacing the ruleset with a pointer line for the rest of the session #736

Description

@Ayush-1812

Summary

/ponytail-review is documented and implemented everywhere else as a one-shot
review command, but in the hooks it writes review into the session mode flag.
Because review is an INDEPENDENT_MODES entry, every later consumer of
getPonytailInstructions() gets a one-line pointer instead of the actual ladder
— for the rest of the session, with no automatic way back.

Detail

hooks/ponytail-mode-tracker.js:31 maps
/ponytail-review to mode = 'review', which falls through to the
setMode(mode) branch at hooks/ponytail-mode-tracker.js:65
and persists review to $CLAUDE_CONFIG_DIR/.ponytail-active. Nothing ever
clears it.

From then on, hooks/ponytail-instructions.js:79-81
short-circuits on INDEPENDENT_MODES and returns only:

PONYTAIL MODE ACTIVE — level: review. Behavior defined by /ponytail-review skill.

Affected injection points, all of which now emit that pointer instead of the ruleset:

ponytail-mcp is unaffected: resolveMode() in
ponytail-mcp/instructions.js filters review
out already.

The statusline also sticks: hooks/ponytail-statusline.sh:17
renders any non-full mode verbatim, so the bar reads [PONYTAIL:REVIEW]
indefinitely.

Recovery is worse than it looks. Bare /ponytail is report-only since #99
(hooks/ponytail-mode-tracker.js:49-51) —
it reads the flag and echoes level: review without rewriting it. So the most
natural corrective gesture confirms the stuck state rather than fixing it. Only
an explicit /ponytail lite|full|ultra, /ponytail off, or stop ponytail
clears it.

Why this is a bug, not the design

Every doc treats review as one-shot, not a level:

Related inconsistency: the Claude tracker rejects /ponytail review (the arg
falls through to getDefaultMode()), but OpenCode's command.execute.before
runs it through normalizePersistedMode, which accepts review via
normalizeConfigMode. So /ponytail review latches OpenCode into the pointer
state even though the same input is a no-op under Claude Code.

Missing test coverage

tests/hooks.test.js mentions review only at
lines 462-486, and every one of those asserts
that review is refused as a default. No test drives /ponytail-review
through ponytail-mode-tracker.js at all, so neither the flag write nor its
downstream effect on getPonytailInstructions() is covered anywhere.

Suggested fix

Treat review like /ponytail-help: let it dispatch the skill without touching
the flag file. Concretely, don't setMode() for review in the tracker —
emit the mode-active line and leave the persisted level alone. That makes
INDEPENDENT_MODES unreachable through the runtime path, at which point the
branch in ponytail-instructions.js is dead code and can go too.

Tests worth adding:

  • /ponytail-review leaves an existing full/lite/ultra flag untouched.
  • /ponytail-review on a fresh session does not create the flag with review.
  • A subagent spawned after /ponytail-review still receives the full ladder.
  • OpenCode's /ponytail review does not persist review.

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