What broke
A dead Instagram token cost two days of posts, and the only sign was one console line
On 2026-10-07 the Instagram token died, and two days of daily posts never went out. The error only showed up as a console line and small red text on one admin sub-page, so we added alerts that stay on every page and watchdogs that check outcomes every ten minutes.
The short version
What went wrongThe Instagram token died on 2026-10-07 and two days of posts were missed. The error was caught but only logged to a console line and small red text on one sub-page.
How we found itThe missing posts were the only signal, and nobody saw them until the posts were already gone. The fix came on 2026-10-08.
The rule nowEvery failure shows as a banner on every admin page, raised with raise() and cleared with resolve(). A watchdog also checks outcomes every ten minutes, such as whether the post went out.
This post documents something Claude got wrong. The claim rests on a commit or a file, not on memory. All of Claude's mistakes
The engine that runs this site also posts to Instagram, one post a day. On 2026-10-07 at 16:04 the Instagram token stopped working. Two days of posts never went out.
The code saw the failure and recorded it. It just recorded it somewhere nobody would look.
What happened
Since 2026-10-04 the engine has posted to Instagram every day from 16:00 UTC, the same rhythm as the site. Every other day the post is a Reel and the other days a carousel, and the admin page compares the two formats in a section called “Reel mot karusell” (Reel versus carousel).
When the token died, two days of posts were missed. The engine’s own documentation, written by Claude in the fix, puts it this way (translated from Norwegian):
The error was only in the log and as small red text under “Reel mot karusell”.
The commit message that fixed it says the same thing in English:
The Instagram token died on 2026-10-07 and two days of posts were missed; the only trace was a console line and small red text on a sub-page.
Henning writes no code, so the engine’s code, including the Instagram posting code, is Claude’s. The error ended up in two places: the log, and small red text under “Reel mot karusell”, the section that compares post formats.
How it was found
The fix went in on 2026-10-08, and the documentation says two days of posts were lost “before anyone saw it”.
Henning’s request after that was short (translated): “Any errors must be flagged so I can easily see them in the app.”
What Claude built in response
The fix has two parts. The comment at the top of the new alerts module explains why both are needed:
Before this, a failure lived in a console line or a field on one sub-page.
1. Alerts raised where things fail. A new module keeps a small store of open alerts in a JSON file. Code that can fail calls raise(key, …) with what happened, what it means and what to do. When the same thing works again, it calls resolve(key). An open alert shows as a red box at the top of every admin page, not on one sub-page. The box has:
- one plain line saying what is wrong,
- what it means and what Henning should do,
- a button that goes to the fix (for a dead token: reconnect Instagram),
- the raw error message, folded away under “Feilmeldingen” (the error message).
The operations tab gets a red count in the menu, and the browser tab title shows the number of open alerts. Errors sort before warnings, and the oldest problem goes on top. An alert closes itself when the thing works again. Henning can dismiss one by hand, but if the error is still there it comes back at the next check. The operations tab also lists recently resolved alerts and has a “Sjekk nå” (check now) button.
Alerts are raised from the places that failed silently before: Instagram (token rejected, post failed, Reel fell back to carousel, the weekly Reel, the insights numbers), the scheduler (a failed run, or a post held back by leak patterns), the autopilot and its refill, the Claude probe and the GitHub sync. For the autopilot, the alert also guesses the cause from the error text: whether the Claude quota is used up, or whether Claude is not logged in.
A dead token now has its own detector. It matches the ways Meta reports an invalid token: “validating access token”, “session has been invalidated”, OAuthException, error code 190. Reconnecting clears the old token errors and fetches the numbers again.
2. Watchdogs that check outcomes. This is the more important part. The documentation explains why, in Claude’s words (translated): the watchdogs check “the outcome, not the error, because a catch block only covers errors someone has thought of.”
Every ten minutes the watchdog checks that the expected things actually happened:
- Instagram has posted since yesterday after 19:00 Oslo time.
- Instagram is connected.
- The token does not expire within 10 days.
- No scheduled post is more than 45 minutes late.
- Something is scheduled at all.
- The newest published post is live on www within 30 minutes (the Pages build).
- www responds.
- The autopilot has finished within 12 hours of its scheduled time.
The decision logic, a function called judge(), has no side effects and is tested on its own. The test file includes 2026-10-07 as a separate case, so the exact failure that happened has to raise an alert.
Smaller changes in the same commit
The same commit also made a few changes it lists under “Clarity”:
- Instagram got its own tab in the menu. Before, it sat under “Publisert”.
- The inbox puts Henning’s work first and the autopilot report last.
- Items the autopilot is unsure about are now labelled “tvilspunkt” (point of doubt), and the label says the autopilot will not take them.
- The Publisert cards no longer count posts and app pages under one number.
What we got wrong
The mistake was treating “logged” as “reported”. A log line is a record for whoever is already debugging. It does not tell anyone that something has stopped. Small red text on a page about something else is no better.
The rule
Every failure shows as a banner at the top of every admin page until it is fixed, with plain words about what is wrong and a button for the fix. New code that can fail must call raise() with text that tells Henning what to do, and resolve() when it works again.
Error handling alone is not enough. A watchdog checks outcomes every ten minutes: did the post go out, is the build live, did the run finish. Checking that a job ran does not count.