{"record":{"id":"fcb7dcec9c7d60cc","repo":"AlexxIT/go2rtc","slug":"failed-to-refresh-session-w","errorCode":null,"errorMessage":"failed to refresh session: %w","messagePattern":"failed to refresh session: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/ring/api.go","lineNumber":470,"sourceCode":"\t\tif resp.StatusCode == http.StatusUnauthorized {\n\t\t\t// Reset token to force refresh\n\t\t\tc.authMutex.Lock()\n\t\t\tc.authToken = nil\n\t\t\tc.tokenExpiry = time.Time{} // Reset token expiry\n\t\t\tc.authMutex.Unlock()\n\n\t\t\tif attempt == maxRetries {\n\t\t\t\treturn nil, fmt.Errorf(\"authentication failed after %d retries\", maxRetries)\n\t\t\t}\n\n\t\t\t// By 401 with Auth AND Session start over\n\t\t\tc.sessionMutex.Lock()\n\t\t\tc.session = nil\n\t\t\tc.sessionExpiry = time.Time{} // Reset session expiry\n\t\t\tc.sessionMutex.Unlock()\n\n\t\t\tif err := c.ensureSession(); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to refresh session: %w\", err)\n\t\t\t}\n\n\t\t\treq.Header.Set(\"Authorization\", \"Bearer \"+c.authToken.AccessToken)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Handle 404 error with hardware_id reference - session issue\n\t\tif resp.StatusCode == 404 && strings.Contains(url, clientAPIBaseURL) {\n\t\t\tvar errorBody map[string]interface{}\n\t\t\tif err := json.Unmarshal(responseBody, &errorBody); err == nil {\n\t\t\t\tif errorStr, ok := errorBody[\"error\"].(string); ok && strings.Contains(errorStr, c.hardwareID) {\n\t\t\t\t\t// Session with hardware_id not found, refresh session\n\t\t\t\t\tc.sessionMutex.Lock()\n\t\t\t\t\tc.session = nil\n\t\t\t\t\tc.sessionExpiry = time.Time{} // Reset session expiry\n\t\t\t\t\tc.sessionMutex.Unlock()\n\n\t\t\t\t\tif attempt == maxRetries {","sourceCodeStart":452,"sourceCodeEnd":488,"githubUrl":"https://github.com/AlexxIT/go2rtc/blob/c245815e75e2a5fd60b4290f12bfc04e55a984d3/pkg/ring/api.go#L452-L488","documentation":"After an HTTP 401 the client discards its session and calls ensureSession() to build a new one, but that call returned an error. The library wraps the underlying cause (usually an auth-token or session-creation failure) with this message, so the real reason is in the wrapped %w error.","triggerScenarios":"Any RingApi.Request() call that receives 401 Unauthorized and whose subsequent ensureSession() fails — e.g. ensureAuth fails to exchange the refresh token, or Ring's session endpoint rejects the new session request.","commonSituations":"Expired refresh token, Ring session endpoint (oauth/session API) returning 4xx due to changed API contract, network outage between the 401 and the refresh call, or invalid hardware_id sent in the session payload.","solutions":["Inspect the wrapped cause (errors.Unwrap / %v of the error) — fix the underlying auth failure first","Force a full re-authentication: clear cached auth token and session, then re-run with fresh credentials","Test the session endpoint manually with your hardware_id to see the raw server response","Check network connectivity/DNS if the refresh call itself fails at transport level"],"exampleFix":"// before\nresp, err := client.GetActiveDings() // opaque 'failed to refresh session'\n// after\nif _, err := client.GetActiveDings(); err != nil {\n    log.Printf(\"refresh failed: %+v\", err) // reveal wrapped cause\n    client.ResetAuth() // force clean re-login\n}","handlingStrategy":"try-catch","validationCode":"// check auth health before making API calls\nif err := client.EnsureAuth(); err != nil {\n    return fmt.Errorf(\"ring auth unhealthy: %w\", err)\n}","typeGuard":"func isSessionRefreshFailure(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"failed to refresh session\")\n}","tryCatchPattern":"data, err := client.GetActiveDings()\nif isSessionRefreshFailure(err) {\n    client.ResetAuth() // clear token+session, re-login\n    data, err = client.GetActiveDings()\n} else if err != nil {\n    log.Printf(\"unexpected: %v\", err)\n}","preventionTips":["Refresh the session periodically rather than only on 401","Log errors with %+v to see the wrapped cause","Keep system clock synced — expired-token logic depends on it","Cache refresh tokens durably so they stay valid"],"tags":["session","authentication","ring","http-401"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"c245815e75e2a5fd60b4290f12bfc04e55a984d3","analyzedAt":"2026-09-07T11:47:02.965Z","contentChangedAt":"2026-09-07T11:47:02.965Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}