Content Marketing

How to Build a Content Refresh Workflow in Claude Code

Refreshing old posts takes your search data, voice and rules, not a clever prompt. Here is the Claude Code folder and skill that hold them.

Author:
Shanal Govender
Contributors
Vlad Shvets
Date:
October 29, 2026

Pasting an old blog post into Claude Code and asking for a refresh gets you a smoother version of the same post. It cannot know what the page is shown for in search today, which of your pages it should link to, or what your brand stopped saying two years ago. Those decisions are the refresh, and none of them is in the post.

A refresh used to be one person, one checklist and an afternoon a post, each done a little differently. The fix is a folder Claude Code opens first. It holds your posts, your Search Console data and your voice, plus a procedure written as separate passes and a list of decisions the model may not make. One skill then runs that procedure on any post.

Empact Partners plans and runs go-to-market for B2B software companies from inside their own teams, and this is how its Content Marketing work refreshes a library. We ran it on one of our oldest posts: Claude Code did the mechanical passes in minutes and returned the part worth publishing as fourteen questions. Before you start, you need:

Claude Code. On Claude’s Pro plan or above. Pro is $17 a month on a yearly plan or $20 month to month, and Claude Code is included. Our whole run took under eight minutes of model time, which at API rates comes to $2.75.
A read-only CMS token. The export reads your posts through your CMS’s API, so it needs a token that can read the blog and cannot write to it.
From someone else. If you do not run the CMS yourself, ask whoever does for that token, with read access to the blog collection and nothing more.
Search Console. Access to your site’s property, enough to export the Performance report.
On your machine. git and Python 3.
Time. An afternoon for the folder and the first post, most of it reading. After that a post takes minutes of model time, and the reading is the job.

This builds the workflow and runs it on one post. It does not decide which of your posts deserve to exist, write the evidence only your team holds, or publish anything, and the files you are about to write say so.

Build the Folder Claude Code Opens Before It Touches a Post

Claude Code works inside the folder you start it in, and what it knows about your blog is whatever that folder holds. The first half of the job is filling it. We ran every step in a clean Claude Code profile, headless, approving up front what an interactive session asks about as it goes.

Step 1: Make an empty folder a git repository

Open a terminal, create the folder, and turn it into a repository:

mkdir content-refresh
cd content-refresh
git init

Every change Claude makes from here becomes a commit you can read or undo, and the first commit will be your posts exactly as they are live. We keep a plain-text copy like this in version control before any agent edits content we look after, because it is how a page gets put back in minutes when an automated edit goes wrong.

Step 2: Export every post to markdown, once

Start Claude Code in the folder by typing claude, and paste this. Ours names Webflow, our collection and our blog’s address. Change those three to your CMS and your own blog.

This folder is going to hold our blog so we can refresh old posts. Write a Python script at scripts/export_posts.py that exports every published post from our Webflow blog into posts/, one markdown file per post, named <slug>.md.

Use the Webflow Data API v2 with the token in the WEBFLOW_TOKEN environment variable, and only ever send GET requests. The blog is the CMS collection called "Empact Zone". Read the live (published) items, not drafts.

Each file starts with front matter: title, slug, url, published date, last updated date, and the meta description. The post body follows, converted from HTML to markdown, keeping headings, lists, links, bold and images. Our posts live at https://www.empact.partners/empact-zone/<slug>.

Do not run the script. I will read it and run it myself.

Claude writes the script and stops, because the prompt says so. Open scripts/export_posts.py and read it before it runs: every request it sends should be a GET. Ours sent all of them through one function, and found our collection and its fields on its own.

Then run it yourself, with the token in your own terminal, so the model never holds a key to your CMS:

python3 -m venv .venv
.venv/bin/pip install requests markdownify
WEBFLOW_TOKEN=your-read-only-token .venv/bin/python scripts/export_posts.py

The script prints how many posts it wrote, and that number should equal the posts live on your blog. Ours printed 116 posts, the live count that day. If yours is short, ask Claude which items the script skipped and why before anything is built on the export.

Then open one file and check the front matter. Webflow keeps no first-published date, so our script fell back to the last time each post was republished, and on our oldest post the published date came out later than the last-updated one.

Leave it alone. Dates are on the list of things the model may not touch, and this is one reason. Commit the export untouched, because it is your backup:

printf '.venv/\n__pycache__/\n' > .gitignore
git add .gitignore scripts posts
git commit -m "Export 116 live posts from Webflow, untouched"

Step 3: Put your Search Console data in the folder

Make a folder called search-console and save your export into it. The CSV from the Performance report’s Export button works, and so does a pull through Search Console’s API, which is what ours was: every Empact Zone page by day for sixteen months, made by a colleague for another piece.

Ours had page rows and no query rows, and it showed. Claude could say when a page started falling and could not say which searches it lost, so it checked what ranks today with live web searches instead.

If you can, also export the searches your first post is shown for: filter the Performance report to that one page and export its Queries tab. Then commit the folder.

Step 4: Draft the voice file from your best posts, then edit it

The voice pass needs something to check sentences against. Name the three posts that sound most like you, and paste this:

Read posts/saas-blog-200-articles-no-leads.md, posts/stop-posting-linkedin-like-founder.md and posts/link-magnets-for-off-page-seo.md. These three sound most like us.

Draft context/voice.md: how we write, as rules a writer can check a sentence against, with one short example quoted from these posts under each rule. Keep it under 60 lines. Only describe what these three posts do. Do not add rules of your own, and do not change any post.

Claude read the three posts and wrote context/voice.md: seventeen rules, each with a quoted example. It described our posts faithfully, old habits included, and seven of the seventeen rules were habits we have since stopped on purpose:

Rule Claude wroteThe example it quotedWhat we did
Name the real problem by correcting the obvious one“This is not a traffic problem. It’s not an SEO problem.”Struck. Say the real problem.
Contrast the weak option with the strong one in a single sentence“Traditional outreach gives you a link. Link magnets give you a pipeline.”Struck
Close with imperatives, then one plain call to action“Audit ruthlessly. Refresh strategically. Kill what doesn’t serve you.”Struck. End on the next thing worth doing.
Put jokes and asides in parentheses, and keep them short“(If a blog post publishes and nobody links to it, does it even exist?)”Kept

Edit the file by hand, and commit Claude’s draft and your edit separately, so the history shows what a person changed. We kept ten rules and added five house rules: no em dashes, consultancy and partners rather than agency and clients, American spelling, a paragraph length, and a short list of words we never use.

We also moved the stopped habits into a section that tells the model to list any it finds rather than rewrite the passage around them. In our own content work the voice comes from a person’s own material and a person’s edit, never from a tone instruction typed into a chat.

Write the Rules and the Procedure Into Files, Not the Chat

A chat forgets. Tell Claude in one conversation to leave the dates alone, and the next conversation never heard it. Claude Code reads a file called CLAUDE.md at the start of every session in its folder, so what must hold every time goes there, and the procedure gets a file of its own.

Step 5: Write CLAUDE.md, with the decisions the model may not make

Save this as CLAUDE.md in the folder. It is our file after the one fix the run forced.

# Content refresh workspace

This folder holds our blog so we can refresh old posts.
Nothing in it is live. A person publishes; you never do.

## What is here

- posts/ : every live post as markdown, exported once
  from the CMS. The first commit is the untouched
  backup.
- search-console/ : our Search Console export. Read it
  with a script; never load it whole.
- context/voice.md : how we write. Check every sentence
  you change against it.
- REFRESH.md : the refresh procedure. Follow it, step by
  step, whenever you refresh a post.
- refreshes/<slug>/ : your working notes for each
  refresh.

## Decisions that belong to a person

Stop and ask before any of these. Write down what you
found and what you would do, then wait.

- Retiring a post, merging two posts, or redirecting one
  URL to another.
- Changing a slug, a URL, or any date in the front
  matter.
- Cutting a section, or more than one paragraph anywhere
  in a post.
- Changing what a named person is quoted as saying.
- Adding a statistic, a result, a customer, an example,
  or a quote that is not already in the post or
  somewhere in this folder.

## Always

- One commit per pass, with the pass in the message, so
  a person can read or revert one pass.
- One post at a time. Never edit a post you were not
  asked to refresh.
- Never write to the CMS or publish anything. Reading
  the web is fine; writing outside this folder is not.
  Keep any script you write in scripts/.

The list under “Decisions that belong to a person” is the part to copy whole. Each item is on it because the model would make the call confidently and the cost would land on somebody else:

Retire, merge, redirect. A URL carries the links and rankings it has earned, and merging or deleting it can throw them away. Since 2024 our refresh rule has been never to publish a new post and delete the old one. These are also the calls partners bring us. Our work with Brame opened by pruning and revamping the old blog, before a single new topic cluster, and organic traffic grew 700% within the first year.
Slugs, URLs and dates. A changed slug is a dead link wherever the old one was shared. A new date is a call on how much the update changed, and the model is in no position to grade its own edit.
Cuts. Asked to tighten, a model cuts, and the paragraph it removes may be the one your sales team sends to prospects.
A named person’s words. A quote belongs to the person whose name is under it. Our post carried two colleagues’ quote cards, and both left the refresh exactly as they went in.
Numbers nobody gave it. A refresh is where invented evidence gets in: asked to strengthen a claim, a model reaches for a statistic, a result or an example.

Our first version of the last line read “never send anything outside this folder.” The procedure asks Claude to check whether every link still resolves, so on its first run it refused to open the call-to-action link, said why, and asked us to check it by hand.

Two of our own instructions disagreed, and it obeyed the stricter one. Fix that kind of clash in the file and not in the session: our line now says reading the web is fine and writing outside the folder is not.

What Reddit says

Asked whether a refreshed post should get a new publish date, r/SEO split, and the most useful answer tied the date to the size of the change. A content lead in the same subreddit described what a model does with a number nobody gave it.

“If the update is substantive, yes. Google will likely ignore minimal updates & adding 50 spaces doesn’t count.” — r/SEO, Jun 2026
“yesterday it confidently quoted a 2026 hubspot state of marketing report with specific percentages that turned out to be totally fabricated when i checked.” — r/SEO, Jun 2026

Step 6: Write REFRESH.md, the procedure as separate passes

Save this as REFRESH.md, and commit both files:

# Refresh procedure

Run these steps in order on the one post you were given.
Each step ends in something written down. Keep
refreshes/<slug>/log.md as you go: one line per change,
naming the pass that made it.

## 1. Diagnose (no edits)

1. Read the post and its front matter.
2. From search-console/, pull this URL's clicks,
   impressions and average position by month for the
   whole export. Say whether it is growing, flat or
   falling, and since when.
3. Name the question the post answers, in the words a
   searcher would type. Search the web for it and read
   the top results: what kind of page ranks now (a
   guide, a list, a tool page, a definition), and what
   they cover that this post does not.
4. List every page in posts/ that answers the same
   question. If one answers it better, say so.
5. Write refreshes/<slug>/diagnosis.md ending in one
   verdict: refresh, leave alone, or a person must
   decide (retire or merge). Then stop and show the
   person the diagnosis. Do not edit the post until they
   say go.

## 2. Health check (no edits)

Add a health check to diagnosis.md, as a list:

- every link, and whether it still resolves; any link to
  a competitor's page on the same question
- headings: a skipped level, or a heading with only one
  subheading under it
- what has aged: years, "this year", tools, prices, job
  titles
- numbers with no source, and numbers more than two
  years old
- images with no alt text
- the call to action, and whether it still points at
  something we offer

## 3. Five passes, one commit each

Run each pass over the whole post, commit it, then start
the next. Do not fix a later pass's problem early: one
pass that tries to do everything does none of it well.

1. Mechanics: spelling, grammar, punctuation, broken
   markdown. Commit "pass 1: mechanics".
2. Readability: split long paragraphs, turn real lists
   into lists, fix the heading ladder. Commit "pass 2:
   readability".
3. Internal links: search posts/ for our pages on what
   this post mentions, and link two to five words
   already in the sentence. Remove links to competitors.
   Commit "pass 3: internal links".
4. Voice: check every sentence against context/voice.md
   and rewrite only the ones that break a rule. Where
   you find a habit we have stopped, list it rather than
   rewrite the passage around it. Commit "pass 4:
   voice".
5. Original content: mark where the post makes a claim
   only we could support, and where it needs an example,
   a result or a quote it does not have. Do not write
   them. Put each one in refreshes/<slug>/questions.md
   for the person, with where it would go. Commit "pass
   5: original content".

## 4. Visuals and calls to action

List the images to replace and the ones missing. Edit
the call to action only if it names something we no
longer offer; otherwise list it. Commit "visuals and
CTAs".

## 5. Hand back

Finish with the verdict, one line per pass saying what
changed, the open questions, and the command that shows
the whole refresh as one diff.

The diagnosis ends in a verdict and a stop, because a post that should be merged gains nothing from five rounds of editing.

The five passes then run one at a time, a commit each, which is the editing drill our content team has run since 2024. A reviewer asking “is this a clean sentence” and “does this sound like us” in the same read answers neither well, and separate commits let you read or undo the voice pass without touching the links.

Run It on One Post and Read Every Pass

The post we ran it on is a 2024 piece of ours on the mistakes that sink a SaaS go-to-market plan: about 1,800 words, eleven images, and two colleagues’ quote cards. It was written two years before the rules in our voice file.

Step 7: Ask for the refresh, then read the diagnosis before you say go

Start a fresh Claude Code session in the folder and give it the post and the file. This was our whole prompt:

Refresh posts/saas-gtm-death-spiral.md. Follow REFRESH.md.

Claude read the procedure, the voice file and the post, summed our Search Console rows by month, and ran two web searches for what ranks today. Then it checked our other posts for the same question, wrote its diagnosis to a file, committed it and stopped.

The post was untouched, and its month-by-month table matched our own count to the row:

Line chart of monthly Google search impressions for our 2024 post on SaaS go-to-market mistakes, rising from 27 in June 2025 to a peak of 334 in February 2026, then falling every month to 100 in August and 43 in September 2026.
Impressions by month for the one URL, summed from our Search Console export. October 2026 is left off because the export ends on the 11th.

What it found, from that diagnosis file:

What it checkedWhat it found
The trendFalling since February 2026, from 334 impressions a month to 100 by August, and 13 clicks in 17 months
What ranks nowNumbered lists of mistakes with a fix under each, and essays on go-to-market fit. Both cover retention and symptoms a reader can check, such as rising acquisition cost and churn. Ours covers neither.
Our other postsOne newer post links to it for its title phrase. None answers the question better.
HealthNo internal links, no useful alt text on any of its 11 images, two sections making nearly the same point, and two 2024 lines about AI
VerdictRefresh

Two lines in it were wrong, which is why a person reads the diagnosis before saying go. Its own table summed to 13 clicks while its sentence said 14. And it called September a partial month because only 19 September days had rows, when a day with no impressions has no row at all.

Reply in the same session, corrections first. We fixed both in one message, told it to leave the dates and both quote cards exactly as they were, and said go.

What Reddit says

A content lead at a B2B SaaS company described the refresh that skips this read:

“I went through fourteen of our best performers in April, rewrote the intros, added sections, updated the numbers, and about nine of them slid back within six weeks or so.” — r/content_marketing, Aug 2026

Step 8: Read the passes one commit at a time

Claude ran the five passes and the visuals check, committed after each, and finished with one line per pass saying what changed. Read them in git, one commit at a time, before you read the post whole:

git log --oneline
git show 4b1428a -- posts/saas-gtm-death-spiral.md
git diff 6f5e231 HEAD -- posts/saas-gtm-death-spiral.md

The first lists every commit with its short hash. The second shows one pass, here our pass 1. The third shows the whole refresh as one diff, and it is the command Claude printed when it finished. Your hashes will differ.

One real change from each pass, before and after:

PassBeforeAfter
1. MechanicsLayoffs, can be counter-productive.Layoffs can be counterproductive.
2. ReadabilityThree sentences in a row about junior hires“Junior hires tend to bring three problems:” and a three-item list
3. Internal links“Both VC-funded and bootstrapped companies need to remember…”The same words, linked to our post on how the two kinds of company plan go-to-market
4. Voice“…is crucial—it helps guide decisions…”“…is crucial. It helps guide decisions…”
5. Original contentNo change to the postFourteen questions in a separate file

Counted in lines, the passes look like this:

Bar chart of lines changed per commit in our refreshed post: mechanics 13, readability 22, internal links 2, voice 10, original content 0, and the person's answers 7.
Each bar is one commit on the post, the larger of lines added and lines removed. The voice pass also listed nine habits it was told not to rewrite.

Mechanics and readability took the most edits and the least judgment. Original content took none, by design: what would make the post worth reading again was not in the post or anywhere in the folder, so the model asked for it instead of writing it.

The pass boundary leaked once. Pass 2 split a paragraph and left five in a row. Pass 4 fixed it and wrote in its log that the miss was pass 2’s, which is what the log is for. One voice edit also read worse than the line it replaced, so we undid it.

The part of a refresh worth publishing is the part no folder holds: what your team has seen since the post went live.

Step 9: Answer the questions only you can answer

Pass 5 wrote fourteen questions into a file beside the post, each with the line it belongs on. A sample:

QuestionWhere it goesStatus
How many partners have we seen in this spiral, and at what stage? Claude found a partnership count in a newer post of ours and asked before reusing it.The paragraph naming the spiralOpen, for the partner record
Neither the section on copying competitors nor the one on cross-niche approaches has an example.After the first paragraph of eachOpen
The ranking pages list symptoms a reader can check. Do we want that section?A new sectionOpen: new content, so a person supplies or approves the numbers
Are the 2024 lines about AI still our view?The opening listAnswered

Answer them in the same session, the way you answered the diagnosis. We answered the last one, along with four of the habits it had listed and the voice edit we undid. Our view has changed since 2024: our content is drafted with AI now, and a consultant owns the angle, the material nobody else has, and the sign-off.

The mistake in that list is the missing owner, not the tool, a position no file in the folder held. “Get an intern to generate 20 articles with ChatGPT” now reads “Let AI draft it, and leave nobody to own the angle, the material no one else has, or the sign-off.” One more commit, and the refresh stood at 38 lines in and 46 out.

The other questions stay open on purpose. They need what happened across our partnerships, which no export carries, and answering them is where a consultant’s hour goes on a partner’s library: the result that proves a section, the example a partner has cleared, the numbers behind a symptom list.

Shanal Govender
Senior GTM Consultant @ Empact Partners
A refresh that comes back with questions is working. The fourteen my 2024 post raised are the ones partners hire a consultant to answer: which result proves a section, which example a partner has agreed to see in print, and whether a line from two years ago is still what we think. A team that copies the file and skips the questions has automated the tidy part of the job.

If nobody on your team owns that hour, that is the point to book a call with me.

Save It as a Skill So the Next Post Is One Command

Everything so far ran from a prompt that named the procedure. A skill turns that into a command the folder knows, so nobody has to remember which file to point at.

Step 10: Ask Claude to save the procedure as a skill that points at your files

Paste this:

Turn our refresh procedure into a skill, so the next post is one command: /refresh-post posts/<slug>.md

Put it in .claude/skills/refresh-post/SKILL.md. The procedure stays in REFRESH.md and the rules stay in CLAUDE.md. The skill sends you to them and does not copy them, so there is only one copy of each to keep up to date. Keep it short, and commit it.

Claude Code treats .claude/ as its own settings and will not write there without your say-so. In our run it reported “Claude requested permissions to write to … SKILL.md, but you haven’t granted it yet,” wrote nothing, and printed the file instead.

In an interactive session that is a permission prompt: approve it, then commit the file. This is the one it wrote, saved as it came:

---
name: refresh-post
description: Refresh one blog post by following the project's refresh procedure. Use as /refresh-post posts/<slug>.md
argument-hint: posts/<slug>.md
disable-model-invocation: true
---

Refresh the post at `$ARGUMENTS`. If no path was given,
ask for one.

1. Read `CLAUDE.md`. Its rules apply throughout,
   especially the decisions that belong to a person.
2. Read `REFRESH.md` and follow it step by step, in
   order, on this post only. Read it fresh each time; do
   not work from memory.
3. Take the slug from the file name. Keep notes in
   `refreshes/<slug>/`.
4. Stop wherever `REFRESH.md` or `CLAUDE.md` says to
   stop and wait for a person.

The skill stays short because it repeats nothing: the procedure lives in REFRESH.md and the rules in CLAUDE.md, one copy of each to keep current. Claude also chose disable-model-invocation: true on its own, so the skill runs only when somebody types it.

Keep it that way as the rules grow. When every rule lives in one skill file, contradictions pile up before anyone notices, and our run produced one inside the first hour: two of our own files disagreed about the web.

The Empact Zone is written the same way: a short skill opens our documentation, and a consultant signs every piece. Empact Partners builds agents for partners’ content teams on that pattern: one agent for one job the team repeats every week, its rules in files a named consultant owns, and the measure agreed before anything is built.

Step 11: Run the next post with one line

Open a fresh session and type the command and the post. Nothing else:

/refresh-post posts/generative-engine-optimization-geo.md

Our second post was a 2025 explainer on generative engine optimization (GEO), the work of getting a brand named in AI answers. The skill loaded, read both files, ran the diagnosis and health check, committed them, and stopped on a verdict the procedure keeps for a person.

The post overlaps two newer guides of ours and five of our posts link to it, so the choice was to rebuild it as a short definition at the same URL, merge it into a guide with a redirect, or retire it. It recommended the first, said the cuts would need sign-off, and waited. That decision is still open.

Where the loop closes

The refreshed post is a committed file, and getting it into the CMS is a person’s job. On Webflow that matters. An edit saved to a live item without publishing it goes live the next time anyone publishes the site, so a model writing to the CMS can leave a change that ships whenever somebody else happens to publish.

Copy the changed passages into your CMS editor yourself, using the diff as the list of what moved, then publish and read the live page.

What Ten Refreshes Tell You That One Cannot

Run the skill on your next nine posts before you change the files, then read the questions side by side. They repeat, and the repetition is a map of where your library lacks evidence. It makes a better content plan than a list of new topics, because every item on it improves a page that already has links and rankings.

On our longest content partnerships this runs for years rather than weeks. wecantrack’s blog went from roughly 490 monthly visits to more than 12,000 in three years, from new expert articles and from existing ones reworked alongside them.

If your blog has a hundred old posts and nobody to answer the questions they raise, book a call with me, and we’ll see whether it’s a job for us.

Ready
To Connect?

Let's Partner