{"record":{"id":"6fc89af32e1c3bfb","repo":"gotify/server","slug":"database-error-v","errorCode":null,"errorMessage":"database error: %v","messagePattern":"database error: (.+?)","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"api/oidc.go","lineNumber":260,"sourceCode":"\t\tclient, err := a.createClient(session.ClientName, user.ID)\n\t\tif err != nil {\n\t\t\thttp.Error(w, fmt.Sprintf(\"failed to create client: %v\", err), http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\t\tauth.SetCookie(w, client.Token, auth.CookieMaxAge, a.SecureCookie)\n\t\t// A reverse proxy may have already stripped a url prefix from the URL\n\t\t// without us knowing, we have to make a relative redirect.\n\t\t// We cannot use http.Redirect as this normalizes the Path with r.URL.\n\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>","sourceCodeStart":242,"sourceCodeEnd":278,"githubUrl":"https://github.com/gotify/server/blob/14bfc256276775c425f988d621dccfe705de18ac/api/oidc.go#L242-L278","documentation":"handleElevationCallback (api/oidc.go:260) returns HTTP 500 'database error: %v' when a.DB.GetClientByID(elevate.ClientID) returns a hard error while resolving the client referenced by the pending elevation session. Unlike the 404 case, this means the lookup itself failed (connection, query, or driver error), not that the client is missing.","triggerScenarios":"The OIDC elevation flow (ElevateHandler -> provider -> CallbackHandler -> handleElevationCallback) reaches GetClientByID and the DB layer errors: connection refused/dropped, timeout, SQL syntax or schema error, or driver-level failure.","commonSituations":"Database restarted or unreachable mid-session; connection pool exhausted under load; schema drift after an upgrade; transient network partition between gotify and a remote Postgres.","solutions":["Read the wrapped driver error and check database connectivity/health from the gotify host","Retry the elevation flow after the database recovers (a fresh state is required)","Check for schema mismatches and run pending migrations","Increase connection-pool/timeout settings if the error is a timeout under load","Inspect DB server logs for the corresponding error at the same timestamp"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Before driving users through the elevation flow, confirm the DB answers\nctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\ndefer cancel()\nif err := db.PingContext(ctx); err != nil {\n    // defer or skip the elevation flow until connectivity is restored\n    return\n}","typeGuard":null,"tryCatchPattern":"c, err := db.GetClientByID(id)\nif err != nil {\n    if isTransient(err) { // net.Error, driver.ErrBadConn, context deadline\n        return retryWithBackoff(err)\n    }\n    return fmt.Errorf(\"get client by id: %w\", err)\n}","preventionTips":["Monitor DB connectivity and set sane connect/read timeouts","Pin schema versions with migrations so queries never hit missing columns","Use a connection pool with health checks (ConnMaxLifetime, ping on acquire)","Alert on client-lookup failures at the DB layer","Prefer retrying transient network errors before surfacing 500s to users"],"tags":["database","oidc","http-500","elevation"],"backgroundTag":"database-connection-failure","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"}