Hmbown/CodeWhale · error
ChatGPT revoke task was lost
Error message
ChatGPT revoke task was lost: {err} What it means
Thrown when the background task that revokes Codewhale-owned ChatGPT OAuth tokens completes but its JoinHandle returns a JoinError instead of the revoke result (the task panicked or was cancelled). The OAuthProvider::revoke call is spawned via tokio::task::spawn and awaited; a JoinError means the outcome of the revocation is unknown — the token may or may not have been revoked server-side. The library surfaces this instead of silently treating an unknown revoke state as success.
Solutions
- Retry the revoke operation — the JoinError is usually transient (shutdown/cancel race), and re-running the revoke re-spawns the task.
- Check logs for a panic in the ChatGPT OAuth revoke path and fix the underlying panic cause (e.g. malformed stored token JSON at the config path).
- If the runtime is shutting down, re-run `codew` after startup completes rather than revoking during teardown.
- As a last resort, revoke tokens manually via the ChatGPT/OpenAI web session and then clear the stored OAuth state with config.clear_codewhale_owned_chatgpt_oauth().
Example fix
// before
let outcome = tokio::spawn(async move { revoke(...) }).await
.map_err(|err| anyhow::anyhow!("ChatGPT revoke task was lost: {err}"))
.and_then(|result| result);
// after: retry the join once before failing
let outcome = match tokio::spawn(async move { revoke(...) }).await
.map_err(|err| anyhow::anyhow!("ChatGPT revoke task was lost: {err}"))
{
Ok(result) => result,
Err(err) => {
tracing::warn!("revoke join failed, retrying: {err:#}");
tokio::spawn(async move { revoke(...) }).await
.map_err(|err| anyhow::anyhow!("ChatGPT revoke task was lost: {err}"))?
}
}; Defensive patterns
Strategy: try-catch
Try / catch
match spawned_revoke.await {
Ok(Ok(())) => config.clear_codewhale_owned_chatgpt_oauth(),
Ok(Err(e)) => eprintln!("revoke failed: {e:#}"),
Err(join_err) => {
eprintln!("revoke task lost: {join_err}; retrying is safe");
// retry once before giving up
}
} Prevention
- Do not revoke tokens during runtime shutdown; revoke while the runtime is fully alive.
- Retry join failures once before surfacing the error to the user.
- Treat an unknown revoke outcome as unverified and re-check token state afterwards.
- Keep the revoke task free of panicking code paths (validate stored token JSON before spawning).
When it happens
Trigger: Calling the Codewhale-owned ChatGPT token revoke flow (clearing ChatGPT OAuth) when the spawned revoke task panics or is cancelled before returning its Result, so `.await` on the JoinHandle yields Err(join_err) which is mapped to this message.
Common situations: Revoking ChatGPT tokens from the TUI while the runtime is shutting down and drops the task; a panic inside the OAuth revoke path (network layer, TLS, serde); running under a runtime configured with a short blocking/task timeout that cancels the spawn.
Related errors
- dispatch task was lost
- MCP import failed
- OAuth login was cancelled
- sub-agent task should finish
- Task worker shutdown failed
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/4c84bc2138044c15.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/tui/ui/apply.rs:3541
)
.await
}
/// `/auth chatgpt-revoke`. The remote revoke is one blocking HTTP round trip
/// per stored token under the OAuth lifecycle lock, so it runs on the blocking
/// pool instead of the event loop. It targets the session's own config file
/// and clears the live route afterwards so the header stops claiming OAuth.
pub(crate) async fn run_chatgpt_revoke_from_tui(app: &mut App, config: &mut Config) {
let config_path = app.config_path.clone();
let outcome = tokio::task::spawn_blocking(move || {
crate::oauth::revoke_owned_login(
crate::oauth::OAuthProvider::Chatgpt,
config_path.as_deref(),
None,
)
})
.await
.map_err(|err| anyhow::anyhow!("ChatGPT revoke task was lost: {err}"))
.and_then(|result| result);
let message = match outcome {
Ok(()) => {
config.clear_codewhale_owned_chatgpt_oauth();
"Revoked Codewhale-owned ChatGPT tokens. Codex CLI consent is unchanged.".to_string()
}
Err(err) => format!("ChatGPT revoke failed: {err:#}"),
};
app.add_message(HistoryCell::System {
content: message.clone(),
});
app.status_message = Some(message);
app.needs_redraw = true;
}
#[cfg(test)]
pub(crate) fn apply_loaded_session(
app: &mut App,View on GitHub (pinned to 73e0f67d83)