The slash commands that broke most often in our production Claude Code setup had one thing in common: branching logic in their definitions. "If the branch name starts with feature/, do X. If it starts with fix/, do Y. Otherwise, do Z." Reasonable-sounding designs. Unreliable in practice. The failure mode is subtle and the fix isn't obvious — until you internalize that slash commands aren't the right primitive for branching logic in the first place.
This post is why branching logic in slash commands fails, and where the logic should actually live instead. Short answer: it belongs in a subagent, or in multiple simpler slash commands, or in a script the command invokes. Longer answer follows.
What "branching logic" actually looks like
For clarity, the anti-pattern we're talking about. A slash command definition with conditionals in its instructions:
---
description: Deploy the current branch
---
# /deploy $ENVIRONMENT
Check the current git branch and $ENVIRONMENT to decide what to do:
- If $ENVIRONMENT is "production":
- Check that current branch is main
- If not on main, ask user to confirm
- Run production tests
- Deploy via the production deploy script
- If $ENVIRONMENT is "staging":
- Any branch is fine
- Run staging tests only
- Deploy via the staging deploy script
- If $ENVIRONMENT is "preview":
- Must be a PR branch
- Skip tests (they run in CI)
- Deploy to preview- subdomain
- If $ENVIRONMENT is anything else:
- Ask user what they meant Looks reasonable. Handles three deployment targets. Has fallback for unexpected input. And it's the wrong shape for a slash command.
Why this fails in production
Four specific failure modes we've watched play out:
Failure 1: Unreliable branching
Claude executing a slash command with branching logic doesn't always pick the branch you'd expect. When $ENVIRONMENT is "prod" (not "production"), does the "production" branch fire? Maybe. When it's "PROD," "Production," or "prod-us-east"? Increasingly uncertain.
Slash commands run in the LLM's inference. LLMs are good at intent-matching but they're not deterministic. "If X, do Y" in a prompt is not the same as "if X, do Y" in code. In production, this shows up as commands that mostly work but occasionally do the wrong thing when input is slightly off-shape.
For deployment commands, "occasionally does the wrong thing" is a serious problem.
Failure 2: Untestable combinatorics
A slash command with three branches has three modes. Testing "does this command work?" requires testing each mode. Each mode has its own success criteria, failure modes, and edge cases.
This isn't hypothetically hard — in practice, teams don't test multi-mode slash commands adequately. The command "works" for the mode the author tested, silently misbehaves in the others. Users encountering the untested modes discover the bugs at runtime.
Single-purpose commands are one thing to test. Multi-mode commands are N things to test, and typically get 1/N as much testing per mode.
Failure 3: Silent scope creep
A command with branching logic invites more branching. "The command handles staging and production; can it also handle preview? And dev? And Alice's personal environment?" Each new branch adds a case; the prompt grows; the reliability degrades further.
This creep is hard to resist because the command "already handles multiple environments." Adding one more feels natural. Six additions later, the command is 400 lines of branching logic that nobody fully understands.
Failure 4: Debug difficulty
When a multi-mode command misbehaves, debugging requires reproducing the exact input that triggered it, then figuring out which branch fired incorrectly. LLM-based execution is non-deterministic; the same input might work one time and fail the next.
Single-purpose commands fail more legibly. "The /deploy-production command failed" tells you where to look. "The /deploy command failed while trying to deploy to production" leaves ambiguity about whether the branching logic misfired or the actual deploy failed.
What to do instead
Three patterns work. Which one to pick depends on the nature of the branching.
Pattern 1: Multiple simpler slash commands
The most common fix. Split the multi-mode command into single-purpose commands.
The /deploy example becomes:
/deploy-production— deploys main to production, with the production-specific checks/deploy-staging— deploys current branch to staging/deploy-preview— deploys PR branch to preview subdomain
Three commands, each doing one thing, each testable independently, each with clear naming that tells users what they do. No branching in any command; the branching happened when the user chose which command to invoke.
Costs: more commands to remember. But three commands with clear names are easier to remember correctly than one command with three modes and mode-selection logic.
Pattern 2: Subagent for the reasoning
If the branching is genuinely reasoning-heavy — not just "which environment" but "given this PR's changes, which tests should we prioritize" — the reasoning belongs in a subagent, not a slash command.
The slash command becomes a thin wrapper:
# /review-pr $PR_NUMBER
Invoke @pr-reviewer with the PR number and standard context.
The subagent handles the reasoning about what kind of review this needs.The subagent, being a proper reasoning primitive, handles the branching internally with much better reliability than a slash command could. See the primitive decision tree for when this applies.
Pattern 3: Deterministic script for the branching
Sometimes the branching is genuinely deterministic — "if environment is X, use config file Y." No reasoning; just lookup. This belongs in a script, not a slash command.
The slash command calls the script; the script handles the branching in code (where branching is reliable); the command's prompt just says "run this script with these args."
# /deploy $ENVIRONMENT
Run: ./scripts/deploy.sh $ENVIRONMENT
The script handles environment-specific config. Report back the result.Deterministic branching in bash/Python is far more reliable than the same logic embedded in a prompt. Use the right tool for the job — code for deterministic branching, LLMs for reasoning.
The rule of thumb
The heuristic that catches most cases: if your slash command's definition contains the words "if," "otherwise," "unless," or "depending on," it's probably in the wrong shape. Not always — some conditions are legitimately part of a single-purpose command. But it's a strong signal to check.
When you find one, ask:
- Is the branching about "which task"? → Multiple slash commands (Pattern 1).
- Is the branching about "how to think about this task"? → Subagent (Pattern 2).
- Is the branching about "which parameter/config"? → Script (Pattern 3).
Almost every branching-slash-command we've seen fits one of these categories. Applying the right pattern eliminates the branching from the command definition and improves reliability substantially.
What ONE-mode slash commands look like
The slash commands that hold up in production have a specific shape:
- Single action — one clear thing they do
- Small parameter surface — usually 0-2 parameters
- No conditional logic in the prompt — no "if," "otherwise," etc.
- Under 300 words of prompt — anything more usually means hidden complexity
- Deterministic in intent — you can predict what it'll do without running it
Commands with these properties are testable, reliable, and stay small over time. Commands without them tend to grow into unreliable monsters.
If your slash command's definition is over 500 words, it almost certainly has hidden branching or scope creep. Long slash commands are a code smell. Short ones don't have room for branching to hide in.
The retrofit
If you have existing slash commands with branching logic, retrofitting is straightforward:
- Identify branches. For each branching command, list the modes it currently handles.
- Classify each branch. Task-type branch (Pattern 1), reasoning branch (Pattern 2), or config branch (Pattern 3)?
- Extract accordingly. Split into multiple commands, promote to subagent, or move logic into a script.
- Keep the old command as a stub for backward compatibility. Point users to the new pattern; deprecate after a few weeks.
- Delete the old command. Once users have migrated, remove the branching version.
For a command with 3-4 branches, this typically takes an hour of work and produces meaningfully more reliable commands. Payoff is fast; retrofit is low-risk.
Why this matters
Slash commands are the most exposed Claude Code primitive — they're what users invoke most often. Unreliable commands train users to distrust the whole Claude Code system. "Did the command work? Let me check." That verification friction accumulates; users start doing the work manually to skip the uncertainty.
Reliable single-purpose commands build the opposite pattern: users trust the commands, invoke them freely, get consistent results. The command interface becomes an accelerator rather than a source of friction.
The one design rule that matters most for command reliability: no branching in the command definition. Extract branching to the right primitive. Everything else follows.
Get cctk running in one command
85 slash commands, 12 subagents, 12 MCP integrations, 12 hooks. All the patterns from this post are shipped in cctk.