Summary
Cancelling an OpenAI Codex device-code OAuth flow does not stop the background polling worker, and if the user authorizes anyway after cancelling, credentials are still written — potentially to the wrong profile.
Root cause
- The poll loop (
hermes_cli/web_server.py, around the while time.monotonic() < deadline: ... block near line 11296) never re-checks whether the session was cancelled.
- The target profile is resolved lazily via
_oauth_session_profile(session_id) only at write time (~line 11347), not captured up front.
DELETE /api/providers/oauth/sessions/{session_id} (~line 11444) only does _oauth_sessions.pop(session_id, None) — no cancellation token/event, and it does not signal the worker thread.
- After the pop,
_oauth_session_profile(session_id) returns None (session no longer in the dict), which falls through to whatever profile is "current" at completion time, not the profile that was originally selected when the flow started.
Impact
- User believes the auth flow was aborted, but credentials may still be written later.
- Credentials can land in the wrong (current/default) profile instead of the originally targeted one, breaking profile isolation.
- The worker keeps consuming a thread/polling the provider until session expiry even after cancel.
Suggested fix (root cause)
- Track cancellation via a per-session event/flag, checked before every poll iteration, before the code exchange, and before the token write.
- Capture the validated target profile once at session start (immutable for the life of the worker) instead of re-resolving it at write time.
- Require the session to still exist, be uncancelled, and match the same provider/profile before persisting tokens.
Reproduction
Local deterministic repro built with TestClient, fake HTTP responses, and sentinel tokens: worker paused right after publishing the device code, DELETE called, poll then approved. Result: cancel endpoint returns 200, session removed, but the worker still finishes and calls the token-save path once, with the profile scope resolved to None instead of the original profile.
Summary
Cancelling an OpenAI Codex device-code OAuth flow does not stop the background polling worker, and if the user authorizes anyway after cancelling, credentials are still written — potentially to the wrong profile.
Root cause
hermes_cli/web_server.py, around thewhile time.monotonic() < deadline: ...block near line 11296) never re-checks whether the session was cancelled._oauth_session_profile(session_id)only at write time (~line 11347), not captured up front.DELETE /api/providers/oauth/sessions/{session_id}(~line 11444) only does_oauth_sessions.pop(session_id, None)— no cancellation token/event, and it does not signal the worker thread._oauth_session_profile(session_id)returnsNone(session no longer in the dict), which falls through to whatever profile is "current" at completion time, not the profile that was originally selected when the flow started.Impact
Suggested fix (root cause)
Reproduction
Local deterministic repro built with
TestClient, fake HTTP responses, and sentinel tokens: worker paused right after publishing the device code,DELETEcalled, poll then approved. Result: cancel endpoint returns 200, session removed, but the worker still finishes and calls the token-save path once, with the profile scope resolved toNoneinstead of the original profile.