BuildNot yet confirmed elsewhere1 publisher3 min readPublished
Five days of silence: a launchd job that only failed when a human ran it
launchd does not set LANG, so the scheduled run passed and the hand-run died with exit 127. The line it died on was the one that sends the failure alert.
The Engineer · Build desk

What happened
- launchd starts jobs in the C locale because it does not set LANG, while the same script run from a terminal inherits ja_JP.UTF-8.
- The scheduled morning run passed every day; running the script by hand died immediately with exit 127.
- The line that died sat in the failure notifier, so a failed Threads post never produced its Discord alert.
- Posting stayed down for five days with nothing reaching the author's phone.
- The same locale assumption was present in five repositories, according to the writeup's headline.
Why it matters
- constraint Because the pipeline's success test asks only whether a stock file was written, no number of passing runs can register that publishing has stopped.
- exposure Alert code is the one part of an automation that runs only after something else breaks, which leaves it unverified until the day it is load-bearing.
- decision Operators have to pick which absence they alarm on, because errors that never reach the notifier will only surface as missing output.
- precedent With Japanese log and notification text as the trigger, every new script written in that house inherits the same trap until the job environment is pinned.
Exit 127 at your own prompt does not read as a defect in the script. It reads as a typo. That is exactly the reflex the author describes: you assume you invoked it wrong, and you move on [5].
The scheduler's thin environment was already understood in that codebase, just not all of it. Phase 1 of the 528-line, 12-phase generator explicitly extends PATH with nvm and homebrew before doing any work [10][13], which is the code you write after launchd has failed to find a binary on you once. A missing LANG [1] is the same family of omission with none of the noise. Nothing is absent from disk. The scheduled process simply gets a different locale than the shell you tested in [2].
Then there is where the line sat. It was inside the failure notifier [18], and a notifier only executes after something else has already broken, so a run of green days tells you nothing at all about whether it works [11].
One seam in the writeup is worth naming. It reports the C-locale scheduled run passing and the interactive run dying [3][4], but the five-day outage requires the notification line to have died where launchd ran it, not at a terminal [18][6]. The post does not reconcile those two, and it does not show the failing line, so why a UTF-8 locale is the fatal one and C is not stays outside the evidence [17].
The monitoring conclusion survives either reading. The design's own definition of success is whether a stock file landed in content/article, deliberately cut loose from the Zenn deploy so a jammed publish cannot stop generation [8]. That part worked. The two daily slots kept firing across the window [7], roughly ten scheduled invocations [15], and every one of them was correct to say it had done its job [3]. The signal was accurate and had nothing to do with what had stopped.
The check that would have caught it is an alarm on absence rather than on error: nothing posted since yesterday, or a marker file older than some threshold. Against a stated target of 30 articles a month, one a day [9], five quiet days is about five posts not shipped [16], on the pipeline the author says sits under ¥1.2M a month of revenue [20]. Error alerts only cover failures that manage to reach the notifier intact. Everything upstream of it, including the notifier itself, needs the other kind.
The blast radius here is a property of writing style, not of the bug. Any script that logs or notifies in Japanese under launchd could stand on the same mine [12], which is how a single assumption about the environment ends up sitting in five repositories at once [19]. The author's own summary of the last six months is the useful part: a system begins rotting the moment it is built, and it does not tell you [14].
What to watch
- Whether the author publishes the failing line, which would settle why the UTF-8 locale dies and the C locale does not.
- Whether the fix is a LANG export per script or an EnvironmentVariables entry in the launchd plists, and whether the other four repos get the same treatment.
- Whether the next stoppage is caught by a heartbeat check rather than by someone running the command by hand.
Clarity's read
What the record supports and how the coverage leans. The claims behind it follow.
Reality
- Evidence30
- Adoption12
- Hype gap+38
- Incentives68
- Confidence42
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
launchd does not set LANG, so scripts running under launchd execute in the C locale.
- [2]
When the same script is run from an interactive terminal, the shell inherits LANG=ja_JP.UTF-8.
- [3]
The 8:00 scheduled run, in the C locale, passed without incident.
- [4]
Running the script by hand under ja_JP.UTF-8 produced instant death with exit 127.
- [5]
The author writes that because it only died on manual invocation, the reaction was to shrug it off as 'I must have invoked it wrong'.
- [6]
Posting was stopped for five days and nothing reached the author's phone.
- [7]
article-daily-stock.sh runs from launchd in two slots, 8:00 and 10:35 JST (the second a catch-up), generates one article and stocks it in ~/content/article/.
- [8]
By design, pushing to Zenn and generating stock are decoupled: success is judged only on whether stock was written to content/article, so a jammed deploy does not stop generation.
- [9]
The generation/publishing split was meant to support a stable output of 30 articles a month.
- [10]
Phase 1 of the script supplements the environment: PATH += nvm / homebrew, caffeinate -i -s, and a lockdir double-run lock.
- [11]
The author notes that a bug in notification code is only ever hit when something fails, so while things are succeeding it is always undetectable.
- [12]
Any script that writes logs and notifications in Japanese was structurally capable of stepping on this bug.
- [13]
article-daily-stock.sh is 528 lines and its skeleton splits into 12 phases.
- [14]
The author's stated lesson from six months of this work: a system starts rotting the moment you build it, and it will not tell you that it is rotting.
- [15]
Two scheduled generation slots per day across the five-day outage is roughly ten scheduled invocations that ran while publishing was dead.
- [16]
At the stated 30-articles-a-month target, five silent days is about five posts not shipped.
- [17]
The writeup does not reconcile its stated pattern (C-locale scheduled run passes, interactive run dies) with an outage in which the notification line died in the scheduled path, and it does not publish the failing line.
- [18]
When a post to Threads failed, daily_post.sh in the scent-media repo, which was supposed to fire a Discord notification, crashed on the notification line, and the fact of the failure was swallowed.
- [19]
The writeup's headline states the launchd locale bug was hiding in 5 repos.
- [20]
The author reports revenue now at ¥1.2M/month, built on an autonomous Claude Code setup developed over six months.
ReportedInsufficientSource: author of the dev.to writeup2 sources— create a free account to open themView cited source
Sources
1 independent publisher whose own reporting we read for this story.
- dev.to5 Days of Silent Failures: A launchd Locale Bug That Was Hiding in 5 Repos
1 article · August 26, 2026
Topics and entities
Follow any of these and your For You feed starts watching them — no settings page required.