Creating Skills
SaaSFoundryAI ships a curated catalogue (see Core Skills, Tool Skills), but your project will have conventions unique to it — domain language, a custom release script, a deployment checklist that only makes sense for your infrastructure. Write a custom skill to teach your coding agent those conventions so every contributor (human or AI) follows them identically.
This page walks through building a skill inside your project. For contributing a skill back to SaaSFoundryAI itself, see Contributing.
What is a skill, in 20 seconds
A skill is a directory containing at minimum a SKILL.md file. Its location depends on the declared host: .claude/skills/ for Claude Code and .agents/skills/ for coding agents that use the portable surface, including Codex. The SKILL.md is:
- A markdown reference the coding agent reads in full before acting
- Plus a YAML front-matter declaring the skill's metadata (name, description, allowed tools, auto-trigger keywords)
Discovery depends on the host. Some hosts match trigger keywords or support /skill-name; others follow the project instructions and read the applicable skill explicitly.
Minimal example: deploy-preview
Let's build a skill that encapsulates your team's "deploy a preview environment" ritual.
1. Create the directory
For a multirepo project, place it under the app that owns the behaviour (apps/api/ if it's API-specific, apps/web/ for frontend). For a monorepo, use the root. Claude Code example:
mkdir -p .claude/skills/sf-deploy-previewFor a portable skill shared with Codex, use .agents/skills/sf-deploy-preview and reference it from AGENTS.md. If multiple agents consume the skill, keep one source of truth and reviewed entrypoints instead of allowing two copies to drift.
Note the sf- prefix — keep it consistent with SaaSFoundryAI's convention to avoid collisions with globally installed skills.
2. Write SKILL.md
---
name: deploy-preview
description: Deploy a preview environment for the current feature branch. Auto-triggers on "deploy preview", "spin up preview", "ephemeral env". Use during Human testing on the draft PR.
model: haiku
allowed-tools: Bash(gh :*), Bash(fly :*), Bash(git :*)
---
# Deploy preview
Spin up a short-lived preview environment on Fly.io for the current feature branch, comment the URL back on the ticket, and (if the Human testing status wants it) post a screenshot.
## Preconditions
- Clean working tree (`git status` empty)
- Pushed branch (`git log origin/$BRANCH..HEAD` empty)
- Ticket is in Human testing (checked via `$CLI status <N>`)
## Workflow
1. Resolve the ticket number from the current branch name (`feature/N-*`)
2. Build the preview Docker image: `docker build -t myapp-preview:$BRANCH`
3. Deploy to Fly.io: `fly deploy --app myapp-preview-$BRANCH`
4. Wait for the health check: poll `https://myapp-preview-$BRANCH.fly.dev/api/health` until 200
5. Post the URL as a comment on the GitHub ticket: `gh issue comment <N> --body "Preview: https://..."`
6. Post in Slack #engineering if the repo contains a .saasfoundry-slack webhook
## Rollback
If step 3 fails, run `fly apps destroy myapp-preview-$BRANCH` before exiting.
## Rules
- Never deploy to production from this skill
- Never skip the health check wait — the URL posted must actually respond
- The preview name includes the branch, so concurrent previews don't collideThat's it. No TypeScript, no JSON config — just Markdown the agent reads. On hosts that support it, allowed-tools restricts what the skill can invoke. Fields such as model are host-specific and must not be presented as portable guarantees.
3. Optional: add a CLI script
For skills that wrap complex CLI interactions, keep the command logic in a shell script alongside SKILL.md and reference it from the workflow:
.claude/skills/sf-deploy-preview/
├── SKILL.md
├── deploy-preview.sh # The actual deploy script
└── README.md # Human-readable docsThen the ## Workflow section of SKILL.md points at the script:
1. Run `bash .claude/skills/sf-deploy-preview/deploy-preview.sh $BRANCH`This is the pattern used by sf-tool-github-projects, sf-tool-atlassian, and all SaaSFoundryAI-shipped tool skills.
4. Test by invocation
In Claude Code, try both invocation paths:
> /sf-deploy-previewshould load the skill and execute the workflow. Say naturally:
> spin up a preview environment for this branchshould hit the auto-trigger keywords and load the skill without the explicit / prefix.
In Codex or another host, inspect the declared profile with sf agents doctor, then request the capability in natural language. The existence of a file does not prove native discovery.
5. Commit
git add .claude/skills/sf-deploy-preview/
git commit -m "feat(#N): add sf-deploy-preview skill"The skill is now part of the project. Anyone who clones the repo — human or AI — picks it up automatically.
Writing a good SKILL.md
A few patterns from the SaaSFoundryAI-shipped skills that make them work reliably:
Front-matter: be specific in description
The description field is what the AI reads to decide whether to load the skill. Vague descriptions mean wrong loads (or worse, missed loads):
Not great:
description: Helps with deploymentsBetter:
description: Deploy a preview environment for the current feature branch. Auto-triggers on "deploy preview", "spin up preview", "ephemeral env". Use during Human testing on the draft PR.Include auto-trigger keywords inline when the selected host uses them.
Body: lead with preconditions
State what must be true before the skill runs. This lets Claude bail out cleanly when the world isn't in the right state instead of producing a half-done result:
## Preconditions
- Clean working tree (`git status` empty)
- Pushed branch
- Ticket is in Human testingThe Team and Solo presets enforce their shipped routes this way. A Custom route needs matching status documents and guard code before it can offer the same contract.
Body: explicit ## Workflow steps
Numbered steps, not prose. Each step should be one CLI invocation or one decision. If a step has substeps, extract it into its own section. This makes the skill debuggable — when something goes wrong, you know which step broke.
Body: ## Rules or ## Gotchas at the end
A few bullets on the surprising bits:
## Rules
- Never deploy to production from this skill
- The preview name includes the branch name, so concurrent previews don't collide
- Credentials are in `~/.claude/credentials/fly/` — never display or logThis is where tribal knowledge that would otherwise live in Slack DMs gets captured.
allowed-tools: minimise
Every tool you grant is a surface area. If a skill only needs git and gh, don't allow the full Bash tool:
allowed-tools: Bash(git :*), Bash(gh :*)When a skill needs to call an external process you don't want to globally whitelist, use a specific bash pattern.
Sharing a skill across the team
Your custom skill is a normal file in the repo. That means:
- Git tracks it. Anyone who pulls the branch gets the skill.
- Code review catches it. Teammates can comment on a skill like any other file.
sf updatepreserves it. It's outside the scaffold's file-hash map, so SaaSFoundryAI never proposes to overwrite it.
For a monorepo, you can share one skill across both apps/api and apps/web by placing it in the root .claude/skills/ — both apps pick it up.
For a multirepo, duplicate it across apps/api/.claude/skills/ and apps/web/.claude/skills/ if it applies to both. Consider a single source of truth inside one repo and a symlink in the other if you want to DRY it up, at the cost of multi-repo coordination.
Hooking a skill into the workflow
Custom skills can be invoked from inside sf-workflow transitions. For instance, you could teach sf-workflow to run sf-deploy-preview when transitioning to Human testing.
To do that, do not edit sf-workflow directly — sf update will overwrite your changes on the next upgrade. Instead:
- Create a pre-transition hook file next to the workflow skill:
.claude/skills/sf-workflow/hooks/pre-human-testing.sh - The hook receives the ticket number as
$1 - From the hook, invoke your custom skill or script
This keeps your customisations isolated from the scaffold code that sf update manages. Look at the shipped hooks/ directory — it contains a commented example.
Graduating a custom skill into SaaSFoundryAI
If a custom skill ends up useful across multiple projects you own, consider opening a PR to promote it into the SaaSFoundryAI catalogue. That way it ships with every new project, not just the one you built it in.
See Contributing for the PR checklist (duplicate into scaffolds/blueprints/api/.claude/skills/, scaffolds/blueprints/web/.claude/skills/, scaffolds/overlays/monorepo/root/.claude/skills/, plus tests and doc updates).
Next steps
- Read a few shipped
SKILL.mdfiles (cat .claude/skills/sf-git-commit/SKILL.md) — they are the best reference for what works sf skillreference — CLI for listing/describing skills- Skills System guide — conceptual overview