A routine log sweep across a family of internal tools on October 12th turned up the same false-alarm pattern twice, in two codebases that share no code: a service's own startup messages were being filed as errors, purely because of which output stream they happened to be written to. Alongside it, a smaller but more interesting access-control story — opening a manager-only weekly dashboard to every editor, and discovering that "remove the UI restriction" was the easy 10% of that change.
A log aggregator usually can't read your message. It reads your stream. If your routine output goes to stderr, it's an error now, whether anything is actually wrong or not.
Eighteen stack traces that were all the same expected event
On an internal ops platform, a log sweep found its error bucket full of AuthApiError: Invalid Refresh Token: Session Expired stack traces — eighteen of them in seven days, each about eleven lines long. Every single one was a session Supabase had ended exactly as designed, on its own 48-hour time-box. Nothing was actually failing. The cause traces to how the Supabase SSR client is wired up: createServerClient registers an onAuthStateChange handler, and when the underlying auth library's initial-session load hits a refused refresh, it throws a bare error up through that handler rather than treating an expired session as a normal, expected outcome. The fix files the event as a single warning line instead of letting an expected state transition print an eleven-line stack trace under @level:error, which is exactly the kind of noise that trains a team to stop reading its own error bucket.
The same root cause, in a completely different tool
The genuinely interesting part is that the same sweep, on the same day, found the identical class of bug in an unrelated ad-variant export tool. Every deploy filed five error-level lines — Starting gunicorn, Listening at, Using worker, Booting worker, and Control socket — none of which indicate any problem whatsoever. The mechanism is almost comically simple once you see it: Railway assigns a log line's severity based on which stream it arrived on, not its content, and gunicorn writes its entire startup log to stderr by convention. Meanwhile, the two print statements in the actual application code that do report a real failure had been going to stdout, and were being filed as mere info.
So the error bucket had it exactly backwards: completely routine infrastructure chatter marked as errors, and the one place something could legitimately go wrong marked as unremarkable info. The fix routes gunicorn's routine lines to stdout and keeps the two genuine failure prints distinct. Finding this pattern twice in one sweep, in tools that don't share a line of code, says less about either codebase specifically and more about how easy it is for a hosting platform's stream-based log-level heuristic to quietly misclassify an entire category of output — worth checking for deliberately rather than assuming it only happens once.
Opening a manager-only dashboard without leaking other people's numbers
The access-control story this week came from direct editor feedback: a request to show one ad-network's performance section inside the broader Week Overview dashboard rather than as a separate page, and a follow-up request a few days later to open the entire Week Overview to every editor, not just managers, "so they are also aware of the stats."
The first request had a real structural reason behind the original split: Week Overview had been manager-only specifically because editors work the backlog, not the aggregate view, so the ad-network section had been pulled out into its own page to be reachable by editors at all. Folding it back in meant the overview itself had to open up — for managers, as a new tab alongside the existing views; for editors, as the single section they're allowed to see, with the manager-only tabs hidden rather than merely disabled.
The second request — opening the whole overview, not just that one section — is where the real engineering work was. Un-hiding a UI tab is trivial. The actual blockers were two routes (/overview/trend and /evaluations/dashboard) that explicitly refused any non-manager caller, and a row-level security policy on the underlying tables scoped to is_admin() or editor_id = my_editor_id() — meaning an editor's own database session could only ever read their own rows. Had the UI restriction simply been removed without addressing either of those, "This week" would have rendered correctly for a manager and silently shown an editor their own individual numbers mislabeled as the whole team's aggregate — a data-correctness bug disguised as a successful feature launch, and a worse outcome than just leaving the page locked.
Making two comparison views agree with each other first
A smaller companion fix tightened the Four-Week Comparison to show only completed weeks, anchoring the comparison window on the selected week once it's finished, or on the last fully-completed week otherwise, with the same clamp applied to a week selected in the future. Applied identically server-side across both the category comparison and the ad-network's own week-on-week table, so the two views can no longer silently disagree about which weeks count — the same "two dashboards reading slightly different windows" failure mode that showed up on the enterprise platform's analytics rebuild this same week, just on a smaller surface.
The pattern: infrastructure defaults and permission removals both hide a second problem
Two unrelated threads, one lesson each. A hosting platform's log-level heuristic is a default nobody chose and most teams never audit — it's worth treating "is my error bucket actually full of errors" as a question to ask periodically, not an assumption. And removing an access restriction is never just a UI change when the restriction was load-bearing for more than the interface — the route guards and the row-level security policy underneath this week's dashboard change were doing real work that a hidden tab had been quietly relying on.
Top comments (0)