vxcontrol/pentagi · warning

user hash mismatch - session invalid for this installation

Error message

user hash mismatch - session invalid for this installation

What it means

errUserHashMismatch means the hash stored in the session does not match the hash stored for the user in the database. The hash is invalidated whenever the user record changes materially (e.g. password change) or the database is reseeded, so sessions issued before that become invalid. This prevents a stolen/old session from remaining valid after credential changes.

Source

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

// 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")
	exp := session.Get("exp")

View on GitHub (pinned to ea665308ba)

Solutions

  1. Log out and log in again — a new session will embed the current user hash
  2. If it appears right after a password change, ensure the client clears/refreshes the session after the change-password call
  3. In test environments, clear cookies after reseeding the database

Example fix

// before
await api.changePassword(newPass); // session not refreshed
// after
await api.changePassword(newPass);
await api.logout();
window.location.href = '/login';
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await api.call();
} catch (e) {
  if (e.response?.status === 401) {
    // likely password changed or DB reseeded — force re-login
    await api.logout();
    window.location.href = '/login';
  }
}

Prevention

When it happens

Trigger: Making an API request with a session cookie created before the user's password was changed, or against a database that was re-seeded/recreated (common in dev/test) so the stored user hash differs from the one embedded in the cookie.

Common situations: Developer changes a password in one tab; other tabs keep failing with 401. CI/test databases are reseeded while a browser still holds an old session cookie. Restoring a DB dump invalidates all previously issued sessions.

Related errors


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