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.
Summary
/ponytail-reviewis documented and implemented everywhere else as a one-shotreview command, but in the hooks it writes
reviewinto the session mode flag.Because
reviewis anINDEPENDENT_MODESentry, every later consumer ofgetPonytailInstructions()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-reviewtomode = 'review', which falls through to thesetMode(mode)branch at hooks/ponytail-mode-tracker.js:65and persists
reviewto$CLAUDE_CONFIG_DIR/.ponytail-active. Nothing everclears it.
From then on, hooks/ponytail-instructions.js:79-81
short-circuits on
INDEPENDENT_MODESand returns only:Affected injection points, all of which now emit that pointer instead of the ruleset:
Task-spawned subagent for the rest of the session runs effectively
ponytail-unaware, which is the exact failure Ponytail rules don't reach subagents — add SubagentStart hook #252 added this hook to fix.
Qoder, which relies on
UserPromptSubmitfor per-turn ruleset injection, getsthe pointer on every prompt.
before_agent_startsystem prompt.OpenCode's per-turn
system.transform.ponytail-mcpis unaffected:resolveMode()inponytail-mcp/instructions.js filters
reviewout already.
The statusline also sticks: hooks/ponytail-statusline.sh:17
renders any non-
fullmode verbatim, so the bar reads[PONYTAIL:REVIEW]indefinitely.
Recovery is worse than it looks. Bare
/ponytailis report-only since #99(hooks/ponytail-mode-tracker.js:49-51) —
it reads the flag and echoes
level: reviewwithout rewriting it. So the mostnatural corrective gesture confirms the stuck state rather than fixing it. Only
an explicit
/ponytail lite|full|ultra,/ponytail off, orstop ponytailclears it.
Why this is a bug, not the design
Every doc treats review as one-shot, not a level:
lists only lite/full/ultra under Levels ("Level sticks until changed or
session end");
ponytail-reviewappears under Skills atline 30.
over-engineering, hands back a delete-list."
reviewis not a valid default —hooks/ponytail-config.js:78-81 and
writeDefaultModeboth refuse it. Refusing it as a persisted default whileletting it persist as a session mode is the same class of mistake, one layer
down.
Related inconsistency: the Claude tracker rejects
/ponytail review(the argfalls through to
getDefaultMode()), but OpenCode'scommand.execute.beforeruns it through
normalizePersistedMode, which acceptsreviewvianormalizeConfigMode. So/ponytail reviewlatches OpenCode into the pointerstate even though the same input is a no-op under Claude Code.
Missing test coverage
tests/hooks.test.jsmentionsreviewonly atlines 462-486, and every one of those asserts
that review is refused as a default. No test drives
/ponytail-reviewthrough
ponytail-mode-tracker.jsat all, so neither the flag write nor itsdownstream effect on
getPonytailInstructions()is covered anywhere.Suggested fix
Treat review like
/ponytail-help: let it dispatch the skill without touchingthe flag file. Concretely, don't
setMode()forreviewin the tracker —emit the mode-active line and leave the persisted level alone. That makes
INDEPENDENT_MODESunreachable through the runtime path, at which point thebranch in
ponytail-instructions.jsis dead code and can go too.Tests worth adding:
/ponytail-reviewleaves an existingfull/lite/ultraflag untouched./ponytail-reviewon a fresh session does not create the flag withreview./ponytail-reviewstill receives the full ladder./ponytail reviewdoes not persistreview.