We Turned Our Whole Publishing Process Into Claude Code Skills
Install takes two minutes. The part worth learning is skills, and the guides skip it. Six of them now produce this site: 5 articles, 6,883 words, and 2 mistakes that cost us an afternoon.
The WJS Desk
Sep 1, 2026 · updated 4 hours ago · 7 min read

Every guide to Claude Code stops at the same place: install it, ask it to fix a bug, marvel. Then you close the terminal and nothing about tomorrow is different.
The part worth learning is skills, and almost nothing is written about them. We built this site's entire content pipeline as six of them. Five published articles and 6,883 words later, here is the setup, and the two things that cost us an afternoon.
What you will end up with
A working install, then a skill of your own: a markdown file that turns a procedure you keep re-explaining into a command you type once. Ours are 40KB of markdown across ten files, and they are the reason this site publishes anything at all.
Budget forty minutes. The install is two.
Prerequisites
A terminal, a code project, and a Claude subscription (Pro, Max, Team or Enterprise), a Claude Console account, or access through Bedrock, Google Cloud or Microsoft Foundry.
No API key needed if you have a subscription. That distinction matters more than it sounds: on a subscription the work is covered by what you already pay, so a skill that runs ten times a day costs nothing extra.
Step 1: install
The native installer is the recommended path on macOS, Linux and WSL:
curl -fsSL https://claude.ai/install.sh | bashWindows PowerShell:
irm https://claude.ai/install.ps1 | iexHomebrew works too, and it is what this machine has:
brew install --cask claude-codeThe tradeoff nobody mentions: native installs update themselves in the background. Homebrew and WinGet installs do not. If you go the Homebrew route you are on the hook for brew upgrade claude-code, and there are two casks: claude-code tracks stable and runs about a week behind, claude-code@latest ships immediately.
Confirm it took:
claude --version
# 2.1.252 (Claude Code)Step 2: log in and open a project
cd /path/to/your/project
claudeFirst run prompts you to authenticate in the browser. Credentials persist, so this is once. Inside a session, /login switches accounts.
One thing to know before you type anything: on Pro, Max and Team plans, interactive sessions start in auto mode, where a classifier approves most actions instead of you. Press Shift+Tab to cycle permission modes. On a repository you care about, know which mode you are in before you ask for anything destructive.
Step 3: the part everyone skips
The quickstart suggests asking what the project does. Do that once, then move on, because the real return is elsewhere.
A skill is a markdown file that Claude loads when relevant, or that you invoke with a slash command. Two locations:
| Scope | Path | Available in |
|---|---|---|
| Personal | ~/.claude/skills/<name>/SKILL.md | Every project |
| Project | .claude/skills/<name>/SKILL.md | That project only |
The file needs frontmatter and nothing else:
---
name: wjs-repo
description: Write a repo spotlight. Pulls live star data, reads the
actual source, and queues a draft for review.
---
# Repo spotlight
A repository is trending. We read the source, try it on something
real, and say whether the stars are justified.
## What qualifies
...That is a real one from this repo. Save it, and /wjs-repo exists. No restart: Claude Code watches the skill directories and picks up changes.
Pro tip: the name in the frontmatter must match the directory name. Ours did not on the first attempt and the skill simply did not appear, with no error explaining why. Check that first when a new skill fails to show up.
Step 4: make the skill worth having
A skill that says "write a good article" is a wasted file. The ones that earn their place encode judgement you would otherwise have to repeat.
Ours specify where to research per vertical, what disqualifies a story, the required structure, and a rule against fabricating tests. That last one exists because the voice asks for "we benchmarked it", which creates real pressure to invent a benchmark. The skill forbids it and requires attribution instead.
The measurable difference: a skill that says "be specific" produces nothing. A skill that says "if the draft never says we, it is a press-release rewrite, spike it" produces a different article.
Step 5: the thing that actually changed our output
Instructions in a skill are advice. Claude follows them well and then, occasionally, does not.
So we wrote the rules twice: once as prose in the skill, once as executable code the pipeline runs before anything can be published.
npm run draft:check drafts/my-article.json
BLOCK no-em-dash: title contains 1 em dash
BLOCK first-person: never says "we"
BLOCK too-thin: 340 words, needs at least 700
BLOCK missing-hot-take: every article must take a positionThose four fired on a real draft. The prose version of the same rules was already in the skill and had not prevented any of them.
A rule you can run catches what a rule you can read does not.
This is the single highest-leverage thing we did, and it generalises well past writing. If your skill produces code, have it run the linter. If it produces config, validate the schema. The gate is the part that holds.
Step 6: share the parts every skill needs
We started with one skill covering everything and it produced the same article shape regardless of subject, which was the opposite of the goal.
Splitting it into five verticals raised a duplication problem: the voice rules belonged in all of them. The fix is that a skill directory can hold supporting files, and a skill's body only loads when used, so long reference material costs nothing until needed.
.claude/skills/
├── wjs-pipeline/
│ ├── SKILL.md
│ └── references/
│ ├── voice.md # how we write
│ ├── fields.md # the output shape
│ └── workflow.md # check, push, report
├── wjs-news/SKILL.md
├── wjs-repo/SKILL.md
└── wjs-tutorial/SKILL.mdEach vertical opens with an instruction to read all three references first. The rules exist once. Change the voice and every skill changes with it.
What broke
The frontmatter name mismatch. Covered above. Silent failure, ten minutes lost.
A safety guard that broke the build. We set the admin app's build script to exit with an error, reasoning that it should never be built by accident. The Cloudflare adapter shells out to npm run build internally, so the deploy died on our own guard. If you make a script fail deliberately, check what else calls it.
The gate that could not see the thing it guarded. Our first secret scanner grepped build output for the string service_role. We tested it by injecting a real Supabase service role key into a build, and it passed. That key is a JWT: the role sits in a base64 payload and the literal string never appears. The scanner now decodes every JWT it finds.
The lesson generalises: test a guard by giving it the thing it is supposed to catch. A guard that has never failed has never been verified.
What it actually cost, in numbers
Worth being concrete, because "it saves time" is the kind of claim that should come with a measurement.
| Before skills | After | |
|---|---|---|
| Skill files | 1 monolith | 6 skills, 10 files |
| Total markdown | ~7 KB | 40 KB |
| Article shapes produced | 1, regardless of subject | 4 distinct |
| Rules enforced automatically | 0 | 11 |
The 40KB is not a cost. A skill's body loads only when invoked, so reference material sits on disk for free until something needs it. That is what makes it reasonable to write the voice guide at length rather than compressing it into three bullet points that lose the nuance.
The row that matters is the last one. Eleven rules that a human previously had to remember are now checks that fail loudly, and the drafts that trip them never reach anyone.
Common mistakes
- Writing a skill for something you do once. The threshold is a procedure you keep re-explaining. Below that, just ask.
- Putting reference material in CLAUDE.md. That file loads every session. A skill's body loads only when used, so long material belongs in a skill.
- Instructions with no gate. If it matters, make it executable.
- One skill for several jobs. Ours produced identical output across different tasks until it was split.
- Not checking the permission mode before asking for something destructive.
How this compares to the alternative
OpenAI's Codex CLI covers similar ground, and we are not going to pretend we ran a fair head-to-head. What we can say precisely is what made the difference for this project, and it was not model quality.
It was that a skill is a plain markdown file in the repo. It gets reviewed in pull requests, it moves between machines, and a teammate can read the rules without being told. Our skills are 40KB of markdown, and that portability is why we ended up publishing them as a second repository.
If your workflow is one-off questions, this distinction will not matter to you at all.
What we would not do yet
We have not automated any of it. The skills run when we type them, because content generation on a subscription with no scheduled trigger means no credentials ever leave the machine. That was a deliberate call, and it holds until the volume justifies revisiting.
We also would not use this for anything where the output cannot be reviewed. Every draft ours produces lands in a review queue and a human approves it. The gates are good. They are not a substitute for reading the thing.
Next steps
Write one skill for the procedure you have explained twice this month. Keep it under a page. Then add the check that proves it worked.
If it survives a week, split the shared parts into a references folder before you write the second one. That is the step we did in the wrong order.


