Significant-Gravitas/AutoGPT · warning · Error

Token exchange failed

Error message

Token exchange failed

What it means

Mirror of the source-side guard: raised by approve_transfer when the caller's active org is the target org and targetApprovedByUserId is already set. The target side may approve only once per transfer.

Source

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

            // 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) {{
                const error = await response.json();
                throw new Error(error.detail || 'Token exchange failed');
            }}

            return response.json();
        }}

        // Handle callback on page load
        window.addEventListener('load', async () => {{
            const params = new URLSearchParams(window.location.search);

            // Check for OAuth callback
            if (params.has('code')) {{
                const code = params.get('code');
                const state = params.get('state');
                const savedState = sessionStorage.getItem('oauth_state');

                log(`Received authorization code: ${{code.substring(0, 20)}}...`);

                if (state !== savedState) {{

View on GitHub (pinned to 9c8bb5550f)

Solutions

  1. Drive the approve button visibility off targetApprovedByUserId (and sourceApprovedByUserId) per side.
  2. Catch the ValueError and re-fetch to confirm the target approval is recorded, then continue the flow.
  3. Return the existing row (idempotent approve) when the same org/user approves twice.

Example fix

# before
await approve_transfer(tid, user_id, org_id)

# after
tr = await get_transfer(tid)
already = (org_id == tr.target_org_id and tr.target_approved_by_user_id) or \
         (org_id == tr.source_org_id and tr.source_approved_by_user_id)
if not already:
    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.target_org_id and tr.target_approved_by_user_id is not None:
    return  # target already approved

Type guard

def target_already_approved(tr: TransferResponse, org_id: str) -> bool:
    return org_id == tr.target_org_id and tr.target_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
    else:
        raise

Prevention

When it happens

Trigger: Any POST approve from a user whose active org equals tr.targetOrganizationId after targetApprovedByUserId was set — e.g. a second target-org admin approving, or a retried request.

Common situations: Multiple target-org admins acting on the same transfer; retry of an approve that already committed; UI showing an approve button for a side that already approved.

Related errors


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