Agent-Assisted Blogging with a Claude Skill
7 min read
I’ve written a few times now about my GitHub Discussions powered blog. First the initial build, then the Astro loader, then scheduled posts. When I first built it, the big win for me was that I could write posts anywhere GitHub works, with no CMS and no content files to commit. What I didn’t expect was how well that same setup would suit AI agents.
Why It Works Well With Agents
Because every post is just a GitHub Discussion, “publishing a blog post” really just boils down to a few GitHub API calls to create a discussion and add some labels to it.
And that’s something tools like Claude Code already know how to do. Claude is very comfortable with the gh CLI and the GitHub GraphQL API. It doesn’t need a plugin, an integration, or an MCP server for my blog. The API it needs is one it already knows.
So now, when I’ve been working on something interesting, I can just say:
Draft me a blog post about what we just built.
And Claude has all the context. It’s been in the session with me. It knows what we changed, what we tried that didn’t work, and why we landed where we did. It writes the draft, creates the discussion, labels it as a draft, and hands me a link. I then open it up on GitHub, edit it into my own voice, and remove the draft label when I’m happy.
You might be thinking “so you’re just letting AI write your blog now?”, and that’s a fair question. The way I see it though, it’s still my knowledge and my ideas, Claude is just helping me pull them together into something shareable. Blogging has never come naturally to me, so having help with the structure means I end up sharing a lot more than I otherwise would.
Adding Structure With a Skill
The one thing Claude doesn’t know out of the box is the conventions of my particular blog. Which repo? Which discussion category? What does the front matter look like? How do drafts work? Which tags already exist?
That’s where a Claude Code skill comes in. A skill is just a markdown file that gets loaded when it’s relevant. Mine doesn’t contain any clever code. It’s really just a description of the rules:
- Posts are discussions in a given repo and category.
- Optional front matter controls the slug, description, and publish date.
- Labels are metadata:
state/drafthides a post,tag/*labels become tags, andseries/*groups posts together. - A future
publisheddate schedules a post. - A few example
ghcommands to point it in the right direction.
And that’s pretty much all it needs. Claude fills in the rest using what it already knows about GitHub, so the skill doesn’t need to explain how to use the API, only what my blog expects.
I think this is a nice general lesson for skills too. If an agent already understands the underlying tools, a skill doesn’t need to be big, it only needs to describe your own conventions on top of them.
The Skill
If you’re using the GitHub Discussions Blog Loader (or a similar setup), here’s a cleaned up version of my skill you can drop straight in.
Save it as ~/.claude/skills/blog/SKILL.md (or .claude/skills/blog/SKILL.md inside your site repo) and replace OWNER, REPO, example.com, and the category name with your own.
---
name: blog
description: Manage blog posts on example.com. Posts are GitHub Discussions with label-based metadata. Use when creating, drafting, editing, publishing, or managing blog posts.
argument-hint: "[create|edit|publish|list] [topic or discussion number]"
---
# Blog Management
Blog posts on example.com are **GitHub Discussions** in the `Blog Post` category
of `OWNER/REPO`, not files. The site fetches discussions at build time using
`github-discussions-blog-loader`.
## Front Matter
Posts support optional YAML front matter at the top of the discussion body:
```markdown
---
slug: my-custom-url-slug
description: A short description for SEO and social cards
published: 2026-03-15
---
Post content starts here...
```
| Field | Required | Description |
|---------------|----------|----------------------------------------------------------------|
| `slug` | No | Custom URL slug. Generated from the title if omitted. |
| `description` | No | SEO/social description. Truncated from the body if omitted. |
| `published` | No | Publish date (`YYYY-MM-DD`) or date and time (`YYYY-MM-DDTHH:MM:SS`). Defaults to discussion creation date. |
Title, author, and reading time come from the discussion itself.
Content after the front matter is standard GitHub-flavored markdown.
## Labels as Metadata
### State Labels
| Label | Effect |
|-------------------|------------------------------------------------|
| `state/draft` | Post is excluded from the build (invisible) |
| `state/scheduled` | Post is excluded from the build until its date |
To publish a draft, remove `state/draft` and trigger a site rebuild.
### Tag Labels
Labels prefixed with `tag/` become post tags (prefix stripped), e.g. `tag/ai` → "ai".
Multiple tags per post are fine. Always list existing labels first and prefer
reusing them. Only create a new `tag/*` label when nothing fits.
### Series Labels
Labels prefixed with `series/` group posts into a series. The label's
**description** is the series display name. One series label per post.
## Workflows
### Create a Draft (default)
Unless told otherwise, always create new posts as drafts.
1. Create the discussion in the `Blog Post` category with the post content
2. Add the `state/draft` label
3. Add relevant `tag/*` labels (and a `series/*` label if appropriate)
4. Reply with the discussion URL so the author can review it
### Publish
1. Remove `state/draft` from the discussion
2. Optionally set `published` in front matter to backdate
### Schedule
Set `published` to a future date, or date and time. (Only if your site handles
`state/scheduled` automatically. Otherwise, leave as a draft.)
## Tooling
Use the `gh` CLI. Write the post body to a temp file and pass it with `-F body=@file`
to avoid shell escaping issues.
```bash
# Get repo ID and discussion category IDs
gh api graphql -f query='{ repository(owner:"OWNER", name:"REPO") { id discussionCategories(first:10) { nodes { id name } } } }'
# Create a discussion
gh api graphql -F repositoryId=REPO_ID -F categoryId=CATEGORY_ID -F title="Post title" -F body=@post.md \
-f query='mutation($repositoryId:ID!,$categoryId:ID!,$title:String!,$body:String!) { createDiscussion(input:{repositoryId:$repositoryId,categoryId:$categoryId,title:$title,body:$body}) { discussion { id number url } } }'
# List recent discussions with labels
gh api graphql -f query='{ repository(owner:"OWNER", name:"REPO") { discussions(first:10, orderBy:{field:CREATED_AT, direction:DESC}) { nodes { number title labels(first:10) { nodes { name } } } } } }'
# Read a discussion
gh api graphql -f query='{ repository(owner:"OWNER", name:"REPO") { discussion(number:NUMBER) { id title body labels(first:10) { nodes { name } } } } }'
# List labels (with IDs)
gh api graphql -f query='{ repository(owner:"OWNER", name:"REPO") { labels(first:100) { nodes { id name } } } }'
# Add / remove labels
gh api graphql -f query='mutation { addLabelsToLabelable(input:{labelableId:"DISCUSSION_ID", labelIds:["LABEL_ID"]}) { clientMutationId } }'
gh api graphql -f query='mutation { removeLabelsFromLabelable(input:{labelableId:"DISCUSSION_ID", labelIds:["LABEL_ID"]}) { clientMutationId } }'
# Create a label
gh label create "tag/example" -R OWNER/REPO
```
A few things I changed from my personal version while cleaning it up:
- The
createDiscussionmutation is now included. My original skill didn’t have it and Claude worked it out on its own every time, but spelling it out saves a few round trips. - Drafts are the default. I always want to review a post before anything goes live, so the skill says so explicitly.
- The post body is passed as a temp file. Long markdown posts full of backticks and quotes are a pain to escape on the command line, so passing the body as a file sidesteps that completely.
- There’s no hard-coded tag list. Mine originally listed my existing tags, but it quickly went out of date, so instead it tells Claude to look them up.
- Scheduling supports a time as well as a date, so you can have a post go live at 8am rather than midnight.
Teaching It Your Voice
The skill above is deliberately minimal, but the one thing I’d really recommend adding is a section on your writing style.
My first attempt at this was to tell Claude to read a few of my recent posts before drafting. The problem was, a fair number of my recent posts had already had some AI help, so Claude was learning to sound like… well, Claude. Things like em dashes, punchy one line paragraphs, and words like “genuinely” were creeping in, none of which I’d have written myself.
So instead I now point it at three specific older posts that I know were written entirely by me, before I started using AI for drafting. I also had Claude read those posts and write up a list of style notes (how I open a post, how I structure steps, how I tend to end) as well as a list of things to avoid. Being able to fetch the posts through the same API makes this really easy.
If you do the same, I’d suggest picking your examples by number rather than just saying “recent posts”, that way it always reads the same posts and you know exactly what it’s learning from.
In Summary
Building my blog on GitHub Discussions was meant to make writing easier for me, and it turns out it made it easier for my AI tools too. This very post started life exactly that way. I asked Claude to draft it, it created the discussion, and I took it from there.
If you’re running your own GitHub Discussions powered blog I hope the skill comes in handy, and I’d love to hear if you’ve found other ways of fitting AI into your own blogging workflow.
Until next time 👋