fix(platform): resolve autopilot beta blockers (SECRT-2266/2267/2268/2269) - #12874
Merged
Conversation
…platform - Tag auto-credentials with `is_auto_credential` and `input_field_name` on `CredentialsFieldInfo` to distinguish them from regular user-provided credentials - Add `regular_credentials_inputs` and `auto_credentials_inputs` properties to `Graph` so UI schemas, CoPilot, and library presets only surface regular credentials - Extract `_acquire_auto_credentials()` helper in executor to resolve embedded `_credentials_id` at execution time with proper lock management - Validate auto-credentials ownership in `_validate_node_input_credentials()` to catch stale/missing credentials before execution - Clear `_credentials_id` in `_reassign_ids()` on graph fork so cloned agents require re-authentication - Propagate `is_auto_credential` through `combine()` and `discriminate()` on `CredentialsFieldInfo` - Add `referrerPolicy: "no-referrer-when-downgrade"` to Google API script loading to fix Firefox API key validation - Comprehensive test coverage for all new behavior Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adapt auto-credentials filtering to dev's refactored graph model: - aggregate_credentials_inputs() now returns 3-tuples (field_info, node_pairs, is_required) - credentials_input_schema moved to GraphModel, builds JSON schema directly - Update regular/auto_credentials_inputs properties for 3-tuple format - Update test mocks and assertions for new tuple format and class hierarchy Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wrap get_creds_by_id call in try/except in the auto-credentials validation path to match the error handling pattern used for regular credentials. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…o None Setting _credentials_id to None on fork was ambiguous — both "forked, needs re-auth" and "chained data from upstream" were represented as None. This caused _acquire_auto_credentials to silently skip credential acquisition for forked agents, leading to confusing TypeErrors at runtime. Now the key is deleted entirely, making the three states unambiguous: - Present with value: user-selected credentials - Present as None: chained data from upstream block - Absent: forked/needs re-authentication Also adds pre-run validation for the missing key case and makes error messages provider-agnostic. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move the _reassign_ids section comment to above the actual _reassign_ids tests, and label the combine() tests correctly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…schema Post-refactor fix: these fields were moved from data/block.py to blocks/_base.py in #12068
Resolved conflicts: - executor/utils.py: kept auto-credentials validation + dev's comment update - graph_test.py: kept both auto-credentials tests AND MCP deduplication tests
Allow users to upgrade existing OAuth credentials with additional scopes instead of creating duplicate credentials. Google uses native incremental auth (include_granted_scopes), GitHub uses scope union in login URL. Backend: OAuthState gains credential_id field, login endpoint accepts credential_id param, callback merges scopes into existing credentials or auto-detects same provider+username for implicit merge. Frontend: API client passes credential_id, credentials provider upserts on callback, useCredentials splits into saved vs upgradeable lists, useCredentialsInput exposes handleScopeUpgrade. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…into feat/incremental-oauth
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use provider_matches() for Python 3.13 StrEnum compat (sentry, coderabbitai) - Filter out managed/system credentials from implicit merge (cursor) - Skip implicit merge when credentials.username is None (sentry) - Preserve existing metadata on credential upgrade (cursor) - Fix test factories to use `is not None` instead of `or` (coderabbitai) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…into feat/incremental-oauth
…e metadata - Reject managed/system credentials in both _prepare_scope_upgrade and _upgrade_existing_credential with 400 (coderabbitai, sentry) - Guard metadata merge against None values (coderabbitai) - Fix test helper _make_state_with_credential_id scopes pattern (cursor) - Add 4 new tests confirming each fix Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pulls origin/ntindle/google-issues-fix into the combined beta-blocker branch. Covers SECRT-2266 (Google Drive OAuth retrieval failure): tags auto-credentials in the data model, resolves embedded _credentials_id at execution time, clears _credentials_id on agent fork, hardens the Firefox referrer on the Google Drive picker, and filters auto-creds out of the CoPilot "missing credentials" and library preset prompts. Conflicts resolved: - backend/data/model.py: kept HEAD's defensive set() copy AND added #12004's is_auto_credential + input_field_name on CredentialsFieldInfo - backend/executor/utils_test.py: kept both test groups (HEAD's credential error marker parity + #12004's auto-credentials validation) - backend/copilot/tools/utils_test.py: file was added by #12004 at the old path backend/api/features/chat/tools/utils_test.py; git rename detection moved it to the new copilot/ location. Updated the in-file import strings from backend.api.features.chat.tools.utils to backend.copilot.tools.utils to match the post-rename module. 66 tests pass in copilot/tools/utils_test.py, executor/utils_test.py, data/graph_test.py. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ndle/diagnose-autopilot-beta-blockers
Replaces PR #12588 with majdyz's recommended approach. Instead of a per-block ``credentials: GoogleCredentials | None = None`` guard (which would have to be repeated across 50+ blocks using ``GoogleDriveFileField``), injects the check into ``Block._execute()`` once: for kwarg_name in self.input_schema.get_auto_credentials_fields(): kwargs.setdefault(kwarg_name, None) if kwargs[kwarg_name] is None: raise BlockExecutionError("Missing credentials for '{name}'. ...") Benefits over per-block guards: - No ``credentials: GoogleCredentials`` → ``GoogleCredentials | None`` churn on individual blocks; type contracts stay non-Optional. - New auto-credential blocks inherit the guard automatically. - Dry-run path skips the guard (executor runs blocks without resolved credentials for schema validation). Tests in backend/blocks/google/sheets_test.py pin the contract: - Valid spreadsheet, no credentials → clean BlockExecutionError - No spreadsheet, no credentials → same clean error (credentials guard fires before the block's own ``No spreadsheet selected`` path) Addresses the credentials-missing half of SECRT-2269. Supersedes #12588. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…auth AuthorizedSession Revoking a Google credential crashed with: AttributeError: 'OAuth2Credentials' object has no attribute 'before_request' at backend/integrations/oauth/google.py:111 in revoke_tokens revoke_tokens() used google-auth's AuthorizedSession with our Pydantic OAuth2Credentials model. AuthorizedSession.request() unconditionally calls self.credentials.before_request(...), which only exists on google-auth's Credentials class. Google's revoke endpoint doesn't need any authentication, just the token in the form body (see https://developers.google.com/identity/ protocols/oauth2/web-server#tokenrevoke). Switched to backend's async Requests helper, matching how the other OAuth providers (reddit, github, etc.) already do revocation. No google-auth objects involved. Regression tests in integrations/oauth/google_test.py cover: - Happy path: POST to revoke endpoint with token in form body - Returns False when access_token is missing (don't crash) - Returns False on non-2xx response Fixes SECRT-2267, AUTOGPT-SERVER-6HB (100 events, status=ignored). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
API callers sending just a spreadsheet ID (e.g. pasted from a Drive
URL) hit a pydantic ValidationError because the block schema expected
a full GoogleDriveFile object. The UI picker always sent the object,
so the UI worked but the API did not — API/UI mismatch.
Added a model_validator(mode="before") that promotes a bare string
input to {id: <str>}. The picker's full-object path continues to work
unchanged (still carries ``_credentials_id``). Non-string / non-dict
values still raise ValidationError so we don't silently swallow real
bad input.
Fixes SECRT-2269 (API side).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e inputs" This reverts commit dfd82b3.
…validator
Evidence from a beta user's Langfuse session 92363101 + the resulting
Supabase rows: across 13 saved versions of one agent, CoPilot's
agent-builder kept stuffing a bare Drive file ID into
GoogleSheetsReadBlock.constantInput.spreadsheet (v4-v10) and later a
partial `{"id": "…"}` object (v11-v13), never wiring an
AgentGoogleDriveFileInputBlock despite finding it via `find_block`
earlier in the session. v4-v10 failed pydantic validation with
"'1KAv…' is not of type 'object'" — which the user pasted back into
chat — and v11-v13 passed pydantic but would have crashed at run time
because `_acquire_auto_credentials` in manager.py has no
`_credentials_id` to resolve.
Adds a defence-in-depth validator in `_validate_graph_get_errors`
that flags any auto-credentials field whose `input_default.<field>`
is a bare string OR a dict missing `_credentials_id`, when there's no
upstream link feeding the field.
Remediation text is format-aware: when `field_schema["format"] ==
"google-drive-picker"` the error points at AgentGoogleDriveFileInputBlock
specifically. For any other auto-credentials format (future OneDrive
/ Dropbox pickers etc.) the remediation is generic ("wire the matching
input block for this provider") so we don't ship stale Google-specific
hints when the actual provider differs.
Companion handoff for the CoPilot agent-builder team is drafted at
/tmp/agent-builder-ticket-drive-file-input.md — the validator is a
safety net, not a substitute for teaching the model the right pattern.
Tests (11 new, all passing):
- bare string real ID (v7-v10 shape) -> rejected with Drive-picker text
- placeholder string (v4-v6 shape) -> rejected
- partial object missing _credentials_id (v11-v13 shape) -> rejected
- empty-string _credentials_id -> rejected (executor treats as missing)
- fully hydrated object with _credentials_id -> accepted
- upstream link from AgentGoogleDriveFileInputBlock -> accepted
- unset field with no link -> no double error with required-field check
- bare string on a non-auto-credentials field -> not flagged
- multi-block anti-pattern -> each bad node flagged independently
- non-google-picker format -> caught but with generic remediation
- regular credentials field (Gmail) -> not flagged
Also absorbs an isort-driven one-line reflow in copilot/tools/
utils_test.py from running poetry run format after the #12004 merge
(Boy Scout).
Addresses SECRT-2269 (platform side).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
poetry run format auto-simplified three assert-with-trailing-tuple expressions in the new auto-credentials validator tests into single- line asserts. No behavioural change; tests still pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ntindle
requested review from
Pwuts and
Swiftyos
and removed request for
a team
April 21, 2026 21:58
…cred error Two cursor review items on commit ad7021f: - Cursor Low (thread PRRT_kwDOJKSTjM58sEDe): the `_mark_optional_skip` closure was being redefined on every iteration of the inner auto-credential field loop. Move it to the outer per-node scope so it's defined once per node. Still captures node.id correctly, no behavior change. - Cursor Medium (thread PRRT_kwDOJKSTjM58sEDl): the `else` branch in `_acquire_auto_credentials` used to raise "No file selected" for ANY truthy non-dict `field_data` (e.g. a bare Drive ID string that bypassed the graph validator — API callers, legacy graphs). The message was misleading because the value *was* provided, it just had the wrong shape. Split into two branches: None/empty → "No file selected" (unchanged); non-dict truthy → a new error that names both the field and the actual type, pointing at the picker as the fix. Parametrized regression test covers bare-string, int, bool, and list inputs — pins the type-name requirement so a future cleanup can't silently regress the message.
Sentry HIGH (thread PRRT_kwDOJKSTjM58sJfA): the None-guard in
``Block._execute()`` used ``field_value.get("_credentials_id")`` and
treated the result's truthiness as "creds available". The documented
chained-upstream pattern ships the picker object with
``_credentials_id=None`` — the executor fills in the resolved id
between prep and run(). But ``None`` is falsy, so the guard raised
``BlockExecutionError("Missing credentials")`` on every valid chained
graph (e.g. AgentGoogleDriveFileInputBlock → GoogleSheetsReadBlock).
Switch to ``"_credentials_id" in field_value`` — the key's presence is
the chained-skip signal (mirrors the rule ``_acquire_auto_credentials``
uses at manager.py:166: ``if cred_id is None: continue``). A dict that
truly lacks the key (hardcoded without creds) still trips the guard.
Pin with a regression test that reproduces Mehmet's v13 graph shape:
spreadsheet dict with ``_credentials_id: None`` must reach run() (we
patch the provider SDK build to detect that boundary) — not be
preempted with "Missing credentials".
This was referenced Apr 22, 2026
Without an `__init__.py`, pytest's default `prepend` import mode adds `backend/blocks/google/` to `sys.path[0]` when it collects a test file under that directory (the new `sheets_test.py` is the trigger). That makes `backend/blocks/google/calendar.py` importable as bare `calendar`, shadowing the stdlib. A `multiprocessing.forkserver` subprocess inherits the polluted `sys.path`; when the forkserver child re-imports pytest via `_fixup_main_from_path`, `email._parseaddr` does `import calendar` and resolves our file instead of the stdlib, which re-enters `email.utils` and crashes with a circular-import `ImportError` on `_has_surrogates`. Every `AppProcess` subprocess (DatabaseManager, ExecutionManager, AgentServer, ...) dies silently; subsequent IPC calls (e.g. `OrchestratorBlock._create_tool_node_signatures`) hang through the full `conn_retry` loop (100 * 30s) and burn the 15-minute CI budget. Adding the empty `__init__.py` promotes `google/` to a regular package; pytest walks up to `autogpt_platform/backend/` for the `sys.path` entry instead, so `calendar.py` is no longer reachable as `calendar` and the subprocess re-imports stdlib cleanly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit ff67797. Configure here.
…or tests The Google Sheets / Gmail blocks auto-disable when `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` are unset, which is the case in CI. The graph validator short-circuits disabled blocks at `graph.py:798` before reaching the auto-credentials anti-pattern branch these tests exercise, so all 14 `test_auto_credentials_*` assertions failed with "Block is disabled and cannot be used in graphs" instead of the expected auto-creds error messages. Add a name-gated autouse fixture that flips `GOOGLE_SHEETS_DISABLED` and `GOOGLE_OAUTH_IS_CONFIGURED` for the auto-creds test group only; other tests in this file retain the real env-derived values. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…oundary Adds five new test files + expands the admin diagnostics page test to cover the rendered-dashboard and error branches. Targets the lowest patch-coverage files in the PR so platform-frontend-ci hits its 80% threshold: - SubscriptionTierSection/helpers.test.ts — formatCost / getTierLabel / formatPendingDate (pure unit tests) - PendingChangeBanner.test.tsx — cancel vs downgrade copy, button disabled while busy, null-date bail-out - PanelHeader.test.tsx — revert-button visibility and aria-label, click callbacks - ArtifactErrorBoundary.test.tsx — children render, fallback with error text, structured clipboard payload on Copy - useDiagnosticsContent.test.ts — loading / error coalescing and refresh fanout across the three composed queries - diagnostics/__tests__/page.test.tsx — extended to cover error state, zeroed happy path, and critical-issues alert-card rendering Also fixes a pre-existing timezone flake in SubscriptionTierSection.test.tsx — the `2026-05-15T00:00:00Z` literal drifts to May 14 in any timezone west of UTC, so the test passes in CI (UTC) but fails locally. Moved to noon UTC to match the pattern used in the other new tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add renderHook tests for React hook wrappers that delegate to already-tested pure functions, pushing codecov/patch/platform-frontend above the 80% threshold: - client.test.ts: BackendAPI.oAuthLogin delegates to _get + buildOAuthLoginQuery - useCredentials.test.ts: upgradeableCredentials exposed via context provider - useCredentialsInput.test.ts: scope upgrade flow, OAuth login delegation - useGoogleDrivePicker.test.ts: openPicker credential flow (insufficient scopes, happy path with picker token, non-oauth2 credential error) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove unused React and vi imports - Add non-null assertions for discriminated union properties in tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Swiftyos
approved these changes
Apr 23, 2026
Ari4ka
approved these changes
Apr 24, 2026
10 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Why / What / How
Why: A beta user spent significant time trying to build and run agents that read Google Sheets. Four separate failures compounded on their session — all already open in Linear as SECRT-2266 through SECRT-2269. Three in-flight PRs each addressed a piece but conflicted on the same files (
backend/data/model.py,backend/blocks/_base.py,autogpt_libs/.../types.py), so landing them individually would have been churn. One of the four reported issues (the credential-delete crash) is also the top unresolved Sentry issueAUTOGPT-SERVER-6HBwith 100+ events going back to 2025-10-20 — it was archived as "ignored" but is a real regression. Bug #4 required new work; the others we got by adopting the existing open PRs and addressing a pending review comment.What: This PR consolidates the three in-flight PRs, adds the two pieces of new work needed to fully close the beta blockers, and addresses the pending review on one of the three PRs so it doesn't require a second round.
revoke_tokensHow:
Adopt PR fix(platform): Fix Google Drive auto-credentials handling across the platform #12004 (Bug 1 — auto-credentials resolution). Tags Drive-file fields as
is_auto_credentialonCredentialsFieldInfo, exposesBlockSchema.get_auto_credentials_fields()andGraph.regular_credentials_inputs/auto_credentials_inputs, extracts_acquire_auto_credentials()in the executor to resolve embedded_credentials_idat run time, clears_credentials_idon agent fork so cloned agents don't inherit the original author's credential, and fixes the Firefox referrer policy on the Google Drive picker script load.Adopt PR feat(platform): add incremental OAuth authorization for scope upgrades #12748 (Bug 3 — credential accumulation). OAuth callback now merges scopes into an existing credential (explicit via
credential_idin OAuth state, or implicit viaprovider + usernamematch) instead of appending a new row on every reconnect. GitHub's non-incremental OAuth path requests the union of existing + new scopes at login so the upgrade path works there too.Replace PR fix(blocks): prevent TypeError when auto_credentials field is empty #12588 with a systemic None-guard (addresses reviewer feedback). The original PR added a per-block
credentials: GoogleCredentials | None = None+ early guard pattern that would need to be repeated across 50+ blocks withGoogleDriveFileField. Per the reviewer's ask, we moved the guard intoBlock._execute()once: after thesetdefaultloop, ifkwargs[kwarg_name] is Nonewe raiseBlockExecutionErrorwith a clean user-facing message. The per-block change insheets.pyis dropped socredentials: GoogleCredentialsstays non-Optional. Dry-run path skips the guard (executor intentionally runs blocks without resolved creds for schema validation).Fix Bug 2 — Google revoke_tokens (SECRT-2267, AUTOGPT-SERVER-6HB).
revoke_tokens()was handing our PydanticOAuth2Credentialsinto google-auth'sAuthorizedSession, which callsself.credentials.before_request(...)on the object and crashes withAttributeError: 'OAuth2Credentials' object has no attribute 'before_request'. Google's token revoke endpoint doesn't need any auth header — justtoken=<token>in the form body per Google's docs. Switched to the platform's asyncRequestshelper, matching howreddit.py/github.py/todoist.py/ other providers do revocation. No google-auth objects involved.Fix Bug 4 — hardcoded Drive file IDs in agent graphs (SECRT-2269). Evidence from the beta user's session: CoPilot's agent-builder produced 13 saved graph versions in one session where each one stuffed either a bare string (
"1KAv…") or a partial object ({"id": "1KAv…"}) intoGoogleSheetsReadBlock.constantInput.spreadsheet, never wiring anAgentGoogleDriveFileInputBlockas the intended input. Bare-string versions failed pydantic validation withis not of type 'object'; object-with-only-idversions would have crashed at run time because_acquire_auto_credentialshas no_credentials_idto resolve. Added a validator inGraphModel._validate_graph_get_errorsthat flags any auto-credentials field whoseinput_default.<field>is a bare string OR a dict missing_credentials_id, when there's no upstream link feeding the field. Remediation text is format-aware: whenfield_schema["format"] == "google-drive-picker"it namesAgentGoogleDriveFileInputBlockspecifically; for any other future auto-credentials format (OneDrive / Dropbox / etc.) the remediation is generic, so we don't ship a stale Google-specific hint that doesn't apply.A companion handoff for the CoPilot agent-builder team is drafted at
/tmp/agent-builder-ticket-drive-file-input.md(to be filed in their tracker). The validator here is a safety net so reviewers and the LLM both get a clear error with the correct remediation; the agent-builder itself still needs to learn the correct pattern so it stops trying to hardcode Drive files in the first place.Changes 🏗️
Backend
backend/data/model.py— mergedis_auto_credential+input_field_name(fix(platform): Fix Google Drive auto-credentials handling across the platform #12004) withOAuthState.credential_id(feat(platform): add incremental OAuth authorization for scope upgrades #12748); kept HEAD's defensiveset()copy ondiscriminator_values.backend/blocks/_base.py—_execute()runs the auto-credentials setdefault loop + raisesBlockExecutionErrorwhen a resolved value isNone.backend/blocks/google/sheets_test.py— 2 new tests (systemic None-guard behaviour).backend/blocks/google/_drive.py,_drive_test.py— unchanged on this branch (earlier bare-string validator was reverted after feedback; see "Out of scope" below).backend/data/graph.py— auto-credentials anti-pattern validator in_validate_graph_get_errors.backend/data/graph_test.py— 11 new tests for the validator.backend/integrations/oauth/google.py—revoke_tokensswapped toRequests().post, removedAuthorizedSessionmisuse.backend/integrations/oauth/google_test.py— 3 new tests covering the revoke happy path, no-access-token, and non-2xx-response.backend/integrations/credentials_store.py— from feat(platform): add incremental OAuth authorization for scope upgrades #12748.backend/api/features/integrations/router.py— incremental-OAuth callback + scope upgrade helpers (from feat(platform): add incremental OAuth authorization for scope upgrades #12748).backend/api/features/integrations/incremental_oauth_test.py— 15 tests (from feat(platform): add incremental OAuth authorization for scope upgrades #12748).backend/api/features/chat/tools/utils.py→ renamed tobackend/copilot/tools/utils.pyduring merge; now usesregular_credentials_inputsfor missing-creds + matching (from fix(platform): Fix Google Drive auto-credentials handling across the platform #12004).backend/copilot/tools/utils_test.py— moved fromapi/features/chat/tools/, import paths updated.backend/api/features/library/db.py— library preset guard usesregular_credentials_inputs(from fix(platform): Fix Google Drive auto-credentials handling across the platform #12004).backend/data/graph.py—regular_credentials_inputs/auto_credentials_inputsproperties +_reassign_idsclears_credentials_idon fork (from fix(platform): Fix Google Drive auto-credentials handling across the platform #12004).backend/executor/manager.py—_acquire_auto_credentials()extracted + validation (from fix(platform): Fix Google Drive auto-credentials handling across the platform #12004).backend/executor/utils.py,utils_test.py,manager_auto_credentials_test.py— auto-credentials tests (from fix(platform): Fix Google Drive auto-credentials handling across the platform #12004).Frontend
frontend/src/components/contextual/GoogleDrivePicker/helpers.ts— Firefox referrer fix (from fix(platform): Fix Google Drive auto-credentials handling across the platform #12004).frontend/src/components/contextual/CredentialsInput/useCredentialsInput.ts,src/hooks/useCredentials.ts,src/lib/autogpt-server-api/client.ts,src/providers/agent-credentials/credentials-provider.tsx,src/app/api/openapi.json— incremental-OAuth scope upgrade UI (from feat(platform): add incremental OAuth authorization for scope upgrades #12748).Shared libs
autogpt_libs/supabase_integration_credentials_store/types.py— merged additions from both fix(platform): Fix Google Drive auto-credentials handling across the platform #12004 and feat(platform): add incremental OAuth authorization for scope upgrades #12748.Test plan 📋
poetry run lint— cleanpoetry run pytest backend/data/graph_test.py— 55 passed including 11 new validator testspoetry run pytest backend/integrations/oauth/google_test.py— 3 new tests passingpoetry run pytest backend/blocks/google/sheets_test.py— 2 new tests passingpoetry run pytest backend/blocks/google/ backend/integrations/oauth/ backend/executor/ backend/data/graph_test.py backend/api/features/integrations/ backend/copilot/tools/utils_test.py— 250 passed, 6 pre-existing failures that require the docker stack (RabbitMQ/Redis/Postgres) and fail identically onorigin/devpnpm format— cleanpnpm lint— 3 pre-existing<img>warnings on files I didn't touch, no errorspnpm types— pre-existing errors onAgentActivityDropdownthat also fail onorigin/dev(unrelated to this PR; needs a separate fix on dev)AUTOGPT-SERVER-6HBat 2026-04-21T21:35:54Z onapp:dev-behave:cloudmatching the exactDELETE /api/integrations/google/credentials/{cred_id}path. Airtable OAuth2 delete as a control worked cleanly, confirming Google-specific.{"spreadsheet": {"id": "..."}}→Cannot use file 'None' (type: None)from_validate_spreadsheet_filemimeType check, as expected.Reviewer post-merge verification:
GoogleSheetsReadBlock.constantInput.spreadsheet = "bare-id"via API — graph validator rejects withAgentGoogleDriveFileInputBlockremediationGoogleSheetsReadBlockwhosespreadsheetis fed by an upstreamAgentGoogleDriveFileInputBlock.result— validator accepts, agent runsOut of scope (for follow-ups)
frontend/src/components/contextual/GoogleDrivePicker/useGoogleDrivePicker.ts:163. Zero hits for this string in the beta user's Langfuse traces and we weren't able to reproduce it from a clean flow. Most likely a stale-credential race condition (delete in another tab, picker queries a stale React-Query cache). Tracked as a separate task; not blocking.GoogleSheetsReadBlocksends{"spreadsheet": {"id": "..."}}withoutmimeType, hits_validate_spreadsheet_file, retries with mimeType. Costs a round-trip. Two possible fixes (relax_validate_spreadsheet_fileto skip when mimeType isNoneand let Google's API surface the real error; OR extendget_auto_credentials_fieldsmetadata so CoPilot's tool description prompts it to always include mimeType). Deliberately deferred — fixing only one of "API caller sends a bare string" or "CoPilot sends an incomplete object" risked the same auth-ambiguity the bare-string commit in this branch history hit.AgentGoogleDriveFileInputBlockupfront rather than discover it through validator retries. Separate handoff ticket filed.🤖 Generated with Claude Code
Note
Medium Risk
Touches OAuth credential issuance/upgrade paths and introduces a new endpoint that returns raw access tokens (scope-gated), plus broad changes to execution-time credential resolution/validation; mistakes could impact auth/security or break integrations.
Overview
Fixes several Google/Drive agent-builder blockers by supporting incremental OAuth scope upgrades and by hardening how credential-bearing file inputs (“auto-credentials”) are validated, resolved, and cleared on graph fork.
On the integrations API,
/{provider}/loginnow acceptscredential_idand persists it inOAuthStateto upgrade an existing OAuth2 credential on callback (explicit upgrade), with an implicit merge path for sameprovider+username. The callback path now merges scopes/metadata, preserves ID/title, preserves existingrefresh_token/usernamewhen missing from incremental responses, blocks upgrades for managed/system credentials, and adds a new/{provider}/credentials/{cred_id}/picker-tokenendpoint to return a short-lived access token for provider-hosted pickers (currently allowlisted to Google Drive scopes).For auto-credentials,
CredentialsFieldInfogainsis_auto_credential+input_field_name, graphs now exposeregular_credentials_inputsvsauto_credentials_inputs, and multiple callers switch fromaggregate_credentials_inputs()toregular_credentials_inputsso embedded picker credentials aren’t treated as user-mapped inputs. Execution-time auto-credential acquisition is extracted into_acquire_auto_credentials()with clearer error handling and lock cleanup; block execution adds a systemic guard to surface a cleanMissing credentialserror when auto-credentials are absent.Separately fixes Google credential deletion by rewriting
GoogleOAuthHandler.revoke_tokens()to use the platformRequestshelper (bounded retries) instead ofAuthorizedSession, and expands test coverage across these flows (incremental OAuth, picker-token, auto-credential validation/acquisition, graph validator, and frontend diagnostics test stubs).Reviewed by Cursor Bugbot for commit cac36ea. Bugbot is set up for automated code reviews on this repo. Configure here.