Why a Stale Deployed Skill Costs a Whole Session, and How to Heal It at the Start

Date:

Your agent followed last week’s version of a skill all day. The drift check only told you when you pushed.

The short answer: A stale deployed skill is one where the copy your agent loads from its skills folder is older than the version merged in your repo. A check that runs at push time reports it only after a session has done all its work with the old rules. Move the check to the start: before a skill does anything, heal it by re-syncing just that one skill from the repo’s default branch. Keep a small deploy manifest of what was last deployed, so the heal can tell a merely stale copy (safe to replace) from a hand-edited one (refuse, and port the edit back). Never heal from a working tree.

If you keep agent skills in git and copy them into the folder the tool loads (for Claude Code, the per-user skills folder described in the skills documentation), the repo is the source and the loaded folder is a mirror. When Your Skills Folder Is a Deploy Target, Not a Drawer covers that model and two push-time checks for it. A push-time check still leaves one gap, and it is the expensive one.

A drift warning that arrives after the work is done is a postmortem, not a guard.

Why push time is too late for a stale deployed skill

A typical drift check compares each skill in the repo with its deployed copy and warns, at push time, when they disagree. That timing looks fine at first, since a skill one commit behind still runs.

Here is how it goes wrong. A skill that reviews records and logs a verdict for each one gets a fix to how it writes those verdicts. The fix merges, and nobody re-runs the deploy step. The next morning a session invokes the skill and reviews records for a full day using the deployed copy, which lacks the fix. The drift warning fires that evening, when the session pushes. Every verdict logged before then followed rules that had already been corrected.

Why a push-time check is too lateThe drift reminder fires after the stale skill has done its work.
Fix merges

The skill's rules are corrected in the repo and reviewed.

No redeploy

Nobody re-runs the deployer. The copy the agent loads stays old.

A day of work

A session runs the old skill. Every result follows the superseded rules.

the cost lands here
Push

The drift reminder fires, after the work is done.

too late

A session-start heal moves the check before the session uses the skill: re-sync from the default branch, refuse a hand-edit.

So the question is when you check, not whether. Some skills are formatting helpers, and one commit of drift is cosmetic. Others encode judgment rules, data contracts or guard rails. For those, a stale copy is wrong in exactly the way the fix was meant to prevent, and you pay for everything the session did before anyone noticed.

Why “just redeploy everything first” backfires

The obvious patch is to make every skill run your full deploy script before it starts. That fails on three counts:

  • It deploys the wrong content. A typical deploy script copies each repo’s checkout as it sits on disk: whatever branch is checked out, uncommitted edits included. Run from a session, it can push an unreviewed branch to every other session on the machine.
  • It overwrites hand-edits. A copy someone edited directly in the deployed folder is lost with no warning.
  • It is too big. A full deploy rewrites every skill, and often your git hooks too, to refresh one file.

What a session needs is narrower: this one skill, from reviewed content, and only if replacing it loses nothing.

Heal one skill from the default branch only

The heal reads the skill straight from git objects on the repo’s default branch (origin/main after a fetch), never from a checkout. That one choice gives the right behavior:

  • Reviewed content only. What deploys is what merged. A change on an unmerged branch is not live until it merges, which is what you want for anything shared.
  • Immune to local mess. Uncommitted edits, a feature branch, or a checkout that is days behind can’t leak into the deployed copy.
  • Scoped to one skill. It writes only inside that skill’s deployed folder, with no hooks and no neighbors touched.

Treat name collisions with the same caution. If two repos both provide a skill with the same name, the heal refuses rather than picking one.

Deploy what merged, not what happens to be checked out.

Stale or hand-edited? A deploy manifest tells them apart

Comparing the deployed copy with the default branch tells you that they differ, not why. “Differs” covers two very different situations:

Deployed copy vs default branchDeployed copy vs its manifestMeaningHeal does
equal(not checked)currentnothing (silent)
differsequalmerely stale: nobody touched it since the last deployre-sync this skill, write a new manifest, tell the session to re-read it
differsdiffershand-edited in the deployed folderrefuse; port the edit back to the repo

The manifest is a small file your deploy step writes into each deployed skill folder. It holds a content hash per file, plus the source repo and commit. It works like fingerprinting a record at import: hash what you copied when you copied it, so you can tell later whether anyone changed it. Hash normalized content (line endings and trailing whitespace stripped) so a Windows checkout never looks like an edit.

Two edge cases need a rule, and both lean conservative:

  • No manifest yet, for a copy deployed before you added manifests. Treat it as stale only if it equals some committed version of the skill, so it can be reproduced from history. Otherwise refuse.
  • The fetch failed. Never write. If the deployed copy matches the last-fetched default branch, carry on with a warning; if not, refuse, because “current” can’t be confirmed.

One rule sits under all of these: a heal deletes only what it can prove is a deployed copy. Anything uncertain becomes a refusal with a reason, and the refusal tells the session to read the skill from the repo by path for now.

Put the heal at the top of every skill

A heal nobody runs is just another reminder. So make it the first section of every skill you keep in a repo, straight after the title, calling whatever small script implements the steps above:

python path/to/heal_skill.py <skill-name>Code language: Bash (bash)

Have that section say what each result means: silent (current, proceed), healed (the session loaded the old text, so re-read it), or refused (don’t trust the loaded copy; read the repo version by path and tell the user). A local lint that flags any skill whose first section isn’t the heal stops new skills from quietly skipping it.

Two refinements close what remains:

  • Heal everything at session start, in the background. A session-start hook can launch a heal of all skills, fetching each source repo once, without blocking startup. The next session start reports any refusals from that run.
  • Serialize heals. Sessions often start in bursts. One machine-wide lock means two heals never write the same folder at once, and a skill invoked mid-heal simply waits, then finds itself current.

Is a stale skill ever harmless?

Sometimes. If a skill’s changes are cosmetic (wording, formatting, examples), one commit of drift costs nothing, and treating drift as tidiness is reasonable. That reasoning breaks for skills that encode rules, and those tend to be the ones that matter most. Keep the push-time drift warning, because it still catches hand-edits and deploys nobody ran. The protection against running a stale skill belongs at the start of the session, where it can still change what the session does.

The short version

A stale deployed skill costs a whole session, because a push-time check reports it only after the work is done. Heal at the start instead: re-sync the one skill from the repo’s default branch, never from a working tree. A per-skill deploy manifest lets the heal replace a merely stale copy and refuse a hand-edited one. When unsure (no manifest and no matching commit, or an unreachable remote), refuse and point the session at the repo copy. Put the heal first in every skill, lint for it, and run a background heal at session start so the per-skill check is nearly always a no-op.

Frequently Asked Questions

Why not block the push until the deployed skill is current?

The push isn’t where the damage happens. By push time the session has already used the stale skill, so a blocking check punishes the push without undoing the work. Healing at the start prevents the damage instead.

Won’t healing from the default branch hide my unmerged skill change?

Yes, on purpose. An unmerged change is unreviewed, so it shouldn’t reach every session on the machine. Test it on its branch by pointing the session at the repo file by path. It goes live for everyone once it merges.

How do I find deployed skills that no repo provides at all?

A heal only looks at skills it can trace to a source, so it won’t notice one that was copied in by hand and never committed. List the deployed folders, subtract every skill name your repos provide, and review what is left before deleting anything.

What does the heal cost at session start?

Mostly one network fetch per source repo, a few seconds each, and they can run in parallel. Running it in the background keeps startup instant, and a skill invoked mid-heal waits on the lock rather than racing it.

Share post:

Subscribe

Popular

More like this
Related

Replace the WordPress Logo With Your Site Icon, Everywhere It Shows

Changing the login logo is the well-known fix. The WordPress mark also sits in the toolbar, the login popup, every tab title and the admin footer, and on a multisite network each site should show its own icon, not one shared logo.

How to Show a WordPress Shortcode Without Running It

You write a tutorial that mentions a shortcode in a code example, and the published page shows something else: a gallery, a block of other posts, or an empty gap where the example should be. WordPress ran the shortcode you were only trying to mention.

How to Customize the tagDiv Newspaper Theme When the Accent Color Won’t Change

You change the Newspaper theme's accent color in the Theme Panel, save, and the header, menu and footer are still the old red. Then you try to adjust the phone layout outside the Composer and find every setting stored as base64-encoded JSON. Both have a reliable fix, step by step.

How to Update Rank Math SEO Fields Through the REST API Without Exposing Your Focus Keywords

You send a meta description to a post's REST API endpoint, WordPress replies with HTTP 200, and the field in the Rank Math panel is still empty. One function call per field fixes the write. Done the obvious way, it also publishes every focus keyword you chose.