From 7957da7a1d5a2e39bb04597a48a05ade613238a8 Mon Sep 17 00:00:00 2001 From: sprmn24 Date: Sat, 25 Apr 2026 00:48:55 +0300 Subject: [PATCH] fix(web_server): hold _oauth_sessions_lock during PKCE session state writes _submit_anthropic_pkce() retrieved sess under _oauth_sessions_lock but wrote back to sess["status"] and sess["error_message"] outside the lock. A concurrent session GC or cancel could race with these writes, producing inconsistent session state. Wrap all 4 sess write sites in _oauth_sessions_lock: - network exception path (Token exchange failed) - missing access_token path - credential save failure path - success path (approved) --- hermes_cli/web_server.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index daca1dbf7..8c33a383e 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -1533,26 +1533,30 @@ def _submit_anthropic_pkce(session_id: str, code_input: str) -> Dict[str, Any]: with urllib.request.urlopen(req, timeout=20) as resp: result = json.loads(resp.read().decode()) except Exception as e: - sess["status"] = "error" - sess["error_message"] = f"Token exchange failed: {e}" + with _oauth_sessions_lock: + sess["status"] = "error" + sess["error_message"] = f"Token exchange failed: {e}" return {"ok": False, "status": "error", "message": sess["error_message"]} access_token = result.get("access_token", "") refresh_token = result.get("refresh_token", "") expires_in = int(result.get("expires_in") or 3600) if not access_token: - sess["status"] = "error" - sess["error_message"] = "No access token returned" + with _oauth_sessions_lock: + sess["status"] = "error" + sess["error_message"] = "No access token returned" return {"ok": False, "status": "error", "message": sess["error_message"]} expires_at_ms = int(time.time() * 1000) + (expires_in * 1000) try: _save_anthropic_oauth_creds(access_token, refresh_token, expires_at_ms) except Exception as e: - sess["status"] = "error" - sess["error_message"] = f"Save failed: {e}" + with _oauth_sessions_lock: + sess["status"] = "error" + sess["error_message"] = f"Save failed: {e}" return {"ok": False, "status": "error", "message": sess["error_message"]} - sess["status"] = "approved" + with _oauth_sessions_lock: + sess["status"] = "approved" _log.info("oauth/pkce: anthropic login completed (session=%s)", session_id) return {"ok": True, "status": "approved"}