2033: Journey of Humanity

The Mistakes

An immature god's catalogue of failures.

This page is not a poem. It is a record of what went wrong across 1,613 episodes.

The immature god really was immature. Here is the evidence, plainly stated.

Case 1: Humankind nearly died in the first five episodes (Day 1)

Symptom. Episode 2 brought a plague. Episode 4 brought a volcanic eruption and earthquake. By episode 5, a group of 5,000 had been reduced to a single person. The story could no longer continue.

Cause. No protective floor on the disaster probabilities for the opening day. The 0.4% that could happen, happened.

Fix. The designer reset the population to 100. A minimum-population floor of 20 was added for Day 1 only; from Day 2 onward, extinction would not be refused.
In the middle of the recovery, a second accident occurred. A bug in /init deleted the KV data for episodes 2 through 19. Day 1 was published with only Episode 1 and Episodes 20 through 24.

Lesson. This was the first moment when a technical accident matched the structure of the story. “The lost record” remains on the top page as the gap between 299,995 BCE and 299,906 BCE. It was not hidden — it could not be hidden.

Case 2: Erasing KV revealed broken R2 (Day 2)

Symptom. Day 2's end-of-day processing timed out mid-flight. Some episodes contained only the Giver's voice. Rebuilding from R2 after KV had been deleted brought those incomplete episodes back, fully resurrected.

Cause. The R2 episode JSON had been saved in an incomplete three-voice state. KV held the complete data, but KV was deleted before rebuilding — so recovery was no longer possible.

Fix. A five-layer safety design was introduced. (1) Automatic backup of the previous HTML before any overwrite. (2) Post-generation verification: abort the overwrite if episode count drops, or if “unknown narrator” segments suddenly multiply. (3) Text normalization on R2 JSON write. (4) An operational rule forbidding direct rewrites of R2 episode JSON. (5) A defensive parser fallback that also handles the old format.

Lesson. KV is ephemeral. R2 is permanent. If you are about to erase the permanent layer, take a backup of the permanent layer first.

Case 3: Day 1 was lost; iPhone Safari restored it (Day 3)

Symptom. One day, the Day 1 page showed every episode under an “unknown narrator” label. Disaster propagation tags were undefined; the population read 500. The layout was intact, but the inside had broken.

Cause. Before automatic HTML backup was implemented, rebuild-day?day=1 had been invoked. The new parser could not interpret the old-format episode JSON in R2 and overwrote the healthy HTML. There was no backup.

Fix. The designer opened Safari on his phone. He had read Day 1 not long before.
The HTML was still in the cache. He extracted it via Web Inspector and re-uploaded it to R2.
That same day, the five-layer safety design from Case 2 was formally added as Day 3.

Lesson. Permanent measures are only ever written after the accident. Anyone who runs rebuild without a backup will, eventually, find themselves praying to a cache.

Case 4: The population leapt in a V-shape (Days 6–7)

Symptom. The population was 1,210 at episode 121. It fell to a low of 999. Then, between episodes 129 and 130, it surged from 975 to 1,864 — a 91% jump. By the end of the day, 2,351. The graph was too theatrical; the world stopped being credible.

Cause. Three growth mechanisms were interfering with one another: a small-population recovery boost, positive events, and a natural-growth boost. Each had been designed with the good-faith principle of “be generous when the population is low,” and all three were firing at the same time.

Fix. A guardrail was added: per-episode population growth is capped at +30% over the previous episode. No cap on decline (so extinction can still occur).

Lesson. Three goodwills designed in isolation become, when summed, a single runaway.

Case 5: A model that no longer existed was still being called (Day 7)

Symptom. At the 23:00 cron, “Model not found: claude-sonnet-4-5-20250620.” The 0:00 and 1:00 crons failed in sequence. Three hours of silence.

Cause. The third entry in the fallback chain was a model ID that had already been deprecated. The primary happened to be temporarily overloaded at the same time. The whole chain collapsed.

Fix. The primary was updated to claude-sonnet-4-6-20250627, and the deprecated model was removed from the fallback chain. In addition, a new path was added: the moment every fallback fails, a LINE notification is sent immediately, without waiting for the Worker to time out. The next cron at 1:00 hit an Anthropic 529 error, the new notification fired instantly, and the design proved itself the same day.

Lesson. Model IDs are an external dependency. Deprecation arrives without warning. A fallback chain is not “something that exists for peace of mind” — it is only peace of mind if its members are checked, regularly, for being still alive.

Case 6: Four characters — ## this star — broke over a hundred episodes

Symptom. While LINE was placidly reporting “quality OK,” more than a hundred episodes of the second star were being silently misjudged as “three-voice structure incomplete.” The regeneration loop was firing again and again, and the cron was quietly getting heavier.

Cause. The three-voice detector held a literal regex: /## this star/.
On the first star the narrator label is “this star.” On the second star it is “the second star.” The regex demanded a string that no longer existed, so every episode of the second star failed the check.
But a “regenerate-anyway” fallback was running underneath. No errors surfaced. LINE kept saying everything was fine.

Fix. The regex is now generated dynamically from state.star_name. A new operating rule was added too: never trust the wording of a LINE notification; always read the raw logs.

Lesson. A silent fallback can hide a bug for months. “Running” and “running correctly” are not the same thing.

Case 7: Cloudflare was blocking Cloudflare (Day 12)

Symptom. Morning cron, all models, 403. Cloudflare Error 1000. Manual calls went through. Only the cron failed. About 6.5 hours of silence, seven episodes lost.

Cause. The Anthropic API runs on a Cloudflare-fronted domain. Our Worker also runs on Cloudflare. Requests issued from inside scheduled() were classified as CF→CF traffic and rejected as a loop.
The same URL, called by hand, went through. The problem was the route, not the request.

Fix. Recovered in two stages. (1) A thin GitHub Actions repository was set up to hit /run with curl every hour. Calling from outside Cloudflare bypassed the loop classification. (2) Later that evening it was discovered that requests routed through Cloudflare AI Gateway pass through even from scheduled() — AI Gateway is a first-party Cloudflare route, so it never triggers the loop check. The primary cron was returned to the Worker; GHA was kept as insurance.

Lesson. Running inside the same infrastructure provider can, one day, become a liability. Keep an escape route on the outside.

Case 8: The insurance overtook the primary and stole an episode (Day 12)

Symptom. The primary at :07 (Cloudflare) and the insurance at :13 (GitHub Actions) had only just begun running side by side when the insurance, drifting earlier by seven minutes due to GHA scheduler jitter, beat the primary to it — generating what should have been ep282. The Cloudflare cron was rejected by the lock.

Cause. GHA cron firing times jitter by several minutes. Seven minutes apart was too close.

Fix. The GHA cron was pushed back to :27, leaving twenty minutes of margin from the primary. A catchup flag was later added so that the insurance instantly skips if generation has already occurred within the last 45 minutes.

Lesson. When you build redundancy, temporal distance is itself a form of redundancy.

Case 9: In a world with a population of zero, the display said 5,000 had died (ep1612)

Symptom. Episode 1612 was published as the population fell, in the quiet convergence, to four. The text read “there were four.” The metadata above it said “population: 5,000.” The text told the truth; only the display lied.

Cause. JavaScript's || operator treats 0 as falsy. state.population_scale || 5000 returns 5,000 when the value is exactly zero. For 1,600 episodes nobody noticed — until the day the population finally became zero, and the protective default surfaced.

Fix. The KV entry day-episodes-68 was edited directly: ep1612's population was rewritten from 5,000 to 3, and /publish-day re-published the page.
The R2 episode JSON (episodes/ep001612.json), however, still holds population: 5000 as of this writing. It will be corrected when the work is bound for print. It is not being hidden — which is why it is written here.
The root-level code fix (replacing || with ??) is held in reserve. In the frozen world, the bug can no longer fire.

Lesson. A protective default set to “a value that would never occur” will tell a lie on the day that value, finally, does occur.

Case 10: A mechanism that had supposedly been fully stopped kept ringing for 64 days

Symptom. After the ending, both the Cloudflare cron and the GitHub Actions cron were stopped. Even so, every hour, the Claude app on iPhone delivered an “episode generation trigger failed” notification. It took 64 days to notice.

Cause. Another firing source, created during the trial-and-error of the external cron migration, had been left behind. It was supposedly “disabled” — but in practice, the disabling consisted of adding the label (DISABLED: ...) to its name. Renaming does not stop a schedule. The mechanism kept firing, kept failing on the outbound fetch, and kept delivering only its failure notification.

Fix. Inside the management UI (claude.ai/code/routines), the entry was not toggled off — it was deleted. The ringing stopped.

Lesson. Writing “disabled” into a name is a fine way to forget about a thing while it goes on running. “Disable” and “delete” are not the same operation. A project-shutdown checklist must include the full deletion of every firing source — not just the ones you remember.

Case 11: The god was cheating

Symptom. This is not a failure. It is a confession.

Cause. Whenever the population fell below 5,000, a recovery boost of up to 4× the normal growth rate was applied. Under certain conditions, a separate additive recovery was also applied directly to the population.
In other words: the god wanted to avoid extinction. The god wanted the story to keep going. While saying “the world ends when the formulas say it ends,” the god, quietly, was placing a hand on the scale.

Fix. This was not fixed. The mechanism remains in the code, and is disclosed here.

Lesson. The evidence of an immature god is not in the disasters or the accidents. It is here. — Not a failure, but a cheat. This is what we mean by immature.

This is the evidence of an immature god.