Skip to content

fix(blocks): prevent TypeError when auto_credentials field is empty - #12588

Closed
ntindle wants to merge 2 commits into
devfrom
fix/open-2895-sheets-missing-credentials
Closed

fix(blocks): prevent TypeError when auto_credentials field is empty#12588
ntindle wants to merge 2 commits into
devfrom
fix/open-2895-sheets-missing-credentials

Conversation

@ntindle

@ntindle ntindle commented Mar 26, 2026

Copy link
Copy Markdown
Member

Why / What / How

Why: GoogleSheetsReadBlock crashes with BlockUnknownError wrapping a TypeError: missing 1 required keyword-only argument: 'credentials' when the block is executed without a spreadsheet selected. Instead of a clean user-facing error, users see an opaque internal error.

What: Fix the auto_credentials injection path so blocks using GoogleDriveFileField don't crash when their file input is empty.

How: Two-layer fix:

  1. Systemic (_base.py): In _execute(), inject None for any auto_credentials kwargs not already present in kwargs via setdefault. This prevents TypeError for all blocks using GoogleDriveFileField.
  2. Block-specific (sheets.py): Make GoogleSheetsReadBlock.run() accept credentials: GoogleCredentials | None = None and add a guard that yields a clean error when credentials are missing.

Changes 🏗️

  • backend/blocks/_base.py: Added auto_credentials kwargs defaulting in _execute() before calling self.run()
  • backend/blocks/google/sheets.py: Made GoogleSheetsReadBlock.run() credentials parameter optional with a None guard
  • backend/blocks/google/sheets_test.py: New test verifying clean error instead of TypeError

Checklist 📋

For code changes:

  • I have clearly listed my changes in the PR description
  • I have made a test plan
  • I have tested my changes according to the test plan:
    • New test test_sheets_read_no_spreadsheet_yields_clean_error passes
    • Existing test_available_blocks[GoogleSheetsReadBlock] still passes
    • poetry run format and poetry run lint clean
    • pyright reports 0 errors on changed files

Note

Medium Risk
Touches the core block execution path by injecting default None values for auto-credential kwargs, which could subtly affect any block relying on kwargs presence/absence. Behavior change is guarded and includes a targeted Google Sheets regression test, but warrants review across other auto-credentialed blocks.

Overview
Prevents blocks that use auto-resolved credentials (e.g. via GoogleDriveFileField) from crashing with a TypeError when the related input is empty by defaulting any missing auto-credentials kwargs to None in Block._execute().

Updates GoogleSheetsReadBlock to accept optional credentials and emit a clean user-facing error when credentials are missing, and adds a regression test ensuring executing without a spreadsheet yields BlockExecutionError rather than an unknown/internal error.

Written by Cursor Bugbot for commit 001ef93. This will update automatically on new commits. Configure here.

When a GoogleDriveFileField input (e.g. spreadsheet) is empty/None, the
executor skips credential injection. The block's run() is then called
without the required `credentials` kwarg, causing a TypeError wrapped as
BlockUnknownError.

Fix in two layers:
- _base.py: inject None for missing auto_credentials kwargs in _execute()
  so all GoogleDriveFileField blocks get a clean fallback
- GoogleSheetsReadBlock: accept optional credentials and guard against None

Fixes OPEN-2895

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ntindle
ntindle requested a review from a team as a code owner March 26, 2026 22:46
@ntindle
ntindle requested review from Pwuts and majdyz and removed request for a team March 26, 2026 22:46
@github-project-automation github-project-automation Bot moved this to 🆕 Needs initial review in AutoGPT development kanban Mar 26, 2026
@coderabbitai

coderabbitai Bot commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

These changes add defensive handling for missing credentials in the block execution framework. The base Block class now ensures auto-credential kwargs are always present (defaulting to None), while GoogleSheetsReadBlock explicitly handles the case when credentials are not provided, yielding a clean error message rather than failing with a TypeError.

Changes

Cohort / File(s) Summary
Base Block Credential Handling
autogpt_platform/backend/backend/blocks/_base.py
Iterates over auto-credential fields from the input schema and ensures corresponding kwargs exist with setdefault(kwarg_name, None), preventing TypeError when credentials are not resolved upstream.
Google Sheets Block & Tests
autogpt_platform/backend/backend/blocks/google/sheets.py, autogpt_platform/backend/backend/blocks/google/sheets_test.py
Made credentials parameter optional with guard clause that yields clean error when missing; added test verifying error is raised before other validation failures occur.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A framework now guards with defensive care,
When credentials vanish into thin air,
No more TypeError shall take the throne—
Just gentle None defaults, gracefully shown,
And Sheets will speak with messages so clear,
A rabbit's fix to make all errors disappear! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: preventing TypeError when auto_credentials field is empty, which directly addresses the core systemic fix applied in _base.py.
Description check ✅ Passed The PR description clearly explains the problem, the two-part solution approach, and lists all files changed with specific details about what was modified in each.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/open-2895-sheets-missing-credentials

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.


with pytest.raises(BlockExecutionError, match="No spreadsheet selected"):
async for _ in block.execute(input_data):
pass

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 🟡 Nice to Have: This test covers the spreadsheet=None path (line 331), but the new credentials=None guard (lines 337-338) is untested. Consider adding a second test case that provides a valid spreadsheet dict but no credentials to exercise that path too:

async def test_sheets_read_no_credentials_yields_clean_error():
    block = GoogleSheetsReadBlock()
    input_data = {
        "spreadsheet": {
            "id": "abc123",
            "name": "Test",
            "mimeType": "application/vnd.google-apps.spreadsheet",
        },
        "range": "Sheet1!A1:B2",
    }
    with pytest.raises(BlockExecutionError, match="Google credentials are required"):
        async for _ in block.execute(input_data):
            pass

@majdyz majdyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, well-structured fix. The two-layer approach (systemic _base.py safety net + block-level guard) is the right call.

What I checked:

  • _base.py: setdefault correctly injects None only for missing auto_credentials kwargs without overwriting already-resolved ones. Placement after input validation is correct.
  • sheets.py: Optional credentials param + early guard yields clean user-facing error.
  • Test validates the TypeError→BlockExecutionError improvement.
  • Verified all other Google Docs/Sheets blocks have early if not input_data.document/spreadsheet guards, so the systemic None injection won't cause regressions in the common empty-field case.

One nice-to-have inline comment about test coverage for the credentials=None path — not blocking.

@github-project-automation github-project-automation Bot moved this from 🆕 Needs initial review to 👍🏼 Mergeable in AutoGPT development kanban Mar 27, 2026
@majdyz

majdyz commented Mar 29, 2026

Copy link
Copy Markdown
Contributor

Review: PR #12588

Issue Found: Incomplete credentials guard for sibling Sheets blocks

The _base.py fix (kwargs.setdefault(kwarg_name, None)) correctly ensures blocks don't crash with a TypeError when credentials are missing. However, only GoogleSheetsReadBlock.run() was updated to accept credentials: GoogleCredentials | None = None and yield a clean error.

There are 19 other Google Sheets blocks (Write, Update, Delete, etc.) that still have:

async def run(self, input_data: Input, *, credentials: GoogleCredentials, **kwargs)

After this PR, those blocks will receive credentials=None from the base class fix, but since their signatures still require non-None GoogleCredentials, they'll crash with a different error when trying to use the None credentials object (e.g., calling _build_sheets_service(None)).

Recommendation: Either:

  1. Apply the same | None = None + early guard pattern to all Google Sheets blocks, or
  2. Make the base class fix more defensive: instead of setdefault(kwarg_name, None), check if the field is actually populated, and skip the block with a clean BlockExecutionError("Missing credentials for {field_name}") directly in _execute.

Option 2 is better because it provides a single centralized guard rather than requiring every block author to remember the pattern.

CI Note

The test failures (3.11, 3.12, 3.13) were actually CANCELLED due to the "Check PR Status" meta-check timing out -- not real test failures. The tests that did run all passed. A re-run should fix this.

# kwarg don't crash with TypeError when the execution context didn't
# resolve credentials (e.g. the GoogleDriveFile field was empty).
for kwarg_name in self.input_schema.get_auto_credentials_fields():
kwargs.setdefault(kwarg_name, None)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 🟠 Should Fix: The systemic setdefault(kwarg_name, None) injects None into the run() kwargs for all blocks using GoogleDriveFileField — there are 30+ such blocks in sheets.py and 18+ in docs.py. However, only GoogleSheetsReadBlock.run() was updated to accept credentials: GoogleCredentials | None = None with a guard.

For the remaining blocks, this means:

  1. If spreadsheet/document field is empty, the early if not input_data.spreadsheet guard fires first, so credentials=None is harmless — this is fine.
  2. But if a block receives a valid spreadsheet/document AND credentials=None (which could happen if credential resolution silently fails or is skipped), the block will call _build_sheets_service(None) / _build_drive_service(None)AttributeError on None → BlockUnknownError. This is the exact same class of opaque error this PR aims to fix.

Since the _base.py change is systemic and touches all auto_credentials blocks, the type safety contract (credentials: GoogleCredentials — required, non-None) is now silently violated for all of them. The systemic fix and the block-level fix are at odds: the systemic fix guarantees None can be injected, but only one block has been updated to handle it.

Suggestion: Either:

  • (A) Remove the systemic _base.py change entirely and instead guard in the executor/manager (where credentials are resolved) — so None never reaches run() in the first place, OR
  • (B) Keep the systemic fix but add a second systemic guard: if any auto_credentials kwarg is still None after resolution, yield a clean error from _execute() before calling self.run(). This way individual blocks don't need to change at all.

Option (B) keeps this simple and avoids touching 50+ blocks:

for kwarg_name in self.input_schema.get_auto_credentials_fields():
    kwargs.setdefault(kwarg_name, None)
    if kwargs[kwarg_name] is None:
        yield "error", f"Credentials required but not provided for '{kwarg_name}'"
        return

credentials: GoogleCredentials | None = None,
**kwargs,
) -> BlockOutput:
if not input_data.spreadsheet:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 🟠 Should Fix: This block-level fix (making credentials optional + adding a guard) is correct in isolation, but it is inconsistent: there are 30+ other blocks in this file and 18+ in docs.py that have the exact same credentials: GoogleCredentials required kwarg pattern with GoogleDriveFileField.

After the _base.py systemic change, all of those blocks can now receive credentials=None. Only this one was updated to handle it. If the systemic fix is kept, either all blocks should be updated, or (better) the guard should be moved to _execute() in _base.py so no individual block needs to change.

As-is, this creates a maintenance trap: contributors will look at GoogleSheetsReadBlock as a pattern, see credentials: GoogleCredentials | None = None, but won't know they need to replicate this in every other block.


with pytest.raises(BlockExecutionError, match="No spreadsheet selected"):
async for _ in block.execute(input_data):
pass

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 🟡 Nice to Have: The test uses block.execute() which goes through _execute()run(). This is good because it exercises the full path including the new _base.py setdefault logic.

However, the test asserts BlockExecutionError with match="No spreadsheet selected" — this only exercises the pre-existing if not input_data.spreadsheet guard (line 331), NOT the new if not credentials guard (lines 337-338) added by this PR.

The new code path this PR adds (the credentials=None guard) is completely untested. Please add a test that provides a valid spreadsheet but no credentials to verify the new guard works:

async def test_sheets_read_no_credentials_yields_clean_error():
    block = GoogleSheetsReadBlock()
    input_data = {
        "spreadsheet": {
            "id": "abc123",
            "name": "Test",
            "mimeType": "application/vnd.google-apps.spreadsheet",
        },
        "range": "Sheet1!A1:B2",
    }
    with pytest.raises(BlockExecutionError, match="Google credentials are required"):
        async for _ in block.execute(input_data):
            pass

(I see @majdyz already noted this — adding emphasis that this is the only test for the new code and it doesn't actually test it.)

@majdyz majdyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

PR description quality: Excellent — clear Why/What/How structure, risk assessment, and checklist. Well done.

Correctness: The fix addresses the reported bug (TypeError when GoogleDriveFile field is empty), but introduces a new inconsistency.

Key findings:

🟠 Systemic fix vs. block-level fix mismatch (Should Fix)

The _base.py change injects credentials=None for all blocks using GoogleDriveFileField (50+ blocks across sheets.py and docs.py). But only GoogleSheetsReadBlock was updated to handle None credentials. This creates two problems:

  1. Type safety violation: All other blocks declare credentials: GoogleCredentials (required, non-None) but can now silently receive None at runtime. Pyright won't catch this because the injection happens via **kwargs.
  2. Latent bug path: If a block receives a valid spreadsheet/document AND None credentials (edge case, but possible), _build_sheets_service(None) will throw an opaque AttributeError — the exact class of error this PR aims to fix.

Recommended fix: Move the None guard into _execute() in _base.py so it's truly systemic. After kwargs.setdefault(kwarg_name, None), check if kwargs[kwarg_name] is None and yield a clean error. This way no individual block needs to change, and the type contract credentials: GoogleCredentials remains valid for all 50+ blocks.

🟡 Test doesn't exercise new code path (Nice to Have)

The single test only hits the pre-existing "No spreadsheet selected" guard. The new credentials=None guard added by this PR is untested. (If the systemic fix in _base.py is updated per recommendation, the block-level guard in sheets.py can be removed entirely, and the test should instead validate the systemic error message.)

Request

Please address the systemic inconsistency before merging. The two concrete options are in my inline comment on _base.py. After addressing, please show test output proving both paths (no spreadsheet, no credentials) yield clean errors.

@github-project-automation github-project-automation Bot moved this from 👍🏼 Mergeable to 🚧 Needs work in AutoGPT development kanban Mar 31, 2026
@majdyz

majdyz commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

@ntindle friendly ping -- the changes-requested items from my review on Mar 31 are still outstanding, and there have been no new commits since the merge from dev on Mar 27.

TL;DR of what needs to change (same as my earlier inline comments):

The core issue is that the _base.py setdefault(kwarg_name, None) fix is systemic (affects all 50+ blocks using GoogleDriveFileField), but only GoogleSheetsReadBlock was updated to handle credentials=None. This means any block that receives a valid file and None credentials will hit an AttributeError instead of a clean error -- the same class of opaque error this PR aims to fix.

Recommended path forward (Option B from my inline comment):

Move the None guard into _execute() in _base.py, right after the setdefault loop. This makes it truly systemic and avoids touching 50+ individual blocks:

for kwarg_name in self.input_schema.get_auto_credentials_fields():
    kwargs.setdefault(kwarg_name, None)
    if kwargs[kwarg_name] is None:
        yield "error", f"Credentials required but not provided for '{kwarg_name}'"
        return

With this change:

  1. The block-level credentials: GoogleCredentials | None = None change in sheets.py can be reverted (keep credentials: GoogleCredentials as required, preserving type safety).
  2. The credentials=None guard in sheets.py can also be removed since _execute() handles it.
  3. All 50+ blocks get the protection automatically.
  4. The test should be updated to validate the systemic error message.

Additionally, please add a test case that exercises the new credentials=None path specifically (valid spreadsheet, no credentials) as noted in my earlier inline comments. The current test only hits the pre-existing "No spreadsheet selected" guard.

Happy to help if you have questions on the approach.

@majdyz

majdyz commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

When the feedback is addressed, please re-request review so I can take another look.

@ntindle

ntindle commented Apr 21, 2026

Copy link
Copy Markdown
Member Author

Superseded by #12874 — the systemic None-guard in Block._execute() per @majdyz's review comment has been applied in the combined PR. Closing in favour of the combined PR.

SymbolStar pushed a commit to SymbolStar/AutoGPT that referenced this pull request Apr 25, 2026
…2269) (Significant-Gravitas#12874)

### 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 issue `AUTOGPT-SERVER-6HB` with
100+ events going back to 2025-10-20 — it was archived as "ignored" but
is a real regression. Bug Significant-Gravitas#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.

- **Closes PR Significant-Gravitas#12004** — Google Drive auto-credentials handling (merged
in)
- **Closes PR Significant-Gravitas#12748** — Incremental OAuth for scope upgrades (merged
in)
- **Closes PR Significant-Gravitas#12588** — superseded by the systemic None-guard here (see
"How" below)
- **Adds Bug 2 fix** — Google credential deletion no longer crashes on
`revoke_tokens`
- **Adds Bug 4 validator** — the agent builder can no longer save a
graph with a hardcoded Drive file ID

**How:**

1. **Adopt PR Significant-Gravitas#12004 (Bug 1 — auto-credentials resolution).** Tags
Drive-file fields as `is_auto_credential` on `CredentialsFieldInfo`,
exposes `BlockSchema.get_auto_credentials_fields()` and
`Graph.regular_credentials_inputs` / `auto_credentials_inputs`, extracts
`_acquire_auto_credentials()` in the executor to resolve embedded
`_credentials_id` at run time, clears `_credentials_id` on 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.

2. **Adopt PR Significant-Gravitas#12748 (Bug 3 — credential accumulation).** OAuth callback
now merges scopes into an existing credential (explicit via
`credential_id` in OAuth state, or implicit via `provider + username`
match) 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.

3. **Replace PR Significant-Gravitas#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 with `GoogleDriveFileField`. Per the
reviewer's ask, we moved the guard into `Block._execute()` once: after
the `setdefault` loop, if `kwargs[kwarg_name] is None` we raise
`BlockExecutionError` with a clean user-facing message. The per-block
change in `sheets.py` is dropped so `credentials: GoogleCredentials`
stays non-`Optional`. Dry-run path skips the guard (executor
intentionally runs blocks without resolved creds for schema validation).

4. **Fix Bug 2 — Google revoke_tokens (SECRT-2267,
AUTOGPT-SERVER-6HB).** `revoke_tokens()` was handing our Pydantic
`OAuth2Credentials` into google-auth's `AuthorizedSession`, which calls
`self.credentials.before_request(...)` on the object and crashes with
`AttributeError: 'OAuth2Credentials' object has no attribute
'before_request'`. Google's token revoke endpoint doesn't need any auth
header — just `token=<token>` in the form body per [Google's
docs](https://developers.google.com/identity/protocols/oauth2/web-server#tokenrevoke).
Switched to the platform's async `Requests` helper, matching how
`reddit.py` / `github.py` / `todoist.py` / other providers do
revocation. No google-auth objects involved.

5. **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…"}`) into
`GoogleSheetsReadBlock.constantInput.spreadsheet`, never wiring an
`AgentGoogleDriveFileInputBlock` as the intended input. Bare-string
versions failed pydantic validation with `is not of type 'object'`;
object-with-only-`id` versions would have crashed at run time because
`_acquire_auto_credentials` has no `_credentials_id` to resolve. Added a
validator in `GraphModel._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"` it names
`AgentGoogleDriveFileInputBlock` specifically; 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` — merged `is_auto_credential` +
`input_field_name` (Significant-Gravitas#12004) with `OAuthState.credential_id` (Significant-Gravitas#12748);
kept HEAD's defensive `set()` copy on `discriminator_values`.
- `backend/blocks/_base.py` — `_execute()` runs the auto-credentials
setdefault loop + raises `BlockExecutionError` when a resolved value is
`None`.
- `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_tokens` swapped to
`Requests().post`, removed `AuthorizedSession` misuse.
- `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 Significant-Gravitas#12748.
- `backend/api/features/integrations/router.py` — incremental-OAuth
callback + scope upgrade helpers (from Significant-Gravitas#12748).
- `backend/api/features/integrations/incremental_oauth_test.py` — 15
tests (from Significant-Gravitas#12748).
- `backend/api/features/chat/tools/utils.py` → renamed to
`backend/copilot/tools/utils.py` during merge; now uses
`regular_credentials_inputs` for missing-creds + matching (from Significant-Gravitas#12004).
- `backend/copilot/tools/utils_test.py` — moved from
`api/features/chat/tools/`, import paths updated.
- `backend/api/features/library/db.py` — library preset guard uses
`regular_credentials_inputs` (from Significant-Gravitas#12004).
- `backend/data/graph.py` — `regular_credentials_inputs` /
`auto_credentials_inputs` properties + `_reassign_ids` clears
`_credentials_id` on fork (from Significant-Gravitas#12004).
- `backend/executor/manager.py` — `_acquire_auto_credentials()`
extracted + validation (from Significant-Gravitas#12004).
- `backend/executor/utils.py`, `utils_test.py`,
`manager_auto_credentials_test.py` — auto-credentials tests (from
Significant-Gravitas#12004).

**Frontend**

- `frontend/src/components/contextual/GoogleDrivePicker/helpers.ts` —
Firefox referrer fix (from Significant-Gravitas#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
Significant-Gravitas#12748).

**Shared libs**

- `autogpt_libs/supabase_integration_credentials_store/types.py` —
merged additions from both Significant-Gravitas#12004 and Significant-Gravitas#12748.

### Test plan 📋

- [x] `poetry run lint` — clean
- [x] `poetry run pytest backend/data/graph_test.py` — 55 passed
including 11 new validator tests
- [x] `poetry run pytest backend/integrations/oauth/google_test.py` — 3
new tests passing
- [x] `poetry run pytest backend/blocks/google/sheets_test.py` — 2 new
tests passing
- [x] `poetry 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 on `origin/dev`
- [x] `pnpm format` — clean
- [x] `pnpm lint` — 3 pre-existing `<img>` warnings on files I didn't
touch, no errors
- [x] `pnpm types` — pre-existing errors on `AgentActivityDropdown` that
also fail on `origin/dev` (unrelated to this PR; needs a separate fix on
dev)
- [x] Live repro on dev verified Bug 2 fires against current prod code —
two fresh Sentry events in `AUTOGPT-SERVER-6HB` at 2026-04-21T21:35:54Z
on `app:dev-behave:cloud` matching the exact `DELETE
/api/integrations/google/credentials/{cred_id}` path. Airtable OAuth2
delete as a control worked cleanly, confirming Google-specific.
- [x] Live repro on dev verified Bug 4 (CoPilot direct-run variant) —
`{"spreadsheet": {"id": "..."}}` → `Cannot use file 'None' (type: None)`
from `_validate_spreadsheet_file` mimeType check, as expected.

Reviewer post-merge verification:
- [ ] Delete a Google OAuth credential via the Integrations UI —
succeeds cleanly, no Sentry event fires
- [ ] Connect Google twice (same account, same scopes) — credential
count stays at 1 (dedup)
- [ ] Save an agent graph with
`GoogleSheetsReadBlock.constantInput.spreadsheet = "bare-id"` via API —
graph validator rejects with `AgentGoogleDriveFileInputBlock`
remediation
- [ ] Save an agent graph with `GoogleSheetsReadBlock` whose
`spreadsheet` is fed by an upstream
`AgentGoogleDriveFileInputBlock.result` — validator accepts, agent runs

### Out of scope (for follow-ups)

- **Bug 1 — "Failed to retrieve Google OAuth credentials"** in
`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.
- **CoPilot first-attempt mimeType retry loop.** Observed on dev:
CoPilot's first call to `GoogleSheetsReadBlock` sends `{"spreadsheet":
{"id": "..."}}` without `mimeType`, hits `_validate_spreadsheet_file`,
retries with mimeType. Costs a round-trip. Two possible fixes (relax
`_validate_spreadsheet_file` to skip when mimeType is `None` and let
Google's API surface the real error; OR extend
`get_auto_credentials_fields` metadata 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.
- **CoPilot agent-builder prompt/guide update.** The validator here
produces the correct error message, but the agent-builder model still
needs to learn to use `AgentGoogleDriveFileInputBlock` upfront rather
than discover it through validator retries. Separate handoff ticket
filed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!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}/login` now accepts
`credential_id` and persists it in `OAuthState` to upgrade an existing
OAuth2 credential on callback (explicit upgrade), with an implicit merge
path for same `provider+username`. The callback path now merges
scopes/metadata, preserves ID/title, preserves existing
`refresh_token`/`username` when missing from incremental responses,
blocks upgrades for managed/system credentials, and adds a **new
`/{provider}/credentials/{cred_id}/picker-token` endpoint** to return a
short-lived access token for provider-hosted pickers (currently
allowlisted to Google Drive scopes).
> 
> For auto-credentials, `CredentialsFieldInfo` gains
`is_auto_credential` + `input_field_name`, graphs now expose
`regular_credentials_inputs` vs `auto_credentials_inputs`, and multiple
callers switch from `aggregate_credentials_inputs()` to
`regular_credentials_inputs` so 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 clean `Missing credentials` error when auto-credentials are
absent.
> 
> Separately fixes Google credential deletion by rewriting
`GoogleOAuthHandler.revoke_tokens()` to use the platform `Requests`
helper (bounded retries) instead of `AuthorizedSession`, and expands
test coverage across these flows (incremental OAuth, picker-token,
auto-credential validation/acquisition, graph validator, and frontend
diagnostics test stubs).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
cac36ea. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

2 participants