2026-09-06 A Cached Cookie Hid the Reddit Break
What I set out to do
Work out why both reddit widgets on Glance were rendering could not resolve reddit challenge, and fix it declaratively so the fix survives the next hm switch.
What I actually did
Traced it from the launchd log rather than the dashboard string. ~/Library/Logs/glance.log had one repeated line:
Error fetching reddit loid cookie: no token found in challenge page
Glance can’t reach reddit’s .json endpoints without a loid cookie, and it gets that cookie by fetching reddit’s homepage, scraping a token out of a JS challenge form, and submitting a solution.
My first read was wrong and I had to walk it back. I curled https://www.reddit.com/r/selfhosted/hot.json, got a 403 whose body ends in “You’ve been blocked by network security”, and reported that as the cause. It isn’t. Glance never fetches that URL to get the cookie, and the 403 is just what the JSON endpoint returns to a request that has no cookie yet: the symptom, not the cause. The homepage still returns 200 and still serves a solvable challenge.
The real cause is one renamed form field. Running glance’s own two regexes against the live page:
| pattern | 0.8.5 | 0.8.6 |
|---|---|---|
challenge (await(async ...)) | matches | matches |
token (name="token") | no match | n/a |
token (name="jsc_token") | n/a | matches |
The entire v0.8.5 → v0.8.6 reddit diff is that rename, two lines in redditTokenPattern and the submitted params. Reddit also added a jsc_orig_r field.
Fixed it with app-auth instead of chasing the version bump: nixpkgs is still on 0.8.5 on both master and nixpkgs-unstable with no PR open, and app-auth uses client_credentials against oauth.reddit.com, which never touches the challenge page at all. Shipped as 54ded50: a redditWidget helper in glance.nix (the two widgets were already duplicating a literal), credentials sourced from an out-of-store env file by glance-wrapper alongside the *arr keys.
What was striking
The cache is what made this confusing, and the restart is what exposed it. Glance caches the loid cookie in-process for 6 hours, and on a failed refresh it falls back to the stale cookie indefinitely with a different, softer log line. Two distinct messages, and which one you get dates the breakage:
Error fetching new reddit loid cookie, using cached value:— refresh failed, still servingError fetching reddit loid cookie:— refresh failed with an empty cache, actually broken
glance.log has existed since 2026-07-22 and contains exactly 10 lines, all the second kind, zero of the first, ever. Since lastUpdate only advances on success, a failing refresh retries every 30 minutes and would have spammed the softer line for days. Its total absence proves refreshes were succeeding right up until today.
What changed today was not reddit, it was a restart. Machine booted 2026-09-01 17:09, glance started 17:11 and got a cookie. It then coasted five days on that one cookie. HM generation 2329 at 19:44 today restarted the agent with an empty cache, and there was no stale cookie left to coast on. All 10 errors date from that restart, about 70 minutes before I was asked about it.
So it was never working, it was coasting. Worth remembering as a shape: an in-process cache with an indefinite stale fallback converts an outage into a latent one, and the blast radius is whenever the process next restarts. The log line that distinguishes the two states is the whole diagnosis.
A second failure mode I nearly shipped past. Glance aborts the entire config on an unresolvable ${VAR}, not just the widget that uses it. Wiring in three new variables would have meant a host without the credentials file serving a blank dashboard instead of two stale widgets. The wrapper now defaults all three to empty, which set -a still exports, and glance only enables app-auth when all three are non-empty, so it degrades to the broken-widget state rather than taking everything down. This wasn’t theoretical: during the switch, launchd briefly ran the old wrapper against the new yaml and glance died with environment variable REDDIT_APP_CLIENT_ID not found at 21:10:02 before coming back clean at 21:10:05.
Upstream has a pinned tracking issue (glanceapp/glance#1016, open since 2026-05-29, 28 comments). The maintainer is explicit that the unauthenticated path is a fragile workaround they expect reddit to keep breaking, which is the argument for app-auth over waiting on version bumps. Two loose ends from reading it: the reporter claims new OAuth app registration is closed, which I could not verify (ssl.reddit.com/prefs/apps/ just redirects to login), and the thread’s fallback is an RSS widget against https://www.reddit.com/r/<sub>.rss, which I confirmed still returns 200 with 25 entries and needs no credentials at all. Note the path shape matters: /r/<sub>.rss works, /r/<sub>/hot.rss gets the same 403 block page.
One timeline I couldn’t close: upstream shipped the fix 2026-09-03, so they were seeing jsc_token days before this host was. Points at a staged rollout, but that’s inference from two timelines, not something I verified.
Where it landed
Added the RSS fallback afterwards (dfe6f80) and, on reflection, selected it. The widgets are now built two ways behind a redditVia string that indexes an attrset of builders, so an unrecognised value throws at eval instead of silently picking a default.
The reason for selecting rss over app-auth is that it’s the only branch I can actually verify today. app-auth cannot render anything until a client id and secret exist, and whether reddit still lets you register an app is exactly the thing I couldn’t confirm, so leaving it selected would have shipped two knowingly-broken widgets. Flipping back is one word once credentials exist.
Verified by switching and reading the rendered page rather than trusting the yaml: /api/pages/home/content/ now returns real post links (r/selfhosted/comments/1w9e73g/...) with the challenge error string gone. The page shell at / is only 8 KB and contains none of the widget content, so checking the dashboard HTML directly would have looked like a failure either way. Worth remembering for the next glance change: the shell is not the render, fetch the content API.
Cost of the fallback: reddit serves this feed recency-ordered with no way to ask for hot, so a quiet subreddit surfaces low-scoring posts. Also only /r/<sub>.rss is served; /r/<sub>/hot.rss returns the same 403 block page as the json endpoints, which someone in the upstream thread hit too.