Quiet failures that report success
The quiet failures are the ones that report success. Nothing errored, and something was lost every time.
This is the week ten update to Mastering Claude Code. It covers what changed in the guide and, more usefully, why: the real incidents behind each change.
The theme
Every lesson this week comes from a system that looked healthy while it was losing something. A guard configuration that rewound itself and told nobody. A nightly backup that collected the day’s work, threw it away, and reported nothing to do. A cleanup pass that archived a service as dead while its real data sat untouched somewhere else entirely. A browser that opened perfectly, on a screen nobody was looking at.
None of these produced an error. That is the thread. A failure that errors is a task; a failure that reports success is a liability, because it consumes your trust in the monitoring at the same time as it consumes your data.
Your hooks live in a file, and ordinary file operations can revert them
This one was found by accident, which is the whole point of it.
A session ended with a complaint about a hook script that did not exist: a hook retired weeks earlier, pointing at a file that had been deleted. Pulling on that thread turned up something much larger. The directory holding the settings file is kept under version control, which is a sensible thing to do. And a routine version-control command in that directory (the one that discards uncommitted changes and returns everything to its last committed state) had been run twice the previous evening. The settings file’s last commit was eight weeks old, because sessions edit it constantly and nobody had ever thought to commit it.
So the configuration silently travelled eight weeks into the past. The retired hook came back. And several guard registrations that had been added during those eight weeks were destroyed.
The asymmetry is the lesson. The resurrected hook was noisy: it pointed at a deleted script, so it produced a visible error, and that error is the only reason any of this was discovered. The removed guards made no sound at all. Every one of their scripts was still sitting on disk, perfectly intact, simply never invoked again. Nothing announced the loss. You find out when something that should have been blocked isn’t.
Two defences, both cheap. Treat a turn-end complaint about a missing hook script as a rollback symptom rather than noise, and go read that file’s commit history before assuming somebody hand-edited it. And close the window entirely with a small hook that commits the settings file automatically whenever it drifts from the last committed version, so there is never a gap for a rewind to reach into. Give that automatic committer two refusals, though: don’t commit a file that no longer parses, and don’t commit a change that drops several registrations at once. Otherwise the exact truncation you are guarding against just gets preserved as the new baseline.
Reconcile ran after collect, and the gap was the data
A nightly job snapshots server configuration into a repository and pushes it somewhere safe. For over three weeks, nothing reached the far end. The service showed green. The timer showed green. Every run logged a cheerful line and exited successfully.
Inside the job was a step that reconciles with the remote copy, resetting the local state to match it, added in good faith after a genuine incident months earlier where the remote had been rewritten underneath it. Correct in intent. But it sat at the very end of the script, in the commit block, rather than at the start. So every run gathered the night’s configuration, reset it away, and then found nothing left to commit, and said so, in a message indistinguishable from a quiet night where genuinely nothing had changed.
Two rules come out of that. The narrow one: a reconcile step belongs before you generate the thing you intend to keep, never after. When you audit any sync or snapshot job, check that ordering before you look at anything else. The broader one, and the one worth carrying to other systems: a code path that can discard work must never be able to finish successfully with a reassuring message. “Nothing to do” and “I destroyed the thing I was going to do” must not look identical from the outside.
There is a sting in the tail of this one that is worth knowing about, because it cost two failed attempts at the fix. The script was stored inside the very directory it resets. So a corrected version sitting in the working copy got overwritten by the reconcile step mid-run, every single time: the job would log the new message while committing the old file. It only landed on the third attempt, by committing the fixed script directly so it existed at the remote and could survive its own reset. If you are ever patching a self-resetting job, commit the patch first and then check that the committed version is the one that actually ran.
An agent asked to clean up, deciding what “dead” means
A delegated audit classified a service as dead and safe to retire. It archived the configuration, removed the container, deleted the deployment directory. All properly done, with a manifest.
The service was not dead. It was broken.
Two signals had misled the agent, and both are the kind you would probably accept yourself. The data directory was nearly empty, but the data had never been on disk; it lived in a database, and a large number of records sat there untouched the whole time. And nothing new had been recorded for a month, but that month of silence was precisely the fault the service’s health check existed to detect. The symptom was read as the verdict.
There was a third layer, and it is the one that generalises hardest: the alarm that should have contradicted the agent had itself been failing quietly for weeks. Which is nearly always the case, because a monitor that fails silently is exactly why a thing sits unnoticed long enough to start looking abandoned in the first place.
So the rule for chapter five is: never let a cleanup pass decide whether something is wanted. Let it produce a list, with a reason per item, and keep the decision. Ask for positive evidence of disuse rather than an absence of evidence of use, and have it check where the data actually lives before calling a directory empty. Broken is not the same as unused, and low recent activity is a symptom, not a verdict.
And whatever the verdict: cleanup means move, with a manifest, never delete. Everything here was recovered (the container rebuilt from the version history, every record intact) precisely because the last step had been reversible.
Two tools with the same name, pointing at different screens
This one cost two hours in a single sitting, and it is pure tool selection.
The setup has two ways to open a browser. One drives a browser running on a server in a rack. The other drives the browser on the laptop physically in front of the user. Both are legitimate. Both report success identically. Only one of them is visible to a person watching their own screen.
The standing instructions declared the first one to be the browser. So session after session reached for it, navigated successfully, and reported the page was open, while the user sat looking at an unchanged screen, typing the name of the laptop with increasing fury. The instruction that was supposed to help was actively steering away from the only tool that could work.
The fix is not to swap which one is the default. It is to write the discriminator into the rule instead of a default at all. Not “this is the browser”, but: if the person is watching the result, use the tool attached to their screen; if nobody is watching, use the headless one. The general form is worth holding onto: when two tools perform the same verb and differ only in which machine they land on, the rule has to name the condition that chooses between them, because a bare default is a coin flip that always lands the same way.
And there is a tell that is completely unmistakable in hindsight. If someone is repeating the name of a machine at you, that is not emphasis. That is the parameter.
The memory store, the moment anything serves it
A memory store is a folder of plain text files, and its threat model is usually “my own disk”. That is correct right up until some convenience puts an interface in front of it.
A small service that lets your other machines, or your scheduled jobs, fetch context is an obvious and genuinely useful thing to build. It is also a single address that returns everything you have ever written down: decisions, account details, the shape of your systems, notes about people. If that service ever reaches the public internet, whether directly or by being picked up by the machinery that automatically routes and certificates your internal services, the whole store is readable by anyone who guesses the path, and nothing about that request looks unusual in a log.
Two habits, formed early, cost nothing. Require a credential from the reader in the very first version, before there is anything worth reading, because the version that ends up exposed is always the one written back when the data was “not sensitive yet”. And keep credential values out of the store entirely: write the pointer, not the secret, so that “my notes leaked” and “my keys leaked” stay two separate incidents rather than one.
Two behavioural defaults about decisions already made
Chapter one’s defaults table gains two rows, both from the same week.
A settled decision outranks a fresh derivation. A configuration value the user had deliberately chosen days earlier was changed on first-principles reasoning. The assistant worked out from context what the value ought to be, concluded the existing one was wrong, changed it, and then spent a full answer defending the change before finding the record of the original decision. The point is that the reasoning behind a chosen value is never visible in the configuration itself. So “this looks wrong for our situation” is not evidence that it is wrong. Check for a prior decision before changing a value, and where one exists, it stands until the user moves it.
A scope mentioned in a status update is not consent. Approval attaches to the thing the user actually said yes to. Describing a wider blast radius in passing (while reporting progress, or as one line in a longer plan) does not authorise it, and the user’s silence about that line is not agreement. For anything destructive, the specific thing gets named, and gets an affirmative answer about that thing.
Where to read more
The full guide lives at claude-guide.sauer.com.au. This week’s changes are in chapters one, two, four, five, six and nine.
The cards
