vxcontrol/pentagi · error · errUserHashMismatch
%w - token invalid for this installation
Error message
%w - token invalid for this installation
What it means
tryProtoTokenAuthentication compares apiClaims.UHASH (baked into the JWT at creation) with the current DB hash for the user. On mismatch it returns errUserHashMismatch wrapped as "token invalid for this installation" — the token was issued by a different installation/salt or the user's hash changed, so the token is bound to the wrong environment.
Source
Thrown at backend/pkg/server/auth/auth_middleware.go:247
if status != models.TokenStatusActive {
return authResultFail, errors.New("token has been revoked")
}
// Verify user hash matches database
dbHash, userStatus, err := p.userCache.GetUserHash(apiClaims.UID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return authResultFail, errors.New("user has been deleted")
}
return authResultFail, fmt.Errorf("error checking user status: %w", err)
}
if userStatus == models.UserStatusBlocked {
return authResultFail, errors.New("user has been blocked")
}
if dbHash != apiClaims.UHASH {
return authResultFail, fmt.Errorf("%w - token invalid for this installation", errUserHashMismatch)
}
// generate UUID from user hash (fallback to empty string if hash is invalid)
uuid, err := rdb.MakeUuidStrFromHash(apiClaims.UHASH)
if err != nil {
// Use empty UUID for invalid hashes (e.g., in tests)
uuid = ""
}
// set session fields similar to regular login
c.Set("uid", apiClaims.UID)
c.Set("uhash", apiClaims.UHASH)
c.Set("rid", apiClaims.RID)
c.Set("tid", models.UserTypeAPI.String())
c.Set("prm", privileges)
c.Set("gtm", time.Now().Unix())
c.Set("exp", apiClaims.ExpiresAt.Unix())
c.Set("uuid", uuid)View on GitHub (pinned to ea665308ba)
Solutions
- Regenerate the API token on the installation you are calling so UHASH matches the current DB hash
- Align GLOBAL_SALT across environments that share a database (or isolate the databases)
- After any salt rotation, re-issue all API tokens and require users to log in again
- Confirm the caller is targeting the correct installation endpoint (prod vs staging URL)
Example fix
// before curl https://staging.example.com/api -H "Authorization: Bearer $PROD_TOKEN" // after curl https://staging.example.com/api -H "Authorization: Bearer $STAGING_TOKEN"
Defensive patterns
Strategy: fallback
Type guard
func isInstallMismatch(err error) bool {
return errors.Is(err, auth.ErrUserHashMismatch)
} Try / catch
if isInstallMismatch(err) {
// token bound to another installation: re-issue on the target environment
return reissueTokenOnTarget()
} Prevention
- Keep one salt per environment and never share databases across different salts
- Re-issue all API tokens after GLOBAL_SALT rotation
- Label tokens with their issuing environment and verify the target URL matches
- Treat UHASH mismatch as a configuration drift signal and alert on it
When it happens
Trigger: API token created under one GLOBAL_SALT used against a server with a different salt; DB copied between environments while tokens/cookies persist; user hash regenerated (e.g., password change flows that rotate the hash) invalidating previously issued tokens.
Common situations: Same Postgres shared by prod and staging with different salts; migrating installations without re-issuing API tokens; rotating GLOBAL_SALT without invalidating/re-issuing all API tokens.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- token required
- token is invalid
- token not found in database
- token has been revoked
- %w - session invalid for this installation
AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01).
Data as JSON: /api/errors/6169987221a8cf62.
Report an issue: GitHub.