Build1 distinct publisher3 min readPublished
Fable 5 shuts down on 2026-07-07 and StartCalendarInterval cannot express a single firing at a fixed datetime, so this job leans on an integer date test, a temp-file jq rewrite, and an unload that runs whether or not the rewrite worked.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
Trace the control flow in order and the one-shot property does not live where the title puts it. The gate's `exit 0` sits above both the rewrite and the unload [17]. Every firing before 2026-07-07 therefore leaves the agent loaded, and the first firing at or after the date detaches it, whether it rewrote the value or found it already changed [1]. That ordering also covers the case an equality test would drop: the gate compares `-lt 20260707`, a lower bound [6], so a Mac that was powered off on the 7th gets its rewrite on the first slot after it comes back [2].
The rewrite half is careful work. `jq ... settings.json > settings.json` truncates the original the instant the shell opens the redirect target [10], so the output goes to a temp file, gets re-parsed by `jq .`, and reaches the real path only through an `mv` that the `&&` chain skips if verification fails [11]. The `.bak` copy is taken before any of that [9]. Three jq invocations, one of which exists only to check the previous one's work [4]. The case-insensitive `grep -qi 'fable'` on the current value is the idempotency that duplicate calendar slots require, since there is no way to ask for one firing at one datetime [5][12].
The two lines after that chain are where I would send this back. The script sets `-uo pipefail` and not `-e`, and neither the `switched model` log line nor the `launchctl unload` is conditioned on the chain having worked [20]. Two paths follow. If jq is not on the PATH launchd hands the job, the read returns empty, `grep` matches nothing, the else branch logs that the model is already fine, and the unload runs anyway [3]. If the read succeeds and the write fails, the log records a switch that did not happen, and the unload still runs [3]. Both paths retire the agent with `settings.json` untouched. The script already thinks about paths in one place, calling `/usr/bin/osascript` in full while jq, grep, cp and mv stay unqualified [22]. I would set PATH in the plist and gate the unload on re-reading `.model`.
The post's title promises four pitfalls; what the walkthrough spells out is three script-level guards, the planting-day misfire, the self-truncating redirect and unloading a job that is already unloaded, on top of launchd's expressiveness limit [5]. The already-unloaded guard is the cheap one, `2>/dev/null || true` so a second pass cannot abort the script [15].
For this shape to fit somewhere else, three things have to hold: the end date is known when you plant the job [1], the change is one scalar in a JSON file [2], and the replacement value is stable enough to write down now for a firing in July 2026, which for this author means the alias `opus` rather than a model ID, per the policy in his CLAUDE.md [13]. The job is a per-user LaunchAgent under `~/Library/LaunchAgents` [18], so it only exists on a machine somebody logs into.
Ranked by verification strength, evidence, and original report placement.
launchctl unload <plist> detaches the job from the daemon, but the plist file itself stays on disk, so it can be re-registered with launchctl load.
The unload line is launchctl unload "$PLIST" 2>/dev/null || true, written that way so an already-unloaded state does not abort the script.
Fable 5 is shutting down on 2026-07-07, and the author's goal was a launchd job he could set up on the day he learned of the deprecation, have it fire only on that day, and have it remove itself once done.
The author's ~/.claude/settings.json currently contains "model": "claude-fable-5[1m]".
The author judged a calendar reminder to hand-edit the model value "too flimsy" because he would forget it, and a daemon that checks the date on every launch "overkill".
The design he settled on is a three-part set: a date gate, a backed-up jq rewrite, and a self-unload.
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 28, 2026
Follow any of these and your For You feed starts watching them — no settings page required.
build
A cost monitor overcounted 4.9x, then went dark for a week when set -e did its job1 distinct publisher
build
The 46GB Leak Your RSS Alert Cannot See: macOS Compressed Memory Breaks Threshold Monitoring1 distinct publisher
build
Exit 0 is not a health check: three weeks of macOS backups that copied nothing1 distinct publisher
build
Your script exited 0 because the file no longer existed: macOS evicted it to iCloud1 distinct publisher
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.
Whole script shown, none of it run yet
The strongest thing going for this is that dev.to prints the script and the plist in full, so the gate, the temp-file chain and the trailing unload are readable rather than described — most of what's asserted can be checked against the code on the page. What cannot be checked is anything that happened. The job's one firing is in July 2026, the shutdown date it keys on comes from the author alone, and the version of the post we have breaks off mid-sentence in the pitfalls section.
One LaunchAgent on one laptop
Nothing here is countable. This is a per-user agent on the author's own Mac, edited into his own settings file; there are no forks, installs, or other people reporting they planted the same job, and dev.to never suggests otherwise. We are not going to convert one dotfile change into an adoption number.
'Deletes itself' is really 'unloads itself'
The overstatement is in the framing rather than the engineering. The headline sells a job that deletes itself; the body correctly says the plist stays on disk and can be reloaded. The same generosity runs through the phrase 'safe no matter how many times it's called' — the script is genuinely re-runnable, but it also declares 'switched model' and retires itself when the rewrite chain has failed, which is a different kind of safety than the one being advertised. And the four-pitfalls count in the title does not line up with what the walkthrough actually enumerates. The technical claims themselves are modest and mostly land.
Series traffic, no vendor behind it
The only pressure visible is authorial. This is the second installment of a one-shot-launchd series, and '4 Pitfalls I Hit' is a headline built to be clicked — which is the likeliest explanation for a promised count the text does not settle. Beyond that there is nothing to sell: the tools recommended are launchd, jq and bash, the config edited is the author's own, and no vendor, sponsor or product benefits from the reader following along.
Mechanics checkable, outcome unknowable
We can be fairly firm about what the script does, because it is on the page and short enough to read whole — the ordering consequences, the unconditional unload, the three jq calls all follow from the listing rather than from trust. We can be firm about very little else: one author, no corroboration for the deprecation date, a truncated ending, and a job whose only real test is months away.