Recommended resources
The freely available resources this lesson will direct you to.
Welcome back!
In our last lesson, you built a custom slash command to scaffold new FastAPI endpoint files. This was a great first step into declarative automation, focusing on file creation. You learned how to define project-level commands and make them dynamic using positional arguments.
Today, we'll take a significant leap forward. We're moving from generating code to orchestrating external tools. The learning outcome is to build and share a project-level slash command for generating database migration scripts via version control. This is a common and critical task in backend development, and automating it makes your workflow faster, more consistent, and less error-prone. This new command will interact directly with your database migration tool and Git, showcasing how Claude Code can become a central operator in your development loop.
1. From Generating Text to Executing Commands
A key feature that elevates slash commands from simple prompt templates to powerful automation scripts is their ability to execute shell commands. This allows Claude to interact with your local environment, run build tools, interact with version control, or, in our case, generate a database migration.
The official Claude API documentation provides the essential details on how this works.
Slash Commands in the SDK - Claude API Docs
The 'Slash Commands in the SDK' documentation explains the syntax for executing shell commands and controlling permissions. This is the technical foundation for today's lesson.
Please read the subsection titled 'Bash Command Execution'. Pay close attention to two key things: The !command syntax used to execute a shell command. The allowed-tools key in the YAML frontmatter, which is a crucial security feature for granting explicit permission to run specific commands (e.g., Bash(git:*)).
As you saw in the documentation, you have two primary ways to use shell commands:
- Execute a command:
!my_command --some-flagruns the command. - Embed command output:
!``my_command --some-flag``runs the command and injects itsstdoutdirectly into the prompt context.
This capability is what we'll harness to call our database migration tool.
2. A Disciplined Approach to State-Changing Operations
Generating a database migration isn't just another task; it's a state-changing operation that affects a critical part of your application. When automating such tasks, it's wise to adopt a disciplined approach. We don't want Claude to be a loose cannon; we want it to be a precise, controlled executor.
Database Migrations with Docker, Alembic, and Automated Tests
This article by Cristian Sifuentes, though focused on a broader workflow, perfectly articulates the mindset for using AI to handle sensitive tasks like database migrations.
Read the section titled 'Claude Code as an operator: plan first, then execute with permission'. Focus on the concept of using Claude as a 'controlled executor' and the value of having it create a plan for state-changing operations that you explicitly approve.
This "plan first, execute with permission" model is exactly what we'll build into our slash command. We'll have it generate the migration, show us the result (the new file), and then proceed only after we confirm.
3. Practical Exercise: The /new-migration Command
It's time to build the command. This exercise assumes you have a Python project using Alembic for database migrations. If you use a different tool (like django-migrations or another CLI-based system), you can adapt the command accordingly.
Your Task (approx. 30 minutes):
-
In your project's
.claude/commands/directory, create a new file namednew-migration.md. -
Paste the following template into your
new-migration.mdfile. This template combines everything we've discussed: YAML configuration, positional arguments, and bash command execution.--- description: "Generates a new Alembic database migration and stages it in Git." argument-hint: "[migration_message]" allowed-tools: - Bash(alembic:revision) - Bash(git:status) - Bash(git:add) --- # Task: Generate and Stage a New Database Migration You will perform a three-step process to create a new database migration. ## Context & Arguments - **Migration Message ($1):** A short, descriptive message for the migration, enclosed in quotes. Example: "add user auth tables". ## Step 1: Generate the Migration First, run the Alembic command to autogenerate a new revision file based on changes to the SQLAlchemy models. Use the provided migration message. !alembic revision --autogenerate -m "$1" ## Step 2: Report Status After the command runs, report back to me. Show me the output of `git status` so I can see the new migration file that was created. The current git status is: !``git status`` ## Step 3: Plan and Await Confirmation Analyze the output from `git status`. Identify the path to the newly created migration file under 'Untracked files'. Propose a `git add` command to stage ONLY that new migration file. Do not execute it. Wait for my explicit confirmation before you run the `git add` command. -
Restart Claude Code: If you have a session running, exit and start a new one to ensure it loads your new command.
-
Test Your Command:
-
First, make a small change to one of your SQLAlchemy models (e.g., add a new column to a table). This is necessary for Alembic's
--autogenerateto detect a change. -
Now, run your new command in the Claude Code chat. Make sure to wrap your message in quotes.
/new-migration "add bio field to user model"
-
-
Verify the Workflow:
- Claude should first execute the
alembic revisioncommand. - Next, it should display the output of
git status, which will show the new migration script in themigrations/versions/directory as an untracked file. - Finally, it should propose the correct
git add <path_to_new_migration_script.py>command and wait for your "yes" or "go ahead" before executing it. - Confirm that it stages the file correctly.
- Claude should first execute the
4. Sharing Your Automation Engine
The "share" part of today's learning outcome is the easiest but perhaps most impactful part. Because you created this command in the project's .claude/commands/ directory, it's now part of your codebase.

By committing .claude/commands/new-migration.md to your Git repository, every team member who pulls the latest code automatically gets this new capability.
To see this principle of sharing articulated, let's watch a brief clip.
Claude Code's Creator Does This Before Every Single Project
In this short segment from a video by AI LABS, Boris, the creator of Claude Code, confirms that this is exactly how he shares commands with his team.
Watch from 08:12 to 08:37. The key takeaway is how storing slash commands in the .claude/commands subfolder makes them committable and shareable, turning individual productivity gains into team-wide standard practices.
This simple act of git commit transforms your personal automation into a reusable, shareable asset for your entire team, standardizing workflows and boosting collective productivity.
Conclusion
Excellent work. You've now moved beyond simple code generation and built a sophisticated slash command that acts as a controlled operator for a critical development task. This command integrates with external tools like Alembic and Git, accepts dynamic input, and follows a safe "plan-then-execute" workflow.
Key Takeaways:
- Slash commands can execute shell commands using the
!commandsyntax. - The
allowed-toolsfrontmatter is a vital security measure to grant explicit permissions. - For state-changing operations, design your command prompts to first create a plan and then wait for user confirmation before executing.
- Placing commands in the project's
.claude/commands/directory makes them shareable with your team via version control.
You're building a powerful, customized automation layer on top of your existing development tools, which is a core skill for advanced AI-assisted coding.
Next Up:
So far, you have to explicitly call your slash commands. But what if automation could be triggered implicitly by your actions? In the next lesson, we will explore hooks, which allow you to run scripts automatically before or after certain events, paving the way for even deeper workflow integration.