{"record":{"id":"3138f772cb1efc6b","repo":"can1357/oh-my-pi","slug":"broker-returned-non-oauth-credential-for-provide","errorCode":null,"errorMessage":"Broker returned non-OAuth credential for ${provider}","messagePattern":"Broker returned non-OAuth credential for (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/mcp/oauth-credentials.ts","lineNumber":131,"sourceCode":"\tconst authorizationUrl = material && \"authorizationUrl\" in material ? material.authorizationUrl : undefined;\n\tconst resourceIsFallback = !material?.resource && Boolean(opts.serverUrl);\n\tconst resource = material?.resource ?? (resourceIsFallback ? opts.serverUrl : undefined);\n\treturn refreshMCPOAuthToken(tokenUrl, credential.refresh, material?.clientId, material?.clientSecret, resource, {\n\t\tauthorizationUrl,\n\t\tstripSameOriginResource: resourceIsFallback,\n\t\tsignal: opts.signal,\n\t});\n}\n\nasync function refreshBrokeredMcpOAuthCredential(\n\tauthStorage: AuthStorage,\n\tcredentialId: number,\n\tprovider: string,\n\tsignal?: AbortSignal,\n): Promise<OAuthCredentials> {\n\tconst entry = await authStorage.forceRefreshCredentialById(credentialId, signal);\n\tif (entry.credential.type !== \"oauth\") {\n\t\tthrow new Error(`Broker returned non-OAuth credential for ${provider}`);\n\t}\n\tconst refreshed = entry.credential;\n\treturn {\n\t\taccess: refreshed.access,\n\t\trefresh: REMOTE_REFRESH_SENTINEL,\n\t\texpires: refreshed.expires,\n\t\taccountId: refreshed.accountId,\n\t\temail: refreshed.email,\n\t\tprojectId: refreshed.projectId,\n\t\tenterpriseUrl: refreshed.enterpriseUrl,\n\t};\n}\n\n/**\n * Resolve and refresh one stored MCP OAuth row through the durable credential owner.\n *\n * Local rows use their embedded OAuth metadata; broker-redacted rows delegate the\n * grant to the broker. The MCP manager and standalone credential consumers share","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/mcp/oauth-credentials.ts#L113-L149","documentation":"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.","triggerScenarios":"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\".","commonSituations":"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.","solutions":["Verify the credential id maps to an OAuth-type credential in the broker's vault (re-register or fix the id)","Re-authenticate the MCP server so the vault row is recreated with type \"oauth\"","Check for vault migration or corruption that relabeled the credential type and restore from a known-good state","Update the caller's credential-id resolution (e.g. mcpOAuthServerUrlFromCredentialId mapping) if ids shifted"],"exampleFix":"// before: passing an id that points at an api_key row\nawait refreshStoredManagedMcpOAuthCredential(stored, { brokerUrl, credentialId: 42 });\n// after: resolve the id through the broker's typed lookup first\nconst entry = await authStorage.getCredentialById(42);\nif (entry?.credential.type !== \"oauth\") throw new Error(\"id 42 is not an OAuth credential\");\nawait refreshStoredManagedMcpOAuthCredential(stored, { brokerUrl, credentialId: 42 });","handlingStrategy":"type-guard","validationCode":"const entry = await authStorage.getCredentialById(credentialId);\nif (entry?.credential.type !== 'oauth') {\n  throw new Error(`credential ${credentialId} is not OAuth; fix the id mapping before refresh`);\n}","typeGuard":"function isOAuthCredential(c) {\n  return typeof c === 'object' && c !== null && c.type === 'oauth' &&\n         typeof c.access === 'string';\n}","tryCatchPattern":"try {\n  creds = await refreshStoredManagedMcpOAuthCredential(stored, { brokerUrl, credentialId });\n} catch (e) {\n  if (e.message.includes('non-OAuth credential')) {\n    // re-register/re-authenticate so the vault row has type 'oauth'\n    await reauthenticateProvider(provider);\n  } else throw e;\n}","preventionTips":["Keep a single source of truth mapping MCP server names to credential ids","After vault migrations or re-registrations, verify credential types before refresh","Log credential type at registration time to catch mismatches early"],"tags":["oauth","mcp","broker","type-mismatch"],"backgroundTag":"oauth-credential-type-mismatch","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}