AlexxIT/go2rtc · error

failed to refresh session

Error message

failed to refresh session: %w

What it means

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.

Solutions

  1. Inspect the wrapped cause (errors.Unwrap / %v of the error) — fix the underlying auth failure first
  2. Force a full re-authentication: clear cached auth token and session, then re-run with fresh credentials
  3. Test the session endpoint manually with your hardware_id to see the raw server response
  4. Check network connectivity/DNS if the refresh call itself fails at transport level

Example fix

// before
resp, err := client.GetActiveDings() // opaque 'failed to refresh session'
// after
if _, err := client.GetActiveDings(); err != nil {
    log.Printf("refresh failed: %+v", err) // reveal wrapped cause
    client.ResetAuth() // force clean re-login
}
Defensive patterns

Strategy: try-catch

Validate before calling

// check auth health before making API calls
if err := client.EnsureAuth(); err != nil {
    return fmt.Errorf("ring auth unhealthy: %w", err)
}

Type guard

func isSessionRefreshFailure(err error) bool {
    return err != nil && strings.Contains(err.Error(), "failed to refresh session")
}

Try / catch

data, err := client.GetActiveDings()
if isSessionRefreshFailure(err) {
    client.ResetAuth() // clear token+session, re-login
    data, err = client.GetActiveDings()
} else if err != nil {
    log.Printf("unexpected: %v", err)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of AlexxIT/go2rtc@c245815e75 (2026-09-07). Data as JSON: /api/errors/fcb7dcec9c7d60cc. Report an issue: GitHub.

Appendix: source

Thrown at pkg/ring/api.go:470

		if resp.StatusCode == http.StatusUnauthorized {
			// Reset token to force refresh
			c.authMutex.Lock()
			c.authToken = nil
			c.tokenExpiry = time.Time{} // Reset token expiry
			c.authMutex.Unlock()

			if attempt == maxRetries {
				return nil, fmt.Errorf("authentication failed after %d retries", maxRetries)
			}

			// By 401 with Auth AND Session start over
			c.sessionMutex.Lock()
			c.session = nil
			c.sessionExpiry = time.Time{} // Reset session expiry
			c.sessionMutex.Unlock()

			if err := c.ensureSession(); err != nil {
				return nil, fmt.Errorf("failed to refresh session: %w", err)
			}

			req.Header.Set("Authorization", "Bearer "+c.authToken.AccessToken)
			continue
		}

		// Handle 404 error with hardware_id reference - session issue
		if resp.StatusCode == 404 && strings.Contains(url, clientAPIBaseURL) {
			var errorBody map[string]interface{}
			if err := json.Unmarshal(responseBody, &errorBody); err == nil {
				if errorStr, ok := errorBody["error"].(string); ok && strings.Contains(errorStr, c.hardwareID) {
					// Session with hardware_id not found, refresh session
					c.sessionMutex.Lock()
					c.session = nil
					c.sessionExpiry = time.Time{} // Reset session expiry
					c.sessionMutex.Unlock()

					if attempt == maxRetries {

View on GitHub (pinned to c245815e75)