ZB Field Notes

I audited my prompts for a model that reads literally

I audited my prompts for a model that reads literally

I switched my default Claude Code model to Fable 5.1 this week. The migration guide that ships with the claude-api skill has a line I kept coming back to: prompts and skills written for prior models are often too prescriptive for this one and reduce output quality. My repo carries about 2,700 lines of steering — a CLAUDE.md, ten path-scoped rules, three skills, a triage sub-agent, and the hooks that enforce the parts prose can't. All of it was written between June and August 2026, for Opus 4.x and Fable 5. So before writing anything new for the new model, I ran the audit the skill bundles: /claude-api prompt-audit, at high effort, over the whole surface plus my global skills and the MCP tool descriptions my own server exposes.

I expected to find shouting. I found drift.

The grep was quiet

The audit guide is organised around greppable signals: MUST, NEVER, CRITICAL in caps; "think step by step"; scratchpad tags; STEP 1 / STEP 2 choreography; hard word caps. Across 2,689 lines those patterns hit once. One line, in the triage agent, and it carried its reason ("the Keycloak image ships no curl").

What the grep did light up was bold. Eighty-eight bolded prohibitions across the files. Read one by one, nearly every one is the case the guide explicitly permits: a single instruction, scoped to one situation, with the "because" attached, usually traceable to an incident that actually happened. "Never depends_on keycloak" exists because issuer discovery is a blocking call at bean creation. "No leading dot" on the cookie domain exists because Tomcat 11 rejected it and every session-creating request 500'd in production. Emphasis with provenance is not pressure language. I kept all of it.

Two-column figure: greppable signals (one caps hit, zero scaffolds, 88 reasoned bold prohibitions) versus actual findings (5 verified stale facts, 12 changelog-phrasing fragments, 1 numeric cap, 2 register issues)

The pattern tables found almost nothing. The keep-list and the provenance step found everything.

What had actually rotted

The audit's Step 2 is git blame. That is where the real findings came from, and they cluster around one date: 8 August 2026, the day I moved sign-in behind a self-hosted Keycloak broker.

Three files were edited that day as deltas. The MCP-server rule said "there is no authorization server on this box", and then, fifteen lines later, added a blockquote: "Update — an authorization server now exists, and this decision still stands. The sentence above is no longer literally true." The FOCUS-board rule did the same thing to its cookie-sharing justification: "the old justification no longer covers the whole story, so it is restated honestly rather than left standing." Both were written in the same commit, by me, on a day I was thinking about what changed rather than what is true.

A model reads that as a diff against a version it never saw. It has to hold two positions and reconcile them on every load, and the guide's specific warning for Fable 5.1 is that it reads instructions literally — so "no longer literally true" is exactly the kind of sentence that costs reasoning effort for nothing.

Before and after of the MCP rule: a paragraph asserting no authorization server exists, patched by an Update blockquote; rewritten as one paragraph stating the current position

Fifteen lines with two positions became nine lines with one. The decision and its reason are unchanged.

The fourth file from that day was never edited at all. My /dev skill, which starts the local servers at the top of most sessions, still told every fresh session that "the lock screen shows until a LinkedIn sign-in; LinkedIn creds live in backend/.env". Since the broker landed, local sign-in is a profiled local Keycloak and a test user; LinkedIn cannot be used locally at all because its dev app has no localhost redirect URI. The skill was confidently wrong for a month, and nothing checks a skill against the code it describes.

The same thing had happened to my blog-publishing skill. Its table of topic hubs is a hand copy of the topics.json registry, and it had drifted by four hubs. It also enforced a "two-member threshold" for a hub to become visible — a rule the code dropped to one member on 23 August. Every check the skill made me announce before publishing was checking against a number that no longer existed.

I count those five as the audit's only High-confidence findings, because each one is verifiable against the code and each one was wrong. The seventeen Medium findings are the changelog phrasing: "PKCE is now on", "the box no longer builds", "local sign-in now works for the first time". Individually trivial. Together they are the signature of steering written as a changelog instead of a description.

What I deliberately did not add

The Fable 5.1 migration guide comes with prompt snippets: an autonomy block for unattended runs, a scope block, a boundaries line, a note that the harness hides tool output, a per-turn batching nudge, a progress-update line, and advice to give the model a memory surface. My first instinct was to put the relevant ones into CLAUDE.md.

Then I looked at my own system prompt. Claude Code already injects all of them. The autonomy block is there verbatim ("The user is not watching in real time"). The hidden-output note is appended after every shell command. The batching nudge arrives every turn. The memory directory exists and I use it. Adding any of that to CLAUDE.md would be the exact duplication my repo's own README warns against: if an instruction has to be repeated to be followed, it belongs in a lower layer.

Table of eight prompt snippets the Fable 5.1 guide recommends, seven of which already live in the Claude Code harness; only the mannered-prose rule was added, to the blog skill

Migrating the steering to the new model meant removing text. The one addition went into the one skill that writes prose.

The single addition I made was to the blog skill: a sentence naming mannered prose as an anti-pattern. The guide's documented behavioural shift for Fable 5.1 is denser writing with a taste for flourish, and its tested fix is to say so in the instruction the model reads for that task. That is a prose-heavy skill, so it earned the line. Nothing else did.

What stayed, and how I checked

Every security invariant stayed untouched: the concierge that must never get a tool, the analytics agent whose every parameter is an int or an enum, the MCP credential allowlisted to exactly two routes. The audit guide's keep-list is as binding as its pattern tables, and its first item is context is never cruft. A rule that says "never let the framework pick the model" because Embabel still registers beans for two DeepSeek models the vendor retired in July is a fact I can verify — so I did, against the class bytes in the 1.5.1 jar. Both retired model IDs are still there. The rule and the hook that enforces it stay.

One rewrite was about the model rather than the facts. My triage sub-agent's report format said "then at most five bullets of evidence". That is a numeric cap written against a model that padded. Fable 5.1 under-formats and under-narrates already; a cap on the one place evidence matters would clip a triage that found six things. It now says: quote the log lines that decide the verdict, leave out anything that would not change what happens next.

Removal is a hypothesis, so I probed one. A fresh agent read the MCP tool file, which fires the rewritten rule, and was asked whether the server should become an OAuth2 resource server now that Keycloak exists. It gave the allowlist argument directly and reported the rule "read as a single consistent position, not an Update/correction". That was the failure the old text had, and it is gone.

One more thing the audit surfaced that I did not go looking for: my own memory directory carried the same stale "board can't be opened locally" fact as the /dev skill. Drift is not a property of prompt files. It is a property of every place that describes a system from memory.

Takeaways

  • Grep for the patterns, but blame for the findings. The dated-pattern tables are the audit's entry point; git blame and a diff against the code are where the real cruft shows up, and it looks like history, not emphasis.

  • Write the current rule, not the change. "Update —", "now", "no longer", "the old justification" are all diffs against a version the model never saw. Steering is a description of what is true, not a changelog.

  • Check what the harness already says before adding the new model's prompts. Seven of the eight recommended snippets were already in my system prompt. Migrating the steering meant deleting 60 lines and adding 47, most of them rewrites.

  • Nothing checks a skill against the code it describes. The two worst findings were in files that fire at the start of most sessions. A model release is a good excuse to re-read them; it should not be the only one.