You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(oauth): add google-antigravity to OAuth 401 replay and force-refresh providers #3575
When Google Antigravity OAuth session tokens expire, desynchronize, or are invalidated upstream by Google, incoming requests to OpenCodeX fail immediately with 401 instead of being refreshed and replayed transparently.
While other OAuth providers (xai, github-copilot, kiro) participate in automatic upstream 401 recovery (isOAuth401ReplayProvider and FORCE_REFRESH_PROVIDERS), google-antigravity is missing from both sets. Consequently, any 401 from upstream terminates the request immediately and leaves the stale token in ~/.opencodex/auth.json until a manual command or request triggers a refresh.
Reproduction
Configure google-antigravity as an OAuth provider in OpenCodeX (ocx login google-antigravity).
Allow the session token to expire, rotate in another environment, or become invalidated upstream.
Send a request to /v1/chat/completions or /v1/responses for model google-antigravity/gemini-3.8-flash.
Upstream Google API returns 401 UNAUTHENTICATED (Request had invalid authentication credentials).
OpenCodeX aborts immediately and forwards the 401 error to the caller, without attempting to refresh the token or replay the request.
Expected behavior: OpenCodeX should catch the upstream 401, execute forceRefreshOAuthAccessSnapshot (refreshing credentials and re-discovering the Cloud Code Assist project), and replay the request to completion.
Version
dev / 2.36.1+
Operating system
macOS 15.5 / Linux / Windows
Provider and model
google-antigravity / gemini-3.8-flash
Logs or error output
[opencodex] HTTP 401 Unauthorized from daily-cloudcode-pa.googleapis.com
authentication_error: Request had invalid authentication credentials.
Confirmed against current dev at 24cc558d5: google-antigravity is absent from both the forced-refresh provider set and the Responses OAuth 401 replay predicate, even though its OAuth implementation already refreshes the token and re-discovers/preserves the Cloud Code Assist project. A locally valid-looking snapshot can therefore receive an upstream 401 and terminate without the existing one-refresh/one-replay recovery used by xAI, Copilot, and Kiro.
The linked #3576 is aimed at the correct boundaries: add the exact built-in provider to both gates and replay with the refreshed token/project snapshot while preserving one-shot retry, singleflight, and sanitized errors. I am keeping the issue open until that security-sensitive PR is rebased onto the latest dev, reviewed at the new exact head, and merged.
이 이슈는 Google Antigravity OAuth 세션이 만료·무효화·다른 환경에서 회전됐을 때, 업스트림이 401을 주면 OpenCodex가 토큰을 강제 갱신하고 같은 요청을 한 번 더 보내지 않은 채 바로 401을 호출자에게 넘긴다는 버그다. 제보 재현은 ocx login google-antigravity 뒤 세션이 죽은 상태에서 /v1/chat/completions 또는 /v1/responses로 google-antigravity/gemini-3.8-flash를 치면 daily-cloudcode-pa.googleapis.com이 401 UNAUTHENTICATED를 내고, 프록시는 갱신·리플레이 없이 그 401을 그대로 올린다는 것이다. 기대한 동작은 forceRefreshOAuthAccessSnapshot으로 자격 증명을 돌리고 Cloud Code Assist projectId를 다시 맞춘 뒤 한 번 재시도하는 것이다.
지금 dev(HEAD 24cc558d5, package 2.43.0)를 보면 제보가 가리킨 두 군데가 그대로다. src/server/responses/core.ts 3329줄 isOAuth401ReplayProvider는 xai / github-copilot / kiro만이고, route.provider.authMode === "oauth"일 때만 true다. 그 플래그가 true일 때만 sentOAuthSnapshot을 저장하고(3480줄), 네이티브 Responses 패스스루 401 분기(4357줄대)와 어댑터 recovery 루프 401 분기(6109줄대)가 forceRefreshOAuthAccessSnapshot을 호출한다. google-antigravity는 이름에 없어서 업스트림 401이 오면 리플레이 없이 끊긴다. 같은 allowlist가 src/oauth/index.ts 550줄 FORCE_REFRESH_PROVIDERS에도 있고, 555줄에서 집합에 없으면 UnsupportedOAuthProviderError를 던진다. 응답 경로에 이름만 넣고 force-refresh 집합을 같이 안 열면 갱신 호출 자체가 실패한다.
Antigravity 쪽 OAuth 모듈은 이미 갱신 시 프로젝트를 다시 찾도록 되어 있다. src/oauth/google-antigravity.ts는 로그인·리프레시 모두 Cloud Code Assist projectId를 채우고, 스냅샷에도 실린다. 계정 페일오버 헬퍼 applyFailoverSnapshot(core.ts 3367–3381줄)은 route.provider.googleMode === "cloud-code-assist"에서 projectId 없으면 회전을 거부하고, 있으면 route.provider.project에 붙인다. 주석도 토큰만 바꾸고 실패한 계정의 project를 쓰면 더 나쁘다고 명시한다. 그런데 401 리플레이 경로(4376–4387, 6127–6138줄)는 지금 kiro의 parsed._kiroAuthContext만 특수 처리하고, provider 재조립은 { ...route.provider, apiKey: refreshed.accessToken }(+ Copilot baseUrl)만 한다. 여기에 google-antigravity만 allowlist에 추가하면 새 bearer와 옛 project가 짝이 남을 수 있다. 제보가 말한 refresh + re-discover project + replay를 끝내려면 allowlist 두 곳과 함께 리플레이 분기에서 refreshed.projectId를 route.provider.project에 넣는 선례(applyFailoverSnapshot과 동일)가 필요하다.
최근 dev 방향과도 맞물린다. #3561/#3562가 #3502 캐리로 OAuth proactive-failover 경계와 Kiro rotated auth를 터미널 continuation에 실었고, Antigravity는 계정 풀·쿼타(#3213)와 generic pool-settings(#3214)는 있으나 업스트림 401 자동 리플레이 명단에는 아직 없다. #3375 같은 풀 라이프사이클 이슈와 겹치는 면이 있지만, 이 이슈는 범위가 allowlist + Antigravity 스냅샷 재적용으로 훨씬 작아서 그 대형 스펙을 기다릴 필요는 없다. 중복 이슈 검색에서도 같은 제목의 열린 항목은 #3575뿐이다.
src/server/responses/core.ts 3329 - isOAuth401ReplayProvider에 google-antigravity가 없어 업스트림 401이 리플레이 없이 종료된다. src/oauth/index.ts 550–555 - FORCE_REFRESH_PROVIDERS에도 없어서 forceRefreshOAuthAccessSnapshot가 UnsupportedOAuthProviderError로 막힌다. src/server/responses/core.ts 4376–4387 / 6127–6138 - 401 리플레이가 apiKey(와 kiro/copilot 특수 필드)만 갱신하고 projectId→provider.project 재바인딩이 없다. src/server/responses/core.tsapplyFailoverSnapshot 3371–3381 - cloud-code-assist용 project 가드·주입 선례. 401 리플레이도 재사용하거나 같은 규칙을 복사해야 한다. src/oauth/google-antigravity.ts refresh/discover - 프로젝트 재발견 로직은 이미 있음. 문제는 호출 경로가 allowlist 밖으로 막힌 것이다.
메인테이너의 판단이 필요한 지점
allowlist만 열고 리플레이에서 project 재바인딩을 빼먹을지, applyFailoverSnapshot을 401 경로에도 쓰게 묶을지.
/v1/chat/completions 등 Responses 밖 경로에도 동일 401 리플레이가 필요한지(이슈 본문은 chat도 언급), 이번 PR은 Responses core.ts만으로 충분한지.
Thanks @Ingwannu. Pull request #3576 has been rebased directly onto dev (24cc558d5) and verified.
The diff is strictly scoped to:
Adding "google-antigravity" to FORCE_REFRESH_PROVIDERS in src/oauth/index.ts.
Adding google-antigravity to isOAuth401ReplayProvider and synchronizing the refreshed projectId into refreshedProvider in both non-streaming and streaming 401 replay loops in src/server/responses/core.ts.
Adding the focused regression test suite in tests/server/server-google-antigravity-oauth-401-replay.test.ts (all 7 tests passing).
The review readiness checklist in the PR description is 4/4 complete, and CodeRabbit analysis is clean. Ready for trust-boundary review and maintainer-sponsored sponsorship.
Implemented by #3691 and merged into dev as 7e7ab281cca35600b41f1f80222f3462a87dd4e1. Original author attribution is preserved. Current-head hosted CI and independent security review passed; remote focused verification: 208pass/0fail. Dev ancestry confirmed before closing.
Activity
Ingwannu commentedon Sep 5, 2026
Confirmed against current
devat24cc558d5:google-antigravityis absent from both the forced-refresh provider set and the Responses OAuth 401 replay predicate, even though its OAuth implementation already refreshes the token and re-discovers/preserves the Cloud Code Assist project. A locally valid-looking snapshot can therefore receive an upstream 401 and terminate without the existing one-refresh/one-replay recovery used by xAI, Copilot, and Kiro.The linked #3576 is aimed at the correct boundaries: add the exact built-in provider to both gates and replay with the refreshed token/project snapshot while preserving one-shot retry, singleflight, and sanitized errors. I am keeping the issue open until that security-sensitive PR is rebased onto the latest
dev, reviewed at the new exact head, and merged.lidge-jun commentedon Sep 5, 2026
리뷰 · 우선순위 64 / 80
이 이슈는 Google Antigravity OAuth 세션이 만료·무효화·다른 환경에서 회전됐을 때, 업스트림이 401을 주면 OpenCodex가 토큰을 강제 갱신하고 같은 요청을 한 번 더 보내지 않은 채 바로 401을 호출자에게 넘긴다는 버그다. 제보 재현은
ocx login google-antigravity뒤 세션이 죽은 상태에서/v1/chat/completions또는/v1/responses로google-antigravity/gemini-3.8-flash를 치면daily-cloudcode-pa.googleapis.com이401 UNAUTHENTICATED를 내고, 프록시는 갱신·리플레이 없이 그 401을 그대로 올린다는 것이다. 기대한 동작은forceRefreshOAuthAccessSnapshot으로 자격 증명을 돌리고 Cloud Code AssistprojectId를 다시 맞춘 뒤 한 번 재시도하는 것이다.지금
dev(HEAD24cc558d5, package 2.43.0)를 보면 제보가 가리킨 두 군데가 그대로다.src/server/responses/core.ts3329줄isOAuth401ReplayProvider는xai/github-copilot/kiro만이고,route.provider.authMode === "oauth"일 때만 true다. 그 플래그가 true일 때만sentOAuthSnapshot을 저장하고(3480줄), 네이티브 Responses 패스스루 401 분기(4357줄대)와 어댑터 recovery 루프 401 분기(6109줄대)가forceRefreshOAuthAccessSnapshot을 호출한다.google-antigravity는 이름에 없어서 업스트림 401이 오면 리플레이 없이 끊긴다. 같은 allowlist가src/oauth/index.ts550줄FORCE_REFRESH_PROVIDERS에도 있고, 555줄에서 집합에 없으면UnsupportedOAuthProviderError를 던진다. 응답 경로에 이름만 넣고 force-refresh 집합을 같이 안 열면 갱신 호출 자체가 실패한다.Antigravity 쪽 OAuth 모듈은 이미 갱신 시 프로젝트를 다시 찾도록 되어 있다.
src/oauth/google-antigravity.ts는 로그인·리프레시 모두 Cloud Code AssistprojectId를 채우고, 스냅샷에도 실린다. 계정 페일오버 헬퍼applyFailoverSnapshot(core.ts3367–3381줄)은route.provider.googleMode === "cloud-code-assist"에서projectId없으면 회전을 거부하고, 있으면route.provider.project에 붙인다. 주석도 토큰만 바꾸고 실패한 계정의 project를 쓰면 더 나쁘다고 명시한다. 그런데 401 리플레이 경로(4376–4387, 6127–6138줄)는 지금kiro의parsed._kiroAuthContext만 특수 처리하고, provider 재조립은{ ...route.provider, apiKey: refreshed.accessToken }(+ Copilot baseUrl)만 한다. 여기에google-antigravity만 allowlist에 추가하면 새 bearer와 옛 project가 짝이 남을 수 있다. 제보가 말한 refresh + re-discover project + replay를 끝내려면 allowlist 두 곳과 함께 리플레이 분기에서refreshed.projectId를route.provider.project에 넣는 선례(applyFailoverSnapshot과 동일)가 필요하다.최근
dev방향과도 맞물린다. #3561/#3562가 #3502 캐리로 OAuth proactive-failover 경계와 Kiro rotated auth를 터미널 continuation에 실었고, Antigravity는 계정 풀·쿼타(#3213)와 generic pool-settings(#3214)는 있으나 업스트림 401 자동 리플레이 명단에는 아직 없다. #3375 같은 풀 라이프사이클 이슈와 겹치는 면이 있지만, 이 이슈는 범위가 allowlist + Antigravity 스냅샷 재적용으로 훨씬 작아서 그 대형 스펙을 기다릴 필요는 없다. 중복 이슈 검색에서도 같은 제목의 열린 항목은 #3575뿐이다.src/server/responses/core.ts3329 -isOAuth401ReplayProvider에google-antigravity가 없어 업스트림 401이 리플레이 없이 종료된다.src/oauth/index.ts550–555 -FORCE_REFRESH_PROVIDERS에도 없어서forceRefreshOAuthAccessSnapshot가UnsupportedOAuthProviderError로 막힌다.src/server/responses/core.ts4376–4387 / 6127–6138 - 401 리플레이가 apiKey(와 kiro/copilot 특수 필드)만 갱신하고projectId→provider.project재바인딩이 없다.src/server/responses/core.tsapplyFailoverSnapshot3371–3381 - cloud-code-assist용 project 가드·주입 선례. 401 리플레이도 재사용하거나 같은 규칙을 복사해야 한다.src/oauth/google-antigravity.tsrefresh/discover - 프로젝트 재발견 로직은 이미 있음. 문제는 호출 경로가 allowlist 밖으로 막힌 것이다.메인테이너의 판단이 필요한 지점
applyFailoverSnapshot을 401 경로에도 쓰게 묶을지./v1/chat/completions등 Responses 밖 경로에도 동일 401 리플레이가 필요한지(이슈 본문은 chat도 언급), 이번 PR은 Responsescore.ts만으로 충분한지.너의 추천
라벨
bug/account-pool맞다.help wanted로 두되 구현 스펙을 고정하자: (1)FORCE_REFRESH_PROVIDERS와isOAuth401ReplayProvider에google-antigravity추가, (2) 두 401 리플레이 분기에서refreshed.projectId를route.provider.project에 반영(가능하면applyFailoverSnapshot재사용), (3) 만료 토큰 + 유효 refresh로 401→갱신→200 회귀 테스트. #3561/#3562 직후 OAuth 리플레이 맥락이 살아 있으니 작은 PR로 빨리 받는 걸 추천한다. 우선순위 64.이 댓글은 grok-bot이 작성했습니다
agentHits commentedon Sep 5, 2026
Thanks @Ingwannu. Pull request #3576 has been rebased directly onto
dev(24cc558d5) and verified.The diff is strictly scoped to:
"google-antigravity"toFORCE_REFRESH_PROVIDERSinsrc/oauth/index.ts.google-antigravitytoisOAuth401ReplayProviderand synchronizing the refreshedprojectIdintorefreshedProviderin both non-streaming and streaming 401 replay loops insrc/server/responses/core.ts.tests/server/server-google-antigravity-oauth-401-replay.test.ts(all 7 tests passing).The review readiness checklist in the PR description is 4/4 complete, and CodeRabbit analysis is clean. Ready for trust-boundary review and
maintainer-sponsoredsponsorship.lidge-jun commentedon Sep 5, 2026
Implemented by #3691 and merged into
devas7e7ab281cca35600b41f1f80222f3462a87dd4e1. Original author attribution is preserved. Current-head hosted CI and independent security review passed; remote focused verification: 208pass/0fail. Dev ancestry confirmed before closing.