can1357/oh-my-pi · error

OAuth authorization failed. Please check your client credent

Error message

OAuth authorization failed. Please check your client credentials.

What it means

The MCP OAuth flow failed with an error message containing '403' or 'unauthorized', mapped to this message. It means the authorization/token server rejected the client — typically bad client_id/client_secret or missing permissions — rather than a network or user-timing problem.

Source

Thrown at packages/coding-agent/src/modes/controllers/mcp-command-controller.ts:1034

				credentialId,
				clientId: flow.resolvedClientId,
				resource: flow.resource,
			};
		} catch (error) {
			// Esc, an external abort, or a newer MCP flow are neutral
			// cancellations. The timeout path also aborts the controller but does
			// not set this flag, so it remains a surfaced error.
			if (cancellationRequested) {
				throw new MCPOAuthCancelledError();
			}

			const errorMsg = error instanceof Error ? error.message : String(error);

			// Provide helpful error messages based on failure type
			if (errorMsg.includes("timeout") || errorMsg.includes("timed out")) {
				throw new Error("OAuth flow timed out. Please try again.");
			} else if (errorMsg.includes("403") || errorMsg.includes("unauthorized")) {
				throw new Error("OAuth authorization failed. Please check your client credentials.");
			} else if (errorMsg.includes("invalid_grant")) {
				throw new Error("OAuth authorization code is invalid or expired. Please try again.");
			} else if (errorMsg.includes("ECONNREFUSED") || errorMsg.includes("fetch failed")) {
				throw new Error("Could not connect to OAuth server. Please check the URLs and your network connection.");
			} else {
				throw new Error(`OAuth authentication failed: ${errorMsg}`);
			}
		} finally {
			this.ctx.editor.onEscape = originalOnEscape;
			externalSignal?.removeEventListener("abort", onExternalAbort);
			manualInputClaim?.clear("Manual MCP OAuth input cleared");
			flowClaim.release();
		}
	}

	/**
	 * Fold a completed OAuth flow back into a server config. Owns the
	 * persistence policy in one place: the auth block records the credential

View on GitHub (pinned to 9690622007)

Solutions

  1. Verify client_id and client_secret are correct and current in the MCP OAuth config
  2. Re-register or update the OAuth app so the requested scopes and redirect URI are allowed
  3. Confirm whether the provider requires a public PKCE client instead of a secret-based client
  4. Retry the flow after fixing credentials

Example fix

// before
clientSecret: "sk-old-rotated-secret"
// after
clientSecret: "sk-current-secret-from-provider-dashboard"
Defensive patterns

Strategy: validation

Validate before calling

// Validate credentials are non-empty and well-formed before the flow
if (!clientId?.trim() || !clientSecret?.trim()) {
  throw new Error('OAuth client_id and client_secret are required for this provider');
}

Try / catch

try {
  await runMcpOAuthFlow();
} catch (err) {
  if (err instanceof Error && err.message.includes('client credentials')) {
    // prompt user to re-enter/rotate client_id/client_secret
  }
}

Prevention

When it happens

Trigger: Token exchange returns HTTP 403; server responds with 'unauthorized' because the client credentials are wrong, the client is not registered for the requested scopes, or the redirect URI is not allowlisted.

Common situations: Copy-pasted client_id/client_secret with a typo or stale values; provider rotated the secret; confidential client configured where a public (PKCE) client is required; scope not granted to the app registration.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/e713699bf577da0a. Report an issue: GitHub.