{"record":{"id":"3585d6dd318f0d20","repo":"gotify/server","slug":"failed-to-elevate-session-v","errorCode":null,"errorMessage":"failed to elevate session: %v","messagePattern":"failed to elevate session: (.+?)","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"api/oidc.go","lineNumber":269,"sourceCode":"\t\tw.Header().Set(\"Location\", \"../../\")\n\t\tw.WriteHeader(http.StatusTemporaryRedirect)\n\t}\n\treturn gin.WrapF(rp.CodeExchangeHandler(rp.UserinfoCallback(callback), a.Provider))\n}\n\nfunc (a *OIDCAPI) handleElevationCallback(w http.ResponseWriter, elevate *pendingElevation, user *model.User) {\n\tclient, err := a.DB.GetClientByID(elevate.ClientID)\n\tif err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"database error: %v\", err), http.StatusInternalServerError)\n\t\treturn\n\t}\n\tif client == nil || client.UserID != user.ID {\n\t\thttp.Error(w, \"client not found\", http.StatusNotFound)\n\t\treturn\n\t}\n\televatedUntil := time.Now().Add(time.Duration(elevate.DurationSeconds) * time.Second)\n\tif err := a.DB.UpdateClientElevatedUntil(client.ID, &elevatedUntil); err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"failed to elevate session: %v\", err), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// The UI rechecks the authentication when the tab is closed.\n\tw.WriteHeader(http.StatusOK)\n\tw.Header().Add(\"content-type\", \"text/html\")\n\tio.WriteString(w, `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <title>Gotify Session Elevation</title>\n  <meta charset=\"UTF-8\" />\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n</head>\n<body>\n  <h1 style=\"text-align:center\">Gotify session elevation successful. Close this tab to continue.</h1>\n  <script>window.close();</script>\n</body>\n</html>`)","sourceCodeStart":251,"sourceCodeEnd":287,"githubUrl":"https://github.com/gotify/server/blob/14bfc256276775c425f988d621dccfe705de18ac/api/oidc.go#L251-L287","documentation":"handleElevationCallback (api/oidc.go:269) returns HTTP 500 'failed to elevate session: %v' when a.DB.UpdateClientElevatedUntil(client.ID, &elevatedUntil) errors after the client was found and ownership verified. The client lookup succeeded but persisting the new elevated-until timestamp failed.","triggerScenarios":"UpdateClientElevatedUntil fails during the elevation callback: database connection dropped between the read and the write, write timeout, read-only replica/transaction, constraint or schema error, or disk-full on the DB server.","commonSituations":"Remote Postgres briefly unavailable or failing over; DB user lacking UPDATE privilege on the clients table; migrations missing a column used by the update; SQLite database locked by another process; storage quota exhausted.","solutions":["Check the wrapped error and database server logs; verify connectivity and that the DB accepts writes","Confirm the DB user has UPDATE permission on the clients table and migrations are current","Retry the elevation flow (with a fresh state) once the database is writable","For SQLite lock errors, ensure no other process holds the DB and consider WAL mode","Rule out read-only replica routing or disk-space/quota issues"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Before the elevation round-trip, verify the DB accepts writes on clients\nctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\ndefer cancel()\nif err := db.PingContext(ctx); err != nil {\n    return err\n}\n// optionally a probe write/transaction to catch read-only replicas early","typeGuard":null,"tryCatchPattern":"err := db.UpdateClientElevatedUntil(clientID, &elevatedUntil)\nif err != nil {\n    if isTransient(err) { // deadlock, bad conn, timeout, lock\n        return retryWithBackoff(func() error {\n            return db.UpdateClientElevatedUntil(clientID, &elevatedUntil)\n        })\n    }\n    return fmt.Errorf(\"update elevated_until: %w\", err)\n}","preventionTips":["Grant the DB user UPDATE privilege on the clients table","Keep migrations current so the elevated_until column exists","Enable WAL/busy-timeout for SQLite to avoid lock errors","Monitor for read-only replica routing and disk-full conditions","Retry transient write failures with backoff before failing the request"],"tags":["database","oidc","http-500","elevation","update-failed"],"backgroundTag":"database-write-failed","analyzedSha":"14bfc256276775c425f988d621dccfe705de18ac","analyzedAt":"2026-09-05T12:52:36.781Z","contentChangedAt":"2026-09-05T12:52:36.781Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}