Skip to content
Centriu
Centriu Axis

Spontaneous Auto-Logout Stale-Closure Guard Automation: A Safety Check That Could Never See a Session

Centriu Axis is built around an explicit, stated design principle: the product should never log a user out on its own, spontaneously, without the user themselves choosing to sign out — because a false or transient session-loss signal (a network hiccup, an auth provider quirk) is a strictly worse experience than occasionally tolerating a request that briefly fails, and a wrongly-triggered logout can mean lost context, an interrupted task, or a person locked out of work they were actively doing. A dedicated guard enforces this: on any unexpected event reporting no active session, it checks whether the user genuinely had one a moment ago, and refuses to clear the application's session state if so — treating a sudden, unexplained loss as suspicious rather than authoritative. That check read its two input values, the current session and the current user, directly from React component state, from inside a function defined once when the component first mounted and never redefined afterward. Because that function's dependency array never changes, it permanently "remembers" both values exactly as they were on the component's FIRST render — both null, since a fresh page load naturally starts with no session yet established. Every subsequent call to this exact guard, for the entire life of the page, could only ever see those two original null values — meaning the specific check built to say "this user had a session a moment ago, do not log them out" could never once actually detect that the user had one, regardless of how long they had genuinely been logged in. The exact spontaneous-logout event the guard exists to catch and block would pass through it every single time. Fixed by mirroring both values into refs updated synchronously on every render, and having the guard — and a related session-deduplication check in the same file — read the refs' current values instead of the stale, closed-over state.
Guard permanently saw null
Reads a live ref now
Authentication session configuration screen
A safety check that could never see a session.

A safety check reading a value that never updates is not a weaker check — it is no check at all

Writing an explicit guard against a specific bad outcome — here, treating an unexpected session-loss signal as suspicious rather than acting on it immediately — is a deliberate, principled engineering decision, and getting it into the code at all reflects the right instinct. That instinct is only as good as the actual, current values the guard reads at the moment it runs. A value captured inside a JavaScript closure that is created once and never re-created reflects the state of the world at the exact moment the closure was created — not the state of the world when the closure is later CALLED, which, for a callback that lives for the life of a long-running page, can be an arbitrarily long time and an arbitrary number of state changes later. A guard reading such a value is not making a slightly-outdated decision; if the value it depends on can only ever be its very first, initial value, the guard is making the SAME decision every single time, regardless of anything that has actually happened since.

How the underlying problem shows up before you fix it

A function is defined once inside a `useEffect` (or an equivalent one-time setup) with a dependency array that causes it to run exactly once, for the life of the component, rather than being redefined whenever the values it reads change.

That function reads component state variables directly (not via a ref) — meaning it permanently sees those variables' values as they were at the exact moment the function was originally created, not their current, live values.

A guard or safety check built specifically to compare a CURRENT value against a PRIOR one (here: "did this user have a session a moment ago?") always evaluates the prior-value side as whatever it was on the very first render — which, for authentication state specifically, is almost always empty/null, since a session has not yet loaded at that exact moment.

A defensive mechanism explicitly documented in the codebase or in a design principle ("this product must never do X spontaneously") turns out, on inspection, to never actually engage under the exact real-world condition it was written to catch — passing every test that happens to exercise only the FIRST render's behavior.

A closely related deduplication or comparison check in the SAME function suffers the identical defect for the identical reason — because both checks were written to read from the same stale, one-time-captured state rather than from a live, current source.

How a guard against spontaneous logout was given eyes that could actually see

Centriu Axis maintains an explicit product principle, stated directly in its own code comments: the application must never sign a user out spontaneously, on its own initiative, without that user's own deliberate action. The reasoning is concrete — an authentication provider can occasionally report a transient, false loss of session (a network blip, a provider-side quirk during token refresh), and reacting to that report by immediately clearing the user's session and forcing a fresh login is a worse outcome for a real person mid-task than simply tolerating a request that might briefly return a 401 until the session self-corrects.

A specific guard exists to enforce exactly this: whenever an authentication state-change handler receives an event reporting no current session, before clearing any application state, it checks a boolean — had this user genuinely been authenticated a moment ago? — and, if so, refuses to proceed with the logout, deliberately leaving the user on-screen with their last known session rather than treating a sudden, unexplained signal as authoritative truth.

That check, before this fix, computed its answer as `!!supabaseUser || !!session` — reading both `supabaseUser` and `session` directly as React component state, from inside a function (`processSession`) defined ONE TIME, inside a `useEffect` whose own dependency array is `[loadProfile]`, itself a `useCallback` with an empty dependency array — meaning `processSession` itself is created exactly once, when the component first mounts, and is never recreated for the rest of that component's life. A function defined once in JavaScript closes over the specific variable BINDINGS that existed at the moment it was created — not over whatever those variables' values happen to be later. Both `session` and `supabaseUser` are `null` at the exact moment a fresh page first mounts (a session has not yet had the chance to load). The guard's read of `!!supabaseUser || !!session`, therefore, permanently evaluates against those two ORIGINAL, first-render values, for the entire remaining life of the page — regardless of how many times the user's real session state actually changed afterward, and regardless of how long they had genuinely been logged in by the time any later event fired.

The practical consequence: the guard built specifically to say "this user had a session a moment ago, do not clear it on an unexplained signal" could never once see that the user had one — its own two inputs were permanently fixed at `null` and `null`. Any unexpected null-session event, at any point after the very first render, would find `hadAuthBefore` evaluating to `false`, and the guard would let the logout proceed — precisely the spontaneous, self-initiated sign-out the entire mechanism exists to prevent, passing through it every time it mattered. A closely related check a few lines later, deduplicating a specific `INITIAL_SESSION` event by comparing an incoming user ID against `supabaseUser?.id`, suffered the identical defect for the identical reason, always comparing against the same permanently-null original value.

The fix introduces two refs, `sessionRef` and `supabaseUserRef`, initialized from the corresponding state and explicitly reassigned to the current state value on every single render (`sessionRef.current = session`, immediately after the `useState` hooks, executed unconditionally each render pass) — a ref's `.current` property, unlike a variable captured by a closure, is read fresh every time it is accessed, regardless of when the surrounding function was originally defined. The guard now computes `hadAuthBefore` from `!!supabaseUserRef.current || !!sessionRef.current`, and the `INITIAL_SESSION` deduplication check now compares against `supabaseUserRef.current?.id` — both reading the genuinely current session state at the exact moment the check actually runs, rather than the state that existed when the enclosing function was first created. The refs are additionally updated synchronously, inline, at the exact point `processSession` itself calls `setSession`/`setSupabaseUser` — ensuring the very next invocation of the guard, even one firing in the same tick, already sees the update, rather than waiting for React's own state-update-and-re-render cycle to complete first.

What is actually built today

Centriu Axis's guard against spontaneous, unexplained logout reads the user's CURRENT authentication state via a ref updated on every render, rather than a value permanently fixed at whatever it was on the component's first render.

A related check deduplicating a specific session-initialization event against the currently-known user ID reads from the same live ref, closing an identical stale-closure defect in the same function.

Both refs are updated synchronously at the exact point session state is set, so even a guard check firing in the same tick as a state change already sees the update — not only after React's own next render.

The underlying product principle — Axis never signs a user out on its own initiative without their explicit action — is now actually enforced by a guard capable of detecting the exact condition (a genuine prior session) it was built to check for.

No change was made to how or when a genuine, user-initiated logout proceeds — the fix is scoped entirely to correctly detecting an UNEXPECTED, spontaneous session-loss signal.

A guard permanently checking against a session that never existed yet (illustrative framing of the actual measured finding)

Before the fix, a person genuinely logged into Centriu Axis for an extended session, experiencing a single transient authentication hiccup — a token refresh briefly failing against a flaky network — would trigger the guard meant specifically to protect against exactly this scenario. That guard's own check for "did this user have a session a moment ago?" would evaluate against two values still fixed at their original, page-load values of null and null, answer "no," and let the spontaneous logout proceed — clearing a genuinely active session over a transient blip, the precise outcome the guard existed to prevent. After the fix, the same guard reads the user's actual, current session state via a live ref, correctly recognizes a genuine prior session, and keeps the user signed in through the transient event.

What changes operationally

Centriu Axis's guard against spontaneous, self-initiated logout now reads a user's genuinely current authentication state at the moment it checks, rather than a value permanently frozen at the component's first render — closing a gap where the guard's own core check could never actually detect that a user had a prior session, meaning the specific event it exists to catch could pass through unblocked in every real-world case after the very first render.

When this is not the right fit

This automation governs Centriu Axis's own internal defense against spontaneous, unexplained session termination — it does not change how a user's own deliberate sign-out works, and it does not add a new authentication feature; it corrects an existing guard's ability to actually see the condition it was already designed to check for.

Reading state through a closure vs. reading it through a ref

Reading component state directly inside a callback is the more natural, common pattern in React, and works correctly whenever the callback itself is recreated on every render (which most are, by default). The specific risk is a callback deliberately defined only ONCE, inside a one-time-effect pattern chosen for a genuinely good reason (avoiding a duplicate initial load on mount) — that same one-time definition permanently fixes whatever state values the callback references at their first-render values. Mirroring the specific values a stale callback needs into refs, updated on every render regardless of the callback's own lifecycle, preserves the original one-time-setup benefit while giving that callback a way to see genuinely current data whenever it actually runs.

Related systems

Main system: Centriu Axis.

What it does NOT do

  • Does not change how a user's own deliberate, self-initiated sign-out works inside Centriu Axis — this fix corrects only the guard against an UNEXPECTED, spontaneous session-loss signal.
  • Does not add a new authentication mechanism or session-management feature — the underlying product principle (never log a user out spontaneously) already existed and was already the intended behavior; this fix corrects the guard's ability to actually detect the condition it checks for.
  • Does not retroactively identify how many real users may have experienced an incorrect spontaneous logout before this fix shipped — a team with that specific historical concern would need its own separate log review.
  • Does not change the deliberate one-time-effect pattern the surrounding code uses to avoid a duplicate initial session load on mount — the fix adds refs alongside that pattern rather than restructuring it.
  • Does not claim every stale-closure risk in Centriu Axis's codebase was found and fixed by this specific investigation — this fix is scoped to the two checks (the spontaneous-logout guard and the INITIAL_SESSION dedup) described above, in this specific file.

Security and governance

Centriu Axis's guard against spontaneous, unexplained session termination now reads a user's genuinely current authentication state rather than a value frozen at the component's first render, closing a gap where a transient authentication signal could incorrectly clear a real, active session. Full detail on access control and audit trails lives at /governanca and /iso.

Pricing and contracting

Included at no extra cost with any Centriu contract. Values and terms come from the official pricing table at /precos (Centriu's central source — never restated here).

Frequently asked questions

What is a "stale closure," in plain terms?

A function defined once, at one specific moment, that reads a variable directly — that function will keep seeing the variable's value AS IT WAS at that moment, not its later, updated value, for as long as that same function instance continues to be used.

Why was the guard's own value permanently null?

Because the function containing the guard was created exactly once, when the page first loaded — and at that exact moment, before any session had loaded, both values it read were genuinely null. The function never got recreated afterward to see the real, later values.

Could this have caused a real user to be logged out unexpectedly?

Yes — the guard specifically built to prevent that outcome could not detect the one condition (a genuine prior session) it existed to check for, meaning a transient authentication signal could proceed to clear a real, active session.

Why use a ref instead of just fixing the dependency array?

The one-time-effect pattern (running only on mount) is itself a deliberate design choice avoiding a duplicate initial session load — changing it could reintroduce that problem. A ref lets the existing one-time function see current values without restructuring when it runs.

Was a similar issue found anywhere else in the same file?

Yes — a related check deduplicating a specific session-initialization event compared against the same stale, permanently-null user value, and was fixed with the identical ref-based approach in the same pass.

What does Centriu Axis cost?

It is sold by subscription with a published starting price — exact current values are on the central pricing page.

See how Centriu Axis protects a real session from a false signal

Reach our commercial team directly, or leave your details below — we'll follow up with guidance for your case.

Sources

  1. Centriu Axis — public product page — Centriu, 2026-07-20 · link(primária)
  2. Centriu Axis — public factsheet (API, JSON) — Centriu, 2026-07-21 · link
  3. Law No. 13,709/2018 — Brazil’s General Data Protection Law (LGPD) — Presidência da República (Brazil), 2018-08-14 · link

Last material update on .

By · AI-assisted production, with human review