vxcontrol/pentagi · warning

session expired

Error message

session expired

What it means

errSessionExpired indicates the session referenced by the cookie exists but is past its TTL. Like the other sentinel errors here it is a routine invalidation: the middleware fails the request (usually 401) instead of erroring, and the user simply needs to authenticate again.

Source

Thrown at backend/pkg/server/auth/auth_middleware.go:111

// non-malicious session/token invalidation rather than a genuine application
// error, so callers can log it at a quieter level.
func isRoutineAuthFailure(authErr error) bool {
	return errors.Is(authErr, errCookieClaimInvalid) ||
		errors.Is(authErr, errSessionExpired) ||
		errors.Is(authErr, errUserHashMismatch)
}

// errCookieClaimInvalid is returned by tryUserCookieAuthentication when the
// session cookie is present but missing one or more required claims (expired
// or otherwise invalid session) - a routine, expected condition.
//
// errSessionExpired and errUserHashMismatch mark the same category of routine
// session/token invalidation, just detected a bit later during validation: a
// session past its TTL, or a stored hash that no longer matches the user
// record (e.g. after a password change or a test database reseed).
var (
	errCookieClaimInvalid = errors.New("cookie claim invalid")
	errSessionExpired     = errors.New("session expired")
	errUserHashMismatch   = errors.New("user hash mismatch")
)

func (p *AuthMiddleware) tryUserCookieAuthentication(c *gin.Context) (authResult, error) {
	sessionObject, exists := c.Get(sessions.DefaultKey)
	if !exists {
		return authResultSkip, errors.New("can't find session object")
	}

	session, ok := sessionObject.(sessions.Session)
	if !ok {
		return authResultFail, errors.New("not a session object")
	}

	uid := session.Get("uid")
	uhash := session.Get("uhash")
	rid := session.Get("rid")
	prm := session.Get("prm")

View on GitHub (pinned to ea665308ba)

Solutions

  1. Log in again to create a fresh session
  2. Increase the session TTL/MaxAge configuration if users are logged out too aggressively
  3. Implement token/cookie refresh (sliding expiration) or re-authenticate automatically when a 401 with this cause is received

Example fix

// before
// every request fails after TTL with no recovery
// after
client.interceptors.response.use(null, async (err) => {
  if (err.response?.status === 401 && !err.config._retried) {
    await refreshSession();
    err.config._retried = true;
    return client(err.config);
  }
  throw err;
});
Defensive patterns

Strategy: retry

Validate before calling

const sessionIssuedAt = Number(localStorage.getItem('sessionIssuedAt') ?? 0);
const expired = Date.now() - sessionIssuedAt > SESSION_TTL_MS;

Try / catch

client.interceptors.response.use(null, async (err) => {
  if (err.response?.status === 401 && !err.config._retried) {
    await reauthenticate();
    err.config._retried = true;
    return client(err.config);
  }
  throw err;
});

Prevention

When it happens

Trigger: Any request made after the session's MaxAge/TTL elapsed — leaving the tab open overnight, returning after the session lifetime configured in the sessions middleware, or a session persisted beyond its expiry window.

Common situations: Long-running browser sessions or websockets that never refresh the cookie; short session TTL configured for security while users expect longer lifetimes; clock skew between issuing and validating nodes.

Related errors


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/456da44ccfb9b9d9. Report an issue: GitHub.