{"record":{"id":"4ba6e112b5fe364e","repo":"Significant-Gravitas/AutoGPT","slug":"managed-credentials-cannot-be-upgraded","errorCode":null,"errorMessage":"Managed credentials cannot be upgraded","messagePattern":"Managed credentials cannot be upgraded","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"autogpt_platform/backend/backend/api/features/integrations/router.py","lineNumber":990,"sourceCode":"\n    existing = await creds_manager.store.get_creds_by_id(user_id, credential_id)\n    if not existing:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=\"Credential to upgrade not found\",\n        )\n    if not isinstance(existing, OAuth2Credentials):\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST,\n            detail=\"Only OAuth2 credentials can be upgraded\",\n        )\n    if not provider_matches(existing.provider, provider.value):\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST,\n            detail=\"Credential provider does not match the requested provider\",\n        )\n    if existing.is_managed:\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST,\n            detail=\"Managed credentials cannot be upgraded\",\n        )\n\n    # Google handles scope merging via include_granted_scopes; others need\n    # the union of existing + new scopes in the login URL.\n    if provider != ProviderName.GOOGLE:\n        requested_scopes = list(set(requested_scopes) | set(existing.scopes))\n\n    return requested_scopes\n\n\nasync def _merge_or_create_credential(\n    user_id: str,\n    provider: ProviderName,\n    credentials: OAuth2Credentials,\n    credential_id: str | None,\n) -> OAuth2Credentials:","sourceCodeStart":972,"sourceCodeEnd":1008,"githubUrl":"https://github.com/Significant-Gravitas/AutoGPT/blob/9c8bb5550f446ba5d3046b78896578742495b3cf/autogpt_platform/backend/backend/api/features/integrations/router.py#L972-L1008","documentation":"In _prepare_scope_upgrade, if the existing OAuth2 credential has is_managed=True (provisioned and governed by an external identity/secret manager rather than the user), HTTP 400 'Managed credentials cannot be upgraded' is raised. Managed credentials are lifecycle-controlled by the managing system; user-initiated scope merges could desync the manager's state or grant scopes the manager did not approve.","triggerScenarios":"Initiating scope upgrade on a credential flagged is_managed — e.g. enterprise-deployed credentials distributed to users via a secret manager integration; attempting to upgrade via the login URL with upgrade_credentials_id pointing at a managed credential.","commonSituations":"Enterprise/shared deployments where some connections are provisioned centrally; UI not distinguishing managed vs personal credentials in the upgrade flow.","solutions":["Exclude is_managed credentials from upgrade actions in the UI/API calls.","To get more scopes, create a personal OAuth connection for the provider instead of touching the managed one.","If the managed credential genuinely needs new scopes, update it through the managing system (the secret manager / provisioning pipeline), not the user upgrade API."],"exampleFix":"# before\neligible = [c for c in creds if c.provider == provider]\n\n# after\neligible = [c for c in creds if c.provider == provider and not c.is_managed]","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"def can_upgrade(cred: dict) -> bool:\n    return cred[\"type\"] == \"oauth2\" and not cred.get(\"is_managed\") and not is_system_credential(cred[\"id\"])","tryCatchPattern":null,"preventionTips":["Hide upgrade actions for managed credentials.","Know your deployment: check is_managed when listing credentials."],"tags":["oauth","credentials","http-400","managed-credentials"],"backgroundTag":null,"analyzedSha":"9c8bb5550f446ba5d3046b78896578742495b3cf","analyzedAt":"2026-08-14T17:17:21.957Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}