aiagent.
aiagent6 min read

Claude Code Slash Commands with $ARGUMENTS: A Practical Guide

How $ARGUMENTS lands inside slash command prompts, parsing patterns that survive whitespace and quoting, and validation strategies before the agent runs.

Claude Code Slash Commands with $ARGUMENTS: A Practical Guide

Slash commands in Claude Code look deceptively simple. You drop a markdown file into .claude/commands/, type /your-command some args in the terminal, and the agent runs the prompt. The interesting part \u2014 and the part people get wrong on first try \u2014 is what happens to the text after the command name. That text becomes $ARGUMENTS, and how you handle it determines whether your command is a sharp tool or a foot-gun.

This walks through where $ARGUMENTS lands in the prompt body, how to parse it without writing a tokenizer, and what validation belongs in the prompt versus what belongs in your tooling.

Where $ARGUMENTS Actually Lands

A slash command is a markdown file. Anywhere you write $ARGUMENTS, Claude Code does a literal string substitution before sending the prompt to the model. There is no shell, no glob expansion, no quote interpretation. Whatever the user typed after the command name \u2014 minus the leading space \u2014 replaces every occurrence.

A minimal command file looks like this:

---
description: Summarize a PR by URL
---

Summarize the GitHub pull request at $ARGUMENTS.
Read the diff, the description, and the top three review comments.
Report the change in under 200 words.

If the user runs /pr-summary https://github.com/anthropics/claude-code/pull/42, the model sees the URL spliced in. If they run /pr-summary with no args, $ARGUMENTS becomes the empty string and the prompt reads "Summarize the GitHub pull request at .". That second case is the first place commands break \u2014 the model sees a malformed request and either asks for clarification or hallucinates a URL.

Parsing Multiple Arguments Without a Tokenizer

There is no built-in $1 / $2 / $3 like bash. You get one variable, and it contains everything. Three patterns cover most cases.

Pattern 1: positional, simple. Tell the model how to split the string in plain English. This works when arguments are obviously distinct (a URL, then a word):

The user passed two arguments separated by space: $ARGUMENTS
The first argument is a repository URL.
The second argument is the branch name.
Extract both. If the second is missing, default to "main".

Pattern 2: named, structured. When arguments are not obviously ordered, force a key=value shape:

Parse the arguments string: $ARGUMENTS
Format is `niche=<slug> count=<n>`. Both are required.
If either is missing or malformed, refuse and explain the expected format.

Pattern 3: free-form, single-purpose. When the command takes one logical thing \u2014 a description, a question, a path \u2014 don't parse at all. Treat $ARGUMENTS as a single payload:

The user wants help with this task: $ARGUMENTS
Treat the entire argument string as the task description.

Pattern 3 is the one to reach for first. Most slash commands in the wild are wrapping a single intent \u2014 /explain <thing>, /refactor <file>, /test <feature> \u2014 and parsing them as multi-arg adds failure modes for no benefit.

Validation: In the Prompt or in the Tooling

Validation has two layers. The prompt can describe what it expects ("if $ARGUMENTS is empty, refuse"), but the model is not a parser. It will sometimes be lenient when you wanted strict, and strict when you wanted lenient. Critical validation belongs upstream of the model.

For Claude Code itself, "upstream" means a hook or a wrapping script that runs before the slash command fires. If the user types /deploy production, you don't want the model to decide whether production is a valid environment \u2014 you want a PreToolUse hook to check that against an allowlist and reject the command before any tokens are spent.

Claude Code's docs on hooks (https://docs.anthropic.com/en/docs/claude-code/hooks) cover this pattern. The model handles intent ("the user wants to deploy"), the hook handles policy ("only staging and production are valid, and production requires a confirmation").

A useful split:

ConcernBelongs in promptBelongs in hook
"Argument is missing"Yes \u2014 model can ask for clarificationOptional
"Argument is malformed"Soft check OKHard check here
"Argument is not in allowlist"No \u2014 too lenientYes
"Argument requires special permission"NoYes \u2014 fail closed
"Argument shape (URL vs path)"YesOptional

Whitespace, Quoting, and the Things That Bite

A few quirks worth knowing before you ship a command to teammates.

Whitespace in $ARGUMENTS is preserved verbatim. If the user runs /foo bar with three spaces, the prompt contains three spaces. The model usually handles this fine \u2014 language models are robust to whitespace \u2014 but if you're writing a command that splits on space and counts tokens, normalize first by telling the model to split on any whitespace run.

Quotes are literal. If the user runs /foo "hello world" thinking the quotes group the argument, the model sees the quotes as part of the string. This is occasionally what you want (the user is passing a quoted phrase) and occasionally what you don't (the user is mimicking shell behavior). Document the contract in the command's description so users know what to expect.

Newlines are allowed in some Claude Code surfaces and not in others. Don't design a command that requires multi-line input through $ARGUMENTS \u2014 push that to a file path instead, and have the command read the file.

A Command That Holds Up in Production

Here's a complete example combining the patterns above. It's a command that takes a topic and a target audience, validates both, and produces a concise tech brief:

---
description: Produce a technical brief on a topic for a specified audience
---

Parse the arguments: $ARGUMENTS

Expected format: `topic="<phrase>" audience=<slug>`
- `topic` is a quoted phrase, required.
- `audience` is one of: backend, frontend, devops, ml.

If $ARGUMENTS is empty, ask the user for both fields and stop.
If the format is malformed, show the expected format and stop.
If `audience` is not one of the four values, list the valid values and stop.

Otherwise:
1. Write a 300-word technical brief on the topic for the audience.
2. Open with the most concrete, specific framing \u2014 no "in today's landscape".
3. Include one code snippet OR one numeric claim.
4. End with two follow-up questions the audience would actually ask.

This command does parsing in the prompt because the arguments are simple, validates the audience field with an explicit allowlist, and refuses cleanly on bad input rather than guessing. For a higher-stakes command \u2014 one that runs git push --force or makes a paid API call \u2014 the audience check should be a PreToolUse hook instead, since model-side validation is best-effort.

When to Reach for a Skill Instead

Slash commands and skills overlap. The rule of thumb: slash commands are for short, prompt-shaped tasks the user invokes by name. Skills are for capabilities the model decides to load when relevant, and they can carry larger context (multiple files, structured procedures, related references).

If your command's prompt body is growing past ~50 lines, or if you find yourself wanting to load reference material conditionally, you've outgrown the slash command shape. Move it to a skill under ~/.claude/skills/<name>/SKILL.md and keep slash commands for the one-line invocations that benefit from being typeable.

The Anthropic engineering team's writeup on skills (https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills) gives the longer framing on this split \u2014 slash commands for invocation, skills for capability.

References: