can1357/oh-my-pi · error · Error

Broker returned non-OAuth credential for ${provider}

Error message

Broker returned non-OAuth credential for ${provider}

What it means

refreshBrokeredMcpOAuthCredential delegates an MCP OAuth refresh to the auth broker via authStorage.forceRefreshCredentialById and assumes the refreshed entry is of type "oauth". If the broker hands back a credential with a different type (e.g. "api_key"), the shape expected by the OAuth refresh path (access/refresh/expires fields) is not there, so this error is thrown as a type-invariant guard.

Source

Thrown at packages/coding-agent/src/mcp/oauth-credentials.ts:131

	const authorizationUrl = material && "authorizationUrl" in material ? material.authorizationUrl : undefined;
	const resourceIsFallback = !material?.resource && Boolean(opts.serverUrl);
	const resource = material?.resource ?? (resourceIsFallback ? opts.serverUrl : undefined);
	return refreshMCPOAuthToken(tokenUrl, credential.refresh, material?.clientId, material?.clientSecret, resource, {
		authorizationUrl,
		stripSameOriginResource: resourceIsFallback,
		signal: opts.signal,
	});
}

async function refreshBrokeredMcpOAuthCredential(
	authStorage: AuthStorage,
	credentialId: number,
	provider: string,
	signal?: AbortSignal,
): Promise<OAuthCredentials> {
	const entry = await authStorage.forceRefreshCredentialById(credentialId, signal);
	if (entry.credential.type !== "oauth") {
		throw new Error(`Broker returned non-OAuth credential for ${provider}`);
	}
	const refreshed = entry.credential;
	return {
		access: refreshed.access,
		refresh: REMOTE_REFRESH_SENTINEL,
		expires: refreshed.expires,
		accountId: refreshed.accountId,
		email: refreshed.email,
		projectId: refreshed.projectId,
		enterpriseUrl: refreshed.enterpriseUrl,
	};
}

/**
 * Resolve and refresh one stored MCP OAuth row through the durable credential owner.
 *
 * Local rows use their embedded OAuth metadata; broker-redacted rows delegate the
 * grant to the broker. The MCP manager and standalone credential consumers share

View on GitHub (pinned to 9690622007)

Solutions

  1. Verify the credential id maps to an OAuth-type credential in the broker's vault (re-register or fix the id)
  2. Re-authenticate the MCP server so the vault row is recreated with type "oauth"
  3. Check for vault migration or corruption that relabeled the credential type and restore from a known-good state
  4. Update the caller's credential-id resolution (e.g. mcpOAuthServerUrlFromCredentialId mapping) if ids shifted

Example fix

// before: passing an id that points at an api_key row
await refreshStoredManagedMcpOAuthCredential(stored, { brokerUrl, credentialId: 42 });
// after: resolve the id through the broker's typed lookup first
const entry = await authStorage.getCredentialById(42);
if (entry?.credential.type !== "oauth") throw new Error("id 42 is not an OAuth credential");
await refreshStoredManagedMcpOAuthCredential(stored, { brokerUrl, credentialId: 42 });
Defensive patterns

Strategy: type-guard

Validate before calling

const entry = await authStorage.getCredentialById(credentialId);
if (entry?.credential.type !== 'oauth') {
  throw new Error(`credential ${credentialId} is not OAuth; fix the id mapping before refresh`);
}

Type guard

function isOAuthCredential(c) {
  return typeof c === 'object' && c !== null && c.type === 'oauth' &&
         typeof c.access === 'string';
}

Try / catch

try {
  creds = await refreshStoredManagedMcpOAuthCredential(stored, { brokerUrl, credentialId });
} catch (e) {
  if (e.message.includes('non-OAuth credential')) {
    // re-register/re-authenticate so the vault row has type 'oauth'
    await reauthenticateProvider(provider);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling refreshStoredManagedMcpOAuthCredential for a broker-managed credential id that resolves in the vault to a non-OAuth credential type, causing forceRefreshCredentialById to return entry.credential.type !== "oauth".

Common situations: Credential-id mixups where an mcp_oauth: id actually points at an api_key row; vault state desync after re-registering a provider under the same id; a broker configured against a changed or migrated credential store.

Related errors


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