From fc9afbc035bb429a4d2cf9fc8e24a82218083a7a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 08:53:28 +0000 Subject: [PATCH 1/2] Initial plan From d659ad060d5a4fbae1205448ef9638e9bef07471 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 08:56:07 +0000 Subject: [PATCH 2/2] fix: convert auth.json timestamps to RFC 3339 format for codex CLI compatibility Agent-Logs-Url: https://github.com/bubbuild/republic/sessions/bec5de29-5d89-4de9-bea2-0ef97ad4cc65 Co-authored-by: frostming <16336606+frostming@users.noreply.github.com> --- src/republic/auth/openai_codex.py | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/republic/auth/openai_codex.py b/src/republic/auth/openai_codex.py index 8a76890..b0c0750 100644 --- a/src/republic/auth/openai_codex.py +++ b/src/republic/auth/openai_codex.py @@ -9,6 +9,7 @@ import time import urllib.parse import webbrowser +from datetime import datetime, timezone from base64 import urlsafe_b64decode, urlsafe_b64encode from collections.abc import Callable from contextlib import suppress @@ -32,6 +33,20 @@ class CodexOAuthResponseError(TypeError): """Raised when Codex OAuth token response is malformed.""" +def _unix_to_rfc3339(ts: int) -> str: + """Convert a Unix timestamp to an RFC 3339 formatted string.""" + return datetime.fromtimestamp(ts, tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + + +def _rfc3339_to_unix(value: str) -> int: + """Parse an RFC 3339 formatted string and return a Unix timestamp.""" + try: + dt = datetime.fromisoformat(value.replace("Z", "+00:00")) + return int(dt.timestamp()) + except (ValueError, AttributeError): + return int(time.time()) + + class CodexOAuthLoginError(RuntimeError): """Raised when Codex OAuth login flow cannot complete.""" @@ -117,11 +132,18 @@ def _parse_tokens(payload: dict[str, Any]) -> OpenAICodexOAuthTokens | None: expires_raw = tokens.get("expires_at") if isinstance(expires_raw, (int, float)): expires_at = int(expires_raw) + elif isinstance(expires_raw, str): + expires_at = _rfc3339_to_unix(expires_raw) else: # Codex CLI file may not persist explicit expiry. # Use last_refresh + 1h or "now + 1h" as best-effort fallback. last_refresh_raw = payload.get("last_refresh") - last_refresh = int(last_refresh_raw) if isinstance(last_refresh_raw, (int, float)) else int(time.time()) + if isinstance(last_refresh_raw, (int, float)): + last_refresh = int(last_refresh_raw) + elif isinstance(last_refresh_raw, str): + last_refresh = _rfc3339_to_unix(last_refresh_raw) + else: + last_refresh = int(time.time()) expires_at = last_refresh + 3600 account_id = tokens.get("account_id") @@ -165,12 +187,12 @@ def save_openai_codex_oauth_tokens( tokens_node.update({ "access_token": tokens.access_token, "refresh_token": tokens.refresh_token, - "expires_at": tokens.expires_at, + "expires_at": _unix_to_rfc3339(tokens.expires_at), }) if tokens.account_id: tokens_node["account_id"] = tokens.account_id payload["tokens"] = tokens_node - payload["last_refresh"] = int(time.time()) + payload["last_refresh"] = _unix_to_rfc3339(int(time.time())) auth_path.write_text(json.dumps(payload, ensure_ascii=True, indent=2) + "\n", encoding="utf-8") with suppress(OSError):