{"record":{"id":"b76a34e0d5f06031","repo":"vxcontrol/pentagi","slug":"w-session-invalid-for-this-installation","errorCode":null,"errorMessage":"%w - session invalid for this installation","messagePattern":"%w - session invalid for this installation","errorType":"http","errorClass":"errUserHashMismatch","httpStatus":401,"severity":"error","filePath":"backend/pkg/server/auth/auth_middleware.go","lineNumber":175,"sourceCode":"\n\tdbHash, userStatus, err := p.userCache.GetUserHash(userID)\n\tif err != nil {\n\t\tif errors.Is(err, gorm.ErrRecordNotFound) {\n\t\t\treturn authResultFail, errors.New(\"user has been deleted\")\n\t\t}\n\t\treturn authResultFail, fmt.Errorf(\"error checking user status: %w\", err)\n\t}\n\n\tswitch userStatus {\n\tcase models.UserStatusBlocked:\n\t\treturn authResultFail, errors.New(\"user has been blocked\")\n\tcase models.UserStatusCreated:\n\t\treturn authResultFail, errors.New(\"user is not ready\")\n\tcase models.UserStatusActive:\n\t}\n\n\tif dbHash != sessionHash {\n\t\treturn authResultFail, fmt.Errorf(\"%w - session invalid for this installation\", errUserHashMismatch)\n\t}\n\n\tc.Set(\"prm\", prms)\n\tc.Set(\"uid\", userID)\n\tc.Set(\"uhash\", sessionHash)\n\tc.Set(\"rid\", rid.(uint64))\n\tc.Set(\"exp\", exp.(int64))\n\tc.Set(\"gtm\", gtm.(int64))\n\tc.Set(\"tid\", tid.(string))\n\tc.Set(\"uname\", uname.(string))\n\n\tif slices.Contains(prms, PrivilegeAutomation) {\n\t\tc.Set(\"cpt\", \"automation\")\n\t}\n\n\treturn authResultOk, nil\n}\n","sourceCodeStart":157,"sourceCodeEnd":193,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/server/auth/auth_middleware.go#L157-L193","documentation":"tryUserCookieAuthentication compares the hash stored in the session (sessionHash) with the current hash in the database (dbHash). On mismatch it returns errUserHashMismatch wrapped as \"session invalid for this installation\". The user hash binds sessions to a specific installation/salt, so a session issued elsewhere or before a salt change is rejected.","triggerScenarios":"Cookie issued by a different PentAGI installation (different GLOBAL_SALT) pointed at the same DB; user row's hash regenerated (salt change, password/user rebuild) while the browser still holds the old cookie; copying a production DB into a dev instance with a different salt and reusing browser cookies.","commonSituations":"Staging/prod sharing one database with different salts; restoring a DB dump locally then loading an old cookie; changing GLOBAL_SALT env without forcing users to re-login.","solutions":["Clear the browser cookie for this instance and log in again to mint a session with the current installation's hash","Make GLOBAL_SALT identical between installations that share a database (or never share the DB)","Communicate that changing GLOBAL_SALT invalidates all existing sessions/tokens and requires re-login","Ensure load balancer routes users to replicas configured with the same salt"],"exampleFix":"// before\n// dev instance sharing prod DB with different salt\nGLOBAL_SALT=dev-salt\n// after\nGLOBAL_SALT=<same-as-issuing-installation>","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"if errors.Is(err, auth.ErrUserHashMismatch) {\n    // session belongs to another installation: drop it and re-authenticate\n    clearSessionCookie()\n    return redirectToLogin()\n}","preventionTips":["Never share a database between installations configured with different GLOBAL_SALT values","Clear browser cookies after switching an environment's salt or database","On salt rotation, force full re-login (sessions are invalidated by design)","Document that sessions are installation-bound and not portable"],"tags":["authentication","session","hash-mismatch"],"backgroundTag":"user-hash-mismatch","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}