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
- Drive the approve button visibility off targetApprovedByUserId (and sourceApprovedByUserId) per side.
- Catch the ValueError and re-fetch to confirm the target approval is recorded, then continue the flow.
- 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
- Reflect both approval fields in the UI per side
- Do not retry approve without re-fetching the transfer
- Watch the status field: single-sided approvals move it to *_APPROVED
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
- PKCE verifier not found in session
- Failed to reset usage
- Failed to update tier
- Failed to start OAuth flow
- Login failed
AI-assisted analysis of Significant-Gravitas/AutoGPT@9c8bb5550f (2026-08-14).
Data as JSON: /api/errors/db541399a9a72f60.
Report an issue: GitHub.