Significant-Gravitas/AutoGPT · warning · Error

PKCE verifier not found in session

Error message

PKCE verifier not found in session

What it means

Raised by approve_transfer when the caller's active org is the source org and sourceApprovedByUserId is already set. Each side of a transfer may approve exactly once; a second approval attempt from the source side is rejected. Guards against double-approval by different users of the same org.

Source

Thrown at autogpt_platform/backend/backend/cli/oauth_tool.py:683

                providers: providersBase64,
                redirect_uri: config.redirectUri,
                state: currentState
            }});

            const wizardUrl = `${{config.platformUrl}}/auth/integrations/setup-wizard?${{params}}`;
            log(`Redirecting to: ${{wizardUrl}}`);

            sessionStorage.setItem('wizard_state', currentState);

            window.location.href = wizardUrl;
        }}

        async function exchangeCodeForTokens(code) {{
            log('Exchanging authorization code for tokens...');

            const verifier = sessionStorage.getItem('oauth_pkce_verifier');
            if (!verifier) {{
                throw new Error('PKCE verifier not found in session');
            }}

            // Use local proxy to avoid CORS issues
            // The proxy forwards the request to the backend
            const response = await fetch('/proxy/token', {{
                method: 'POST',
                headers: {{ 'Content-Type': 'application/json' }},
                body: JSON.stringify({{
                    grant_type: 'authorization_code',
                    code: code,
                    redirect_uri: config.redirectUri,
                    client_id: config.clientId,
                    client_secret: config.clientSecret,
                    code_verifier: verifier
                }})
            }});

            if (!response.ok) {{

View on GitHub (pinned to 9c8bb5550f)

Solutions

  1. Check the transfer's sourceApprovedByUserId / targetApprovedByUserId fields in the UI and hide the approve action for a side that has already approved.
  2. Catch this ValueError in the client and treat it as success-equivalent (the org's approval is recorded) after re-fetching the transfer.
  3. Add idempotency: if sourceApprovedByUserId == user_id, return the current row instead of raising.

Example fix

# before
await approve_transfer(tid, user_id, org_id)

# after
tr = await get_transfer(tid)
if tr.source_approved_by_user_id is None or tr.target_approved_by_user_id is None:
    await approve_transfer(tid, user_id, org_id)
Defensive patterns

Strategy: validation

Validate before calling

tr = await get_transfer(transfer_id)
if org_id == tr.source_org_id and tr.source_approved_by_user_id is not None:
    return  # source already approved; nothing to do

Type guard

def source_already_approved(tr: TransferResponse, org_id: str) -> bool:
    return org_id == tr.source_org_id and tr.source_approved_by_user_id is not None

Try / catch

try:
    await approve_transfer(tid, user_id, org_id)
except ValueError as e:
    if "already approved" in str(e):
        pass  # idempotent: approval recorded
    else:
        raise

Prevention

When it happens

Trigger: User A in the source org approves (sets sourceApprovedByUserId), then User B (or User A again) in the same org calls approve on the same transfer. Any POST approve from the source org after sourceApprovedByUserId is non-null.

Common situations: Two admins in the same org both clicking approve; a retry of a timed-out approve request that actually succeeded; frontend not reflecting that the current org already approved.

Related errors


AI-assisted analysis of Significant-Gravitas/AutoGPT@9c8bb5550f (2026-08-14). Data as JSON: /api/errors/d1bf52a06e1660ef. Report an issue: GitHub.