Your slash commands shouldn't have branching logic | Claude Code Toolkit
Anti-patterns

Your slash commands shouldn't have branching logic

The moment your slash command's definition contains "if the user wants X, do Y; otherwise do Z," you've outgrown the primitive. Branching logic in slash commands is one of those anti-patterns that seems reasonable until you've watched it fail. Here's why it fails and what to do instead.

MK
Mateo K. September 18, 2026 · 8 min read

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:

markdown
---
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:

markdown
# /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."

markdown
# /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.

The prompt-length canary

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:

  1. Identify branches. For each branching command, list the modes it currently handles.
  2. Classify each branch. Task-type branch (Pattern 1), reasoning branch (Pattern 2), or config branch (Pattern 3)?
  3. Extract accordingly. Split into multiple commands, promote to subagent, or move logic into a script.
  4. Keep the old command as a stub for backward compatibility. Point users to the new pattern; deprecate after a few weeks.
  5. 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.

MK

Written by

Mateo K.

Mateo focuses on platform engineering, agentic workflows, and turning experimental patterns into infrastructure that scales.

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.

npx cctk@latest init Get cctk v1.5.2 →

Share with