Conversation
A Codex pool account whose refresh grant is rejected upstream cooled down for five minutes and then read as Live again, forever. Classify the refresh failure (invalid_grant, no refresh token, lost rotated writeback) with a typed marker and mark the account needs_relogin from the resolution, force-refresh, token_env-401 and still-401-after-refresh paths, as the Claude pool already does. Transient token-endpoint failures keep the cooldown only. Closes pleaseai#616
There was a problem hiding this comment.
Code Review
This pull request introduces a structured error-handling mechanism for ChatGPT OAuth authentication by defining a ChatGptAuthError type and a TerminalRefresh enum. This allows the system to distinguish between transient failures and terminal failures, such as invalid refresh tokens. Consequently, the account pool now correctly marks accounts as needs_relogin upon terminal failure, preventing dead accounts from remaining in a continuous cooldown loop. The changes include updates to the account resolution logic, logging, and the addition of comprehensive tests to verify these scenarios. I have no feedback to provide.
|
| match refreshed.to_credential() { | ||
| Ok(credential) => { | ||
| tokio::task::spawn_blocking(move || { | ||
| write_refreshed_auth(&path, refreshed) | ||
| }) | ||
| .await | ||
| .map_err(|error| { | ||
| auth_error(format!("ChatGPT auth write task failed: {error}")) | ||
| ChatGptAuthError::new(format!( | ||
| "ChatGPT auth write task failed: {error}" | ||
| )) | ||
| }) |
There was a problem hiding this comment.
Rotated tokens remain retryable
Once the provider returns a different refresh token, every failure before that replacement is persisted must be terminal. Here, an access token missing the account-id claim exits through Err(error) without writeback, while a blocking write-task failure becomes nonterminal before rotated is checked. Either path can leave the consumed refresh token on disk, so the account repeats five-minute cooldowns without being marked needs_relogin.
Knowledge Base Used:
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auth/codex/auth.rs
Line: 283-293
Comment:
**Rotated tokens remain retryable**
Once the provider returns a different refresh token, every failure before that replacement is persisted must be terminal. Here, an access token missing the account-id claim exits through `Err(error)` without writeback, while a blocking write-task failure becomes nonterminal before `rotated` is checked. Either path can leave the consumed refresh token on disk, so the account repeats five-minute cooldowns without being marked `needs_relogin`.
**Knowledge Base Used:**
- [Authentication and account management](https://app.greptile.com/passionfactory/-/custom-context/knowledge-base/pleaseai/shunt/-/docs/authentication.md)
- [Provider OAuth and credential flows](https://app.greptile.com/passionfactory/-/custom-context/knowledge-base/pleaseai/shunt/-/docs/provider-oauth.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| dashboard, or run `shunt login codex --name <account-name>` again", | ||
| )); | ||
| } | ||
| let detail: String = text.chars().take(200).collect(); |
There was a problem hiding this comment.
The token endpoint's first 200 response characters are copied into detail and logged verbatim on refresh failures. A proxy or endpoint response containing newlines, control characters, reflected input, or sensitive diagnostics can therefore corrupt structured logs or disclose response content. Sanitize the body before logging it, or retain only parsed OAuth error fields.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auth/codex/auth.rs
Line: 492
Comment:
**Response bodies reach logs**
The token endpoint's first 200 response characters are copied into `detail` and logged verbatim on refresh failures. A proxy or endpoint response containing newlines, control characters, reflected input, or sensitive diagnostics can therefore corrupt structured logs or disclose response content. Sanitize the body before logging it, or retain only parsed OAuth error fields.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Merging this PR will degrade performance by 11.79%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ❌ | parse_body_to_value[50] |
756.7 µs | 860 µs | -12.01% |
| ❌ | parse_body_to_value[200] |
3 ms | 3.4 ms | -11.57% |
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing r-uben:fix/616-codex-needs-relogin (63c230b) with main (bd2cf38)
Summary
A Codex (
chatgpt_oauth) pool account whose refresh grant is rejected upstream cooled down for five minutes and then read asLiveon the admin dashboard again, forever. The Claude pool already classifies a terminal refresh failure and marks the accountneeds_relogin; the Codex pool never did.auth::codex::authgains a typedTerminalRefreshmarker (InvalidGrant,NoRefreshToken,WritebackFailed) carried on a newChatGptAuthErrornext to the loggeddetail, mirroringClaudeResolveError. The token endpoint's status anderrorbody are no longer collapsed into the constant"authentication failed"before the pool sees them.adapters::responses::poolmarksneeds_reloginfrom the four paths the Claude pool uses: a terminal failure at resolution or on the 401 → force-refresh (RefreshGrant), a 401 on atoken_envcredential, and a still-401 retry after a good refresh (ServedRequest). Transient token-endpoint failures keep the plain cooldown.docs/m9-admin-surface.mdrecords the Codex behaviour. Site docs already describe the flag generically for every pool.Closes #616
Test plan
invalid_grant→ terminal, 503 → not terminal, no refresh token → terminal.codex_multi_account: aninvalid_grantrefresh marks account Aneeds_reloginand serves from B; a 503 only cools A down.cargo fmt --all --check,cargo clippy --all-targets --all-features -- -D warnings.cargo test --all-features --workspace: all pass except three tests inresponses_chain_streamthat also fail on untouchedmainon this macOS host (a bound non-listening socket times out instead of refusing).Summary by cubic
Fixes Codex pool accounts whose refresh grant is terminally rejected (
invalid_grant, no refresh token, or lost rotated writeback) being reported as live on the admin dashboard after each five-minute cooldown, forever. These failures now mark the accountneeds_relogin, matching the Claude pool, while transient token-endpoint failures keep the existing cooldown-only behavior.needs_reloginfrom the resolution, force-refresh,token_env401, and still-401-after-refresh paths."authentication failed".Written for commit 63c230b. Summary will update on new commits.