diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6e019b9..d5cef71 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,14 @@
# Changelog
+## v2.1.0 (2026-05-19)
+
+- Added Codex auth status detection (reads `codex login status`)
+- Auth status bar shows logged-in provider or warning if auth missing/expired
+- "Re-login" button opens `codex login` in a terminal for re-authentication
+- Auto re-checks auth 30s after re-login flow starts
+- Pre-launch auth check warns before launching Codex Default mode if auth is invalid
+- Auth status checked asynchronously at startup (non-blocking)
+
## v2.0.1 (2026-05-19)
- Added Codex CLI/Desktop installation verifier to main page
diff --git a/codex-launcher_2.0.1_all.deb b/codex-launcher_2.0.1_all.deb
deleted file mode 100644
index 141678b..0000000
Binary files a/codex-launcher_2.0.1_all.deb and /dev/null differ
diff --git a/codex-launcher_2.1.0_all.deb b/codex-launcher_2.1.0_all.deb
new file mode 100644
index 0000000..7bee63b
Binary files /dev/null and b/codex-launcher_2.1.0_all.deb differ
diff --git a/src/codex-launcher-gui b/src/codex-launcher-gui
index 6a65119..9170285 100755
--- a/src/codex-launcher-gui
+++ b/src/codex-launcher-gui
@@ -24,6 +24,12 @@ model_catalog_json = ""
"""
CHANGELOG = [
+ ("2.1.0", "2026-05-19", [
+ "Added Codex auth status detection (codex login status)",
+ "Added Re-login button to re-authenticate via codex login",
+ "Auto-checks auth before launching Codex Default mode",
+ "Warns if OAuth token expired or missing before launch",
+ ]),
("2.0.1", "2026-05-19", [
"Added Codex CLI/Desktop installation verifier to main page",
"Disables Desktop/CLI launch buttons when corresponding tool is missing",
@@ -450,6 +456,25 @@ def _detect_codex_desktop():
return str(START_SH)
return None
+def _check_codex_auth():
+ try:
+ out = subprocess.run(
+ ["codex", "login", "status"],
+ capture_output=True, text=True, timeout=10,
+ )
+ text = (out.stdout or "").strip()
+ if not text:
+ text = (out.stderr or "").strip()
+ if out.returncode == 0 and text:
+ return ("logged_in", text)
+ if text:
+ return ("error", text)
+ return ("unknown", "No output from codex login status")
+ except FileNotFoundError:
+ return ("not_installed", "codex not found")
+ except Exception as e:
+ return ("error", str(e))
+
# ═══════════════════════════════════════════════════════════════════
# Main window
# ═══════════════════════════════════════════════════════════════════
@@ -469,7 +494,7 @@ class LauncherWin(Gtk.Window):
# header row
hdr = Gtk.Box(spacing=8)
vbox.pack_start(hdr, False, False, 0)
- lbl = Gtk.Label(label="Codex Launcher v2.0.1")
+ lbl = Gtk.Label(label="Codex Launcher v2.1.0")
lbl.set_use_markup(True)
hdr.pack_start(lbl, False, False, 0)
changelog_btn = Gtk.Button(label="Changelog")
@@ -522,6 +547,19 @@ class LauncherWin(Gtk.Window):
if not self._desktop_info:
self._missing.append("desktop")
+ auth_box = Gtk.Box(spacing=12)
+ vbox.pack_start(auth_box, False, False, 0)
+ self._auth_label = Gtk.Label()
+ self._auth_label.set_markup("Checking auth…")
+ self._auth_label.set_use_markup(True)
+ self._auth_label.set_ellipsize(3)
+ auth_box.pack_start(self._auth_label, False, False, 0)
+ self._relogin_btn = Gtk.Button(label="Re-login")
+ self._relogin_btn.set_sensitive(False)
+ self._relogin_btn.connect("clicked", lambda b: self._codex_relogin())
+ auth_box.pack_end(self._relogin_btn, False, False, 0)
+ threading.Thread(target=self._check_auth_async, daemon=True).start()
+
ops_box = Gtk.Box(spacing=8)
vbox.pack_start(ops_box, False, False, 0)
self._refresh_all_btn = Gtk.Button(label="Refresh Models")
@@ -634,6 +672,50 @@ class LauncherWin(Gtk.Window):
else:
self.log("All dependencies OK.")
+ def _check_auth_async(self):
+ status, msg = _check_codex_auth()
+ GLib.idle_add(self._update_auth_status, status, msg)
+
+ def _update_auth_status(self, status, msg):
+ if status == "logged_in":
+ self._auth_label.set_markup(f"✔ Auth: {msg}")
+ self._relogin_btn.set_sensitive("cli" not in self._missing)
+ elif status == "not_installed":
+ self._auth_label.set_markup("Auth: N/A (CLI not installed)")
+ else:
+ self._auth_label.set_markup(f"⚠ Auth: {msg}")
+ self._relogin_btn.set_sensitive("cli" not in self._missing)
+ return False
+
+ def _codex_relogin(self):
+ self.log("Opening codex login in terminal…")
+ terms = [
+ ("x-terminal-emulator", ["-e"]),
+ ("kgx", ["--"]),
+ ("gnome-terminal", ["--"]),
+ ("konsole", ["-e"]),
+ ("xterm", ["-e"]),
+ ]
+ term = None
+ term_args = None
+ for t in terms:
+ if shutil.which(t[0]):
+ term = t[0]
+ term_args = t[1]
+ break
+ if not term:
+ self.log("ERROR: no terminal emulator found for re-login")
+ return
+ cmd_parts = [term] + term_args + ["codex", "login"]
+ subprocess.Popen(cmd_parts, preexec_fn=os.setsid)
+ self.log("Login flow started in terminal. Re-checking auth in 30s…")
+ self._auth_label.set_markup("Auth: waiting for login…")
+ threading.Thread(target=self._delayed_auth_check, daemon=True).start()
+
+ def _delayed_auth_check(self):
+ time.sleep(30)
+ self._check_auth_async()
+
def _set_busy(self, busy):
def _update():
has_cli = "cli" not in self._missing
@@ -883,6 +965,20 @@ class LauncherWin(Gtk.Window):
threading.Thread(target=self._run, args=(ep, model, target), daemon=True).start()
def _launch_codex_default(self, target):
+ if "cli" not in self._missing:
+ status, msg = _check_codex_auth()
+ if status != "logged_in":
+ d = Gtk.MessageDialog(
+ self, 0, Gtk.MessageType.WARNING, Gtk.ButtonsType.YES_NO,
+ f"Codex auth check: {msg}\n\n"
+ "Launch may fail without valid authentication.\n"
+ "Continue anyway?"
+ )
+ r = d.run()
+ d.destroy()
+ if r != Gtk.ResponseType.YES:
+ self._set_busy(False)
+ return
self._set_busy(True)
self.log(f"=== Codex Default (OAuth) → {'Desktop' if target == 'desktop' else 'CLI'} ===")
threading.Thread(target=self._run_codex_default, args=(target,), daemon=True).start()