charmbracelet/crush · error

failed to refresh OAuth token: %w

Error message

failed to refresh OAuth token: %w

What it means

This error wraps a transport-level failure that occurred while POSTing to /workspaces/{id}/config/refresh-oauth. The client could not complete the HTTP request (connection failure, timeout, request construction error), so RefreshOAuthToken aborts before reading any response. The underlying cause is preserved via %w.

Source

Thrown at internal/client/config.go:156

	var result struct {
		Token   *oauth.Token `json:"token"`
		Success bool         `json:"success"`
	}
	if err := json.NewDecoder(rsp.Body).Decode(&result); err != nil {
		return nil, false, fmt.Errorf("failed to decode import copilot response: %w", err)
	}
	return result.Token, result.Success, nil
}

// RefreshOAuthToken refreshes an OAuth token for a provider on the
// server.
func (c *Client) RefreshOAuthToken(ctx context.Context, id string, scope config.Scope, providerID string) error {
	rsp, err := c.post(ctx, fmt.Sprintf("/workspaces/%s/config/refresh-oauth", id), nil, jsonBody(struct {
		Scope      config.Scope `json:"scope"`
		ProviderID string       `json:"provider_id"`
	}{Scope: scope, ProviderID: providerID}), http.Header{"Content-Type": []string{"application/json"}})
	if err != nil {
		return fmt.Errorf("failed to refresh OAuth token: %w", err)
	}
	defer rsp.Body.Close()
	if rsp.StatusCode != http.StatusOK {
		return fmt.Errorf("failed to refresh OAuth token: status code %d", rsp.StatusCode)
	}
	return nil
}

// ProjectNeedsInitialization checks if the project needs
// initialization.
func (c *Client) ProjectNeedsInitialization(ctx context.Context, id string) (bool, error) {
	rsp, err := c.get(ctx, fmt.Sprintf("/workspaces/%s/project/needs-init", id), nil, nil)
	if err != nil {
		return false, fmt.Errorf("failed to check project init: %w", err)
	}
	defer rsp.Body.Close()
	if rsp.StatusCode != http.StatusOK {
		return false, fmt.Errorf("failed to check project init: status code %d", rsp.StatusCode)

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Check that the server backing the client is running and reachable at the configured base URL
  2. Retry the refresh; OAuth refresh is idempotent server-side and transient network errors are the most common cause
  3. Inspect the wrapped error (errors.Unwrap / errors.As) to distinguish timeout, connection refused, or TLS problems
  4. Verify the workspace id is valid so the request is not rejected mid-flight

Example fix

// before
if err := client.RefreshOAuthToken(ctx, id, scope, providerID); err != nil {
    return err
}
// after
if err := client.RefreshOAuthToken(ctx, id, scope, providerID); err != nil {
    var netErr net.Error
    if errors.As(err, &netErr) && netErr.Timeout() {
        return retryWithBackoff(ctx)
    }
    return fmt.Errorf("refresh oauth: %w", err)
}
Defensive patterns

Strategy: retry

Validate before calling

// Before calling, verify reachability
if u, err := url.Parse(client.BaseURL()); err != nil || u.Host == "" {
    return fmt.Errorf("invalid client base URL")
}
conn, err := net.DialTimeout("tcp", u.Host, 3*time.Second)
if err != nil {
    return fmt.Errorf("server unreachable: %w", err)
}
conn.Close()

Type guard

func isTransportError(err error) bool {
    var netErr net.Error
    return errors.As(err, &netErr)
}

Try / catch

if err := c.RefreshOAuthToken(ctx, id, scope, pid); err != nil {
    var netErr net.Error
    if errors.As(err, &netErr) && netErr.Timeout() {
        // retry with backoff
    }
    return err
}

Prevention

When it happens

Trigger: Calling Client.RefreshOAuthToken(ctx, id, scope, providerID) when the HTTP POST to /workspaces/{id}/config/refresh-oauth fails at the transport layer: server unreachable, connection reset, TLS error, or context deadline exceeded.

Common situations: Server is down or restarted while refreshing an expired provider OAuth token; DNS or proxy misconfiguration in CI; network flaps on laptops switching Wi-Fi; ctx deadline too short for a slow server.

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/4ffba19c7904aa59. Report an issue: GitHub.