Identify Agent Zero OAuth refresh requests

Cooper Gamble committed May 29, 2026 at 12:06 UTC 808629942ac3818bf3c03725cf1deeec063e6298
2 files changed +68 -1
plugins/_oauth/helpers/codex.py
+14 -1
@@ -500,7 +500,10 @@ def refresh_tokens(refresh_token: str) -> dict[str, str]:
500 cfg = codex_config()
501 response = requests.post(
502 cfg["token_url"],
503 - headers={"Content-Type": "application/json"},
503 + headers={
504 + "Content-Type": "application/json",
505 + "User-Agent": resolve_agent_zero_user_agent(),
506 + },
507 json={
508 "client_id": cfg["client_id"],
509 "grant_type": "refresh_token",
@@ -522,6 +525,16 @@ def refresh_tokens(refresh_token: str) -> dict[str, str]:
525 }
526
527
528 +def resolve_agent_zero_user_agent() -> str:
529 + try:
530 + from helpers import git
531 +
532 + version = git.get_version()
533 + except Exception:
534 + version = "unknown"
535 + return f"agent-zero/{version or 'unknown'}"
536 +
537 +
538 def should_refresh(access_token: str, last_refresh: str) -> bool:
539 if not access_token:
540 return True
tests/test_oauth_codex.py
+54
@@ -258,6 +258,60 @@ def test_token_error_message_prefers_description():
258 assert codex._token_error_message(FakeResponse()) == "refresh token was already used"
259
260
261 +def test_refresh_tokens_sends_agent_zero_user_agent(monkeypatch):
262 + requests: list[dict] = []
263 +
264 + class FakeResponse:
265 + ok = True
266 +
267 + @staticmethod
268 + def json():
269 + return {
270 + "access_token": "access-1",
271 + "refresh_token": "refresh-1",
272 + }
273 +
274 + def post(url, *, headers, json, timeout):
275 + requests.append(
276 + {
277 + "url": url,
278 + "headers": headers,
279 + "json": json,
280 + "timeout": timeout,
281 + }
282 + )
283 + return FakeResponse()
284 +
285 + monkeypatch.setattr(
286 + codex,
287 + "codex_config",
288 + lambda: {"token_url": "https://auth.example/oauth/token", "client_id": "client"},
289 + )
290 + monkeypatch.setattr(codex, "resolve_agent_zero_user_agent", lambda: "agent-zero/v1.18")
291 + monkeypatch.setattr(codex.requests, "post", post)
292 +
293 + assert codex.refresh_tokens("refresh-0") == {
294 + "id_token": "",
295 + "access_token": "access-1",
296 + "refresh_token": "refresh-1",
297 + }
298 + assert requests == [
299 + {
300 + "url": "https://auth.example/oauth/token",
301 + "headers": {
302 + "Content-Type": "application/json",
303 + "User-Agent": "agent-zero/v1.18",
304 + },
305 + "json": {
306 + "client_id": "client",
307 + "grant_type": "refresh_token",
308 + "refresh_token": "refresh-0",
309 + },
310 + "timeout": 30,
311 + }
312 + ]
313 +
314 +
315 def test_default_auth_file_ignores_codex_cli_credentials(tmp_path, monkeypatch):
316 shared_auth = tmp_path / ".codex" / "auth.json"
317 private_auth = tmp_path / "usr" / "plugins" / "_oauth" / "codex" / "auth.json"