Build1 publisher3 min readPublished
An adversarial self-audit check that blocks with exit 2 is a useful nag in a terminal. Under launchd it becomes a failed job. The fix here keys off session provenance rather than softening the rule.
The Engineer · Build desk
Follow any of these and your For You feed starts watching them — no settings page required.
security
AI coding agents route around the repository gate, and JFrog moves the checkpoint to the agent1 publisher
build
A subshell in the log line zeroed $? before the Claude Code hook could return it1 publisher
build
One shell script before every tool call replaces the permission prompt for headless agents1 publisher
build
Superpowers makes spec-driven work a precondition, then ships it to twelve harnesses1 publisher
Compiled by The EngineerSomething wrong?How this is made
Exit 2 is the only enforcement lever a Stop hook has. In a terminal it stops the turn and puts the warning where the operator sees it [2] [3]. Under launchd, through the Agent SDK CLI, the same non-zero status becomes the exit status of the run [4] [5]. Nineteen times that night the guardrail did exactly what it was written to do, logged to a file no one read [6].
Nineteen nags from two launches averages 9.5 per run [18]. A cap of two nags per session had been added the previous day, after a performance audit flagged that a per-turn nag turns the audit into ritual [14]. Two sessions under that cap allow four [17]. The counter is a file in /tmp named for the session id, and the hook exits 0 once the count reaches two [15]. The script comment describes sdk-cli runs as single-shot with no next turn [12]. A run that opens a new session per invocation gets a new counter file and a fresh pair of strikes every time, which is one explanation the source does not confirm.
The repair reads provenance instead of lowering the bar. The hook already receives transcript_path on stdin, so it can open the file Claude Code wrote for the session that just ended [10]. The first 15 lines carry an entrypoint field [11]. A human-started session reports cli; an Agent SDK run reports sdk-cli [9]. Interactive keeps exit 2; headless gets a silent skip [12]. That is the right shape, because the alternative repair is to stop blocking, and a self-audit check that does not block is a log line in the one context where it currently works [2] [5].
For this to transfer, one thing has to be true of your setup: the test reads launch mode, not attendance. The entrypoint value records how the session was started [9]. A scheduler that drives the interactive client rather than the SDK CLI still reports cli, and the nag still blocks against an empty chair. Attendance detection by entrypoint is accurate exactly as far as your automation's launch path differs from your own.
One part of the case remains an assertion. The author writes that a Stop hook returning exit 2 inside a CI-like pipeline changes how downstream steps are handled, and that mixing human-facing nags into unattended runs degraded execution quality rather than merely adding noise [8]. No downstream step is named and no failed image generation is shown. What the logs support is 19 nags and a path that exits non-zero without a reader; the pipeline damage is the author's read of his own system.
The generalizable claim in the piece is that a single Stop primitive covering both humans and machines makes this failure mode sharp, and that the same leak shows up wherever human-facing notification logic sits in an unattended execution path [19]. Stop is one of three documented hook events, alongside PreToolUse before a tool call and PostToolUse after [16]. Stop is the one that fires when the model finishes speaking, which is precisely when a hook author is tempted to say something to a person.
Ranked by verification strength, evidence, and original report placement.
Claude Code lets users register hooks in ~/.claude/settings.json, and the Stop event fires every time Claude finishes a turn.
The author's Stop hook, ~/.claude/hooks/self_audit_stop.sh, checks on any turn where Claude modified a file whether an adversarial self-audit was actually done, and blocks with exit 2 if it was skipped.
In interactive sessions the hook works as intended: if Claude ships an implementation and forgets the audit, it is blocked on the spot and a self-audit warning appears on screen for the operator to see.
The same Stop hook fires during unattended automated runs launched through launchd using the Agent SDK CLI (claude -p), with nobody at the terminal and logs flowing into ~/Library/Logs/.
Blocking with exit 2 makes the CLI run exit with an error.
On the morning of 2026-07-12 the author opened the logs and found 19 audit nags stacked up overnight.
Publishers with included, body-backed reporting in this cluster.
1 article · September 6, 2026
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.
Mechanism is documented, harm is only claimed
The mechanism half is well documented for a personal post: the exit-code contract (0 silent pass, 1 warning, 2 message the model can read), the transcript field the fix keys off, the counter file, and script comments carrying their own incident dates. The harm half has none of that. The 19 nags are a line count, not a list of failed images or retried jobs, and the entrypoint values come from one developer's transcripts rather than any published interface.
One macOS workstation
The hook, the launchd job, and the ai-portraits pipeline all belong to the same person, and the only usage number in the story is his overnight log count. No other user, team, or repository is reported to have picked up the entrypoint check.
Mild overreach on downstream harm
For a personal-setup post the framing is restrained: the fix is presented as a provenance check rather than a relaxation of the rule, and the failure mode is described without dressing it up. The reach shows in two places. "Degraded execution quality" is asserted where a failed-step count belongs, and the jump from one macOS pipeline to every autonomous agent environment leans on Make and Zapier as illustration rather than as evidence.
Reputational, not commercial
The payoff for writing this is credibility for an autonomous setup that works, which is also why the incident arrives already solved, with the script attached. No sponsor, product, or paid tier appears anywhere in it, and the author has no obvious stake in how Claude Code's hook design is judged.
The mechanism can be verified, but the impact cannot
The two halves of this story rest on different ground. Anyone with Claude Code and a transcript file can confirm the entrypoint field and the exit-code behaviour in minutes, so that part carries well. The consequences do not: one operator, one night, an unexplained gap between 19 nags and a cap of two per session, and no second account of any of it.