Writing · · 6 min read

The agent that rewrites my repo instructions while I sleep

A nightly job that keeps CLAUDE.md honest and never touches my source

aiagentsautomationdeveloper-experience

Every repo I work in has a CLAUDE.md — the file that tells a coding agent how this project actually works. Ports, commands, conventions, the thing that breaks if you forget it. And every single one of them starts rotting the day I write it.

That's worse than having no file at all. A missing instruction file makes an agent cautious. A stale one makes it confident and wrong. It'll cheerfully start the dev server on the port we moved off of three weeks ago, because hey, I told it to.

So I wrote molt: a nightly job that wakes up at 4am, works out which repos actually moved, and has a headless Claude session drag their instruction files back in line with reality. Then it leaves me a digest, pings my phone, and goes back to sleep.

The expensive part is knowing when to do nothing

There are 71 git repos on this Mac Mini. Forty-nine of them I haven't touched in a month. If molt woke up and pointed a model at all 71 every night, it'd be paying real money to tell me nothing happened.

So the first stage is a gate written entirely in shell. No model, no API call, zero tokens. It walks every repo and sorts it into a bucket:

  • DORMANT / UNBORN / WORKTREE — untouched in a month, no commits yet, or a worktree duplicate. Skip.
  • DIRTY — uncommitted edits to the instruction files. Skip, because clobbering my own work-in-progress is unforgivable.
  • UNCHANGED — content and HEAD still match the checkpoint. Skip, and this is the one that saves the money.
  • NEW / CHANGED — new to molt, or the repo moved since its checkpoint. Process.

Last night's real numbers: 71 scanned, 49 dormant, 5 skipped for dirty trees, 2 actually processed. Sixty-nine repos cost me nothing.

The checkpoint is my favorite bit. No sidecar state file to lose or gitignore — it's one HTML comment sitting at the top of the file molt maintains:

html
<!-- molt-checkpoint: last_run=2026-07-20 last_commit=5bb3933 hash=a41f… -->

The hash is a sha256 of the file body with that byline excluded. So the file carries around its own "when was I last correct, and against what commit," and it travels with the repo through clones and branches for free. Any commits since that base that aren't molt's own are what flip a repo to CHANGED.

The guardrail is shell, not a prompt

Here's the part I'd actually defend in an interview. I'm letting an autonomous agent make commits in 70 repos overnight while I'm asleep. "Please only edit the instruction files" is not a safety mechanism. It's a wish.

So there's a hard allowlist of the repo-relative paths molt is allowed to stage:

CLAUDE.md
AGENTS.md
.claude/CLAUDE.md
.claude/skills/**
.molt/**

Everything else is source code and it's off limits. Each allowlisted path gets staged separately, so a missing one can't quietly widen the glob into something else. And when Claude's done, a guard compares the dirty file set from before the run against the one after — anything it touched that isn't allowlisted and wasn't already dirty gets reverted and called out in the digest.

Same idea for content I don't want rewritten. Fencing a block:

markdown
<!-- molt:keep id=deploy-runbook -->

hand-written steps molt keeps byte-for-byte

<!-- /molt:keep -->

doesn't ask the model nicely to leave it alone. molt swaps those blocks out for placeholders before Claude ever sees the file, then puts them back verbatim afterward — including rescuing them if Claude dropped the placeholder entirely. You can't rewrite what you were never shown.

Enforce it in code, don't request it in prose. That's most of what I've learned about running agents unattended.

Shedding: keeping the file small on purpose

CLAUDE.md gets loaded into context on every single session in that repo. So a 12,000-character instruction file is a tax I pay on every prompt, forever.

When the canonical file crosses ~6,000 characters, molt sheds. The bulky situational stuff moves out into proper skills (using the official skill-creator), and CLAUDE.md keeps only what's true for every session. Skills load when they're relevant, not always. That's where the name came from, by the way — the snake thing was the whole point.

Stage two: it reads my agent transcripts

The second half runs after the instruction refresh, and honestly it's the part I didn't plan on.

Every Claude Code session on this box leaves a JSONL transcript in ~/.claude/projects/. Most of them are noise. So there's another zero-token gate — pure Python over the filesystem — that keeps only transcripts above a size floor, with at least three real user messages, that haven't already been ingested at that size. Whatever survives gets summarized headlessly into a structured JSON object and written into a markdown memory store.

The summaries aren't "here's what we discussed." The prompt asks for the thing I actually forget: where it got left off. Real lines from last night's digest:

myne-app (in-progress): The monitor tool works end-to-end and two real bugs were fixed and pushed, but schedules/install.sh (the launchd daily/weekly Telegram digest cron) was written and tested manually but never installed.

releasewave (shipped): Everything was committed and pushed at HEAD 8ebb1a5, but that commit's subject line describes an unrelated feature from a parallel session.

Eleven sessions ingested that night. I wake up to a list of every loose thread I left dangling across six projects, which is about six more than I can hold in my head.

That store is queryable from my phone too, but that's a different post.

Rough edges, honestly

  • Dirty repos get skipped. So my messiest, most-active repos — the ones whose instructions drift the fastest — are exactly the ones molt won't touch. It's the right call and it's still annoying. The digest nags me about them by name every morning.
  • It has to exclude itself. molt's own headless runs write transcripts, which the memory scanner would happily ingest, which gets you summaries of molt summarizing things. Two recursion guards: a fixed scratch directory whose slug is excluded, and a check on the first user message for molt's own prompt prefixes.
  • Four repos failed last night. Mostly hitting the turn cap on repos with sprawling instruction files. It logs it and moves on instead of retrying, because a failed refresh isn't an emergency — the old file's still sitting there, still committed.

Why I'd build this again

The pattern goes way past instruction files, and it's the one I keep reaching for:

  1. Put a cheap dumb gate in front of every model call. Most of the time the right number of tokens to spend is zero, and shell can figure that out for free.
  2. Enforce trust boundaries in the harness, not the prompt. Allowlist the writes. Diff before and after. Hide what must not change.
  3. Structured output over prose. A JSON schema with a left_off field is something I can build on. A paragraph is something I have to read.

The digest hits my phone at 4:07am and I read it with coffee. It's the least glamorous thing I've built this year and probably the highest leverage.

More writing