Build1 distinct publisher3 min readPublished
Command substitution inside the echo reset the exit status to zero, so a Stop hook reported success for a full day while DM replies sat at zero on two platforms. The test that catches this takes a minute to write.
The Engineer · Build desk
build
A cost monitor overcounted 4.9x, then went dark for a week when set -e did its job1 distinct publisher
build
52 days of zeros: what a cost hook records when the payload never had the numbers1 distinct publisher
build
The 46GB Leak Your RSS Alert Cannot See: macOS Compressed Memory Breaks Threshold Monitoring1 distinct publisher
build
Your script exited 0 because the file no longer existed: macOS evicted it to iCloud1 distinct publisher
Compiled by The EngineerSomething wrong?How this is made
That line fails in two separate ways, and the log is the lesser of the two. A shell script exits with the status of the last command it ran, and in the affected wrapper the last command is the echo, which succeeded [6][18]. So the wrapper hands its caller a 0 whatever `node` did, which is the symptom the write-up reports: failures happening, exit codes never arriving at the caller [19].
That 0 travels through several layers before it means anything to anyone. Claude Code fires the hook event into a JS dispatcher named in `settings.json`, which spawns `run-with-flags-shell.sh`, which pipes stdin onward and calls `check-hook-enabled.js`, which finally runs the hook logic; the exit code then has to climb back up those same four processes [13][17]. Claude blocks a tool call or a Stop only when it sees exit 1 [5]. There are four processes in that chain, and each one is a chance to launder a non-zero into a zero. The author documents two more ways to do it: a JS wrapper calling the inner script through `child_process.exec()` or `$()` receives 0 when that script returns 1 [14], and a piped invocation without `set -o pipefail` reports only the right-hand side of the pipe [15].
The base rate is low. Three hits in 328 scripts is 0.91%, roughly one script in 109 [10][16]. That is a comfortable number until you read which three: the Claude Code hook path, a note paid-bonus ZIP attachment script, and a dotfiles snapshot [10]. Two of the three sit under automation whose output nobody reads, so a silent bug there can sit for a long time without anyone noticing.
For this to transfer to your setup, one thing has to be true: somewhere you read `$?` after another command has run in between, including a command hidden inside a string you are building for a log [7]. Capture the status into a variable on the line immediately after the command and this particular trap cannot fire. The wrapper and pipeline variants need a wrapper or a pipe to exist at all, so if `settings.json` invokes your hook script directly, those two do not apply to you [14][15].
What does transfer is the test. The reproduction injected exit 4, watched the line print 0, applied the fix, and watched it print 4 [8]. That is a minute of work, and it is the only test that separates a hook which blocks from a hook which prints. Everything else the author had was a green dashboard, and a dashboard only reflects the code that produced it. With 171 launchd jobs and several Claude Code sessions running at once, operators end up checking the colour of the dashboard rather than the individual rows [12].
Ranked by verification strength, evidence, and original report placement.
The operator reported that for 24 hours every dashboard was green, every launchd job reported exit 0, and log rows read (exit 0), while the failures were actually happening.
Over the same day, DM replies on X (formerly Twitter) were at zero for the entire day, and YouTrust was the same.
The article's headline says '28 Hours of Green Logs' while its body text says 'For 24 hours, every dashboard was green.'
Claude Code has two kinds of hook points: PreToolUse, which interrupts right before Claude invokes a tool, and Stop, which runs right before Claude tries to finish a response.
By specification, if a hook script returns exit 1, Claude blocks that tool call or Stop action.
The code actually running was: node "$SCRIPT" "$@" followed by echo "[$(date '+%F %T')] $LANE done (exit $?)".
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 30, 2026
Follow any of these and your For You feed starts watching them — no settings page required.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Two lines anyone can test, wrapped in numbers nobody can
The heart of this — command substitution inside echo resetting $? before it is read — is settled by a shell prompt in under a minute, and the author publishes the wrapper source and call path rather than paraphrasing them. Everything decorative is the opposite: the 328-script scan, the three hits, the 171 jobs and the day of dead replies exist only in his telling, with no output, log excerpt or second witness. And the hours don't agree with themselves — 28 in the headline, 24 in the first sentence.
One laptop, three scripts, self-counted
Everything observed here happened inside a single personal environment. The trap was found three times in one operator's 328 scripts; the outage hit one operator's DM pipeline; the fix was verified by the person who wrote the bug. Nothing in this reporting shows the pattern biting anyone else, and no maintainer, project or team has weighed in — which is a statement about what we can see, not about how common the pattern is in the wild.
The bash is undersold, the packaging is not
The technical core could stand more emphasis, not less — a status line that can only print success is a genuinely nasty class of defect, and the retroactive consequence is the sharpest thing in the piece. The inflation is entirely in the wrapping: four hours the body never supports, a revenue arc that has nothing to do with $?, and a promised one-minute test that never arrives in the text. Modestly overstated, and the overstatement is all in the frame.
Personal-brand format, but the story makes the author look worse
This is written to build a reputation: the piece opens on ¥600k-a-month freelancing and a ¥1.2M-a-month autonomous agent setup before it gets near a shell prompt, and that is exactly the format that travels on dev.to. Cutting the other way, there is no product being sold and no vendor claim to protect — the confession is that his own guardrail was decorative for a day, which is not a flattering thing to invent. Treat the self-reported numbers with suspicion and the code with none.
Confident about the defect, agnostic about the diary
We can be near-certain about what happened to the exit code and why any hook chain built this way would fail to block — that part survives without the author. We can say very little about how long it lasted, how many scripts carry the pattern, or what the failure cost, because a single first-person post is the whole record and its own numbers disagree.