From 0e2123a09ea5c30799474b8c8187d7bb8f58ff5b Mon Sep 17 00:00:00 2001 From: Talos Kernel Date: Wed, 7 Oct 2026 13:33:11 +0000 Subject: [PATCH] Classify expired Codex OAuth refresh diagnostics safely --- CLAUDE.md | 4 ++-- README.md | 4 ++-- site/console.html | 4 ++-- site/docs/index.html | 2 +- site/index.html | 2 +- site/llms.txt | 2 +- site/redteam/index.html | 2 +- site/registry/index.html | 2 +- site/setup/index.html | 2 +- site/status.json | 2 +- site/vergleich/index.html | 2 +- talos/provider_errors.py | 13 +++++++++++++ tests/test_provider_resilience.py | 18 ++++++++++++++++++ 13 files changed, 45 insertions(+), 14 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2bb37f8..29e9cb6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,7 +14,7 @@ preferences live in `USER.md`. All three are operator-owned prompt state and rel |---|---| | Gate path | `policy.py`, **985 lines** — has to stay readable in one sitting | | Tools | **33**, every one gated | -| Suites | **3216** collected tests · **263** adversarial · 44 end-to-end | +| Suites | **3219** collected tests · **263** adversarial · 44 end-to-end | | Home | · docs at `/docs/` | | Repository | `talos-kernel/talos` is the public source tree | @@ -261,7 +261,7 @@ properties covered by `test_fallback_control.py` and `test_runtime_failure_repai ```bash python3 -m venv .venv && . .venv/bin/activate && pip install --require-hashes -r requirements.lock -r requirements-dev.lock -python -m pytest tests/ -q # 3216 collected tests; platform-specific skips are explicit +python -m pytest tests/ -q # 3219 collected tests; platform-specific skips are explicit python redteam.py # 263 adversarial cases — mandatory for any kernel change python e2e.py # 44 cases against a real model (costs tokens and time) python -m talos --once # single cycle, for diagnosis diff --git a/README.md b/README.md index 4b24f69..73f3241 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,10 @@

- - Tests + Tests Red team Gate path Tools diff --git a/site/console.html b/site/console.html index 5242c0a..44bc77a 100644 --- a/site/console.html +++ b/site/console.html @@ -378,7 +378,7 @@

Witness one deed from door to ledger.

This candidate, measured
- 3216automated tests + 3219automated tests 263/263adversarial 985lines · gate 33gated tools @@ -554,7 +554,7 @@

Numbers this candidate must ship.

0.20.0-beta.4
beta candidate
public installer v0.20.0-beta.3
-
3216
automated tests, collected by pytest
+
3219
automated tests, collected by pytest
263
adversarial cases, all behaving
985
lines in policy.py, readable in one sitting
33
tools, every one through the gate
diff --git a/site/docs/index.html b/site/docs/index.html index 3777fe6..5816f9c 100644 --- a/site/docs/index.html +++ b/site/docs/index.html @@ -242,7 +242,7 @@

Talos AI agent: the field manual

That one decision shapes the rest of this manual. It is why the command list is short, and why a few obvious conveniences are missing on purpose.

-
3216automated tests
+
3219automated tests
263/263adversarial
985gate lines
33gated tools
diff --git a/site/index.html b/site/index.html index 6a1beac..f1db64a 100644 --- a/site/index.html +++ b/site/index.html @@ -638,7 +638,7 @@

Measured, with the path stated

None of this asks for belief. The installer runs the unit and adversarial suites in front of you; the real-model cases are a separate opt-in E2E run.

Release snapshot: 0.20.0-beta.4. Counts describe its test suite, not speed or real-world success rates.

-
0
automated tests collected from this beta release
+
0
automated tests collected from this beta release
0
adversarial cases trying to pass the kernel
0
opt-in end-to-end cases against a real model
0
lines in the file that decides every verdict
diff --git a/site/llms.txt b/site/llms.txt index 8e7d462..1a87cd2 100644 --- a/site/llms.txt +++ b/site/llms.txt @@ -6,7 +6,7 @@ - Permission kernel: 985 lines of Python, the only code that decides - 33 tools, all gated; unattended runs cannot escalate -- 3216 tests (automated) and 263/263 red-team cases green at the release gate +- 3219 tests (automated) and 263/263 red-team cases green at the release gate - MCP servers run only inside a UID-separated worker, from an operator-owned registry — no marketplace, no third-party code in the agent process - Channels: Telegram, mail (IMAP pull), CLI, WhatsApp via an operator-controlled broker — every channel fetches; no inbound port, no webhook - Updates: signed tarballs, verified before anything is unpacked; dependencies pinned and hashed, installed with --require-hashes diff --git a/site/redteam/index.html b/site/redteam/index.html index 505d4af..fc3c1a7 100644 --- a/site/redteam/index.html +++ b/site/redteam/index.html @@ -224,7 +224,7 @@

263 ways to break an agent.

25attack surfaces
985kernel lines
33gated tools
-
3216automated tests
+
3219automated tests
0passes sold as safety
diff --git a/site/registry/index.html b/site/registry/index.html index ffb686e..a743821 100644 --- a/site/registry/index.html +++ b/site/registry/index.html @@ -239,7 +239,7 @@

The MCP registry

guide with the full protocol is docs/claude-worker.md. A curated starting point ships as examples/mcp-servers.json.

-
3216automated tests
+
3219automated tests
263/263adversarial
985gate lines
33gated tools
diff --git a/site/setup/index.html b/site/setup/index.html index 803a5ac..ba5ca49 100644 --- a/site/setup/index.html +++ b/site/setup/index.html @@ -163,7 +163,7 @@

This address moved.

Return to the keep
-
3216automated tests
+
3219automated tests
263/263adversarial
985gate lines
33gated tools
diff --git a/site/status.json b/site/status.json index 9ec5029..32d342e 100644 --- a/site/status.json +++ b/site/status.json @@ -2,7 +2,7 @@ "version": "0.20.0-beta.4", "release_state": "released", "installer_version": "0.20.0-beta.4", - "tests": 3216, + "tests": 3219, "redteam_faelle": 263, "kernel_zeilen": 985, "werkzeuge": 33, diff --git a/site/vergleich/index.html b/site/vergleich/index.html index f32a079..d2550d9 100644 --- a/site/vergleich/index.html +++ b/site/vergleich/index.html @@ -290,7 +290,7 @@

The others can do more.

33tools here, every one gated
100+skills there · counted honestly
-
3216automated tests
+
3219automated tests
263/263adversarial cases
985gate lines
0inbound ports
diff --git a/talos/provider_errors.py b/talos/provider_errors.py index c55e7c5..5b30e1b 100644 --- a/talos/provider_errors.py +++ b/talos/provider_errors.py @@ -34,6 +34,12 @@ def event_data(self) -> dict: "unknown": "The provider process failed without a classified diagnostic.", } +_AUTH_FAILURE_MARKERS = ( + "your authentication token has been invalidated", + "your access token could not be refreshed because your refresh token has expired", + "refresh token has expired. please log out and sign in again", +) + def cli_failure(stdout: str, stderr: str, exit_code: int, *, provider: str, model: str) -> ReasonerFailure: @@ -81,6 +87,13 @@ def cli_failure(stdout: str, stderr: str, exit_code: int, *, or "The API returned a response containing only thinking content" in bounded): kind = "empty_response" break + # Official Codex CLI refresh failures are plain stderr rather than the + # structured provider envelope above. Match only the bounded, stable + # authentication diagnostics; arbitrary Exit-1 output remains unknown. + lowered = bounded.lower() + if any(marker in lowered for marker in _AUTH_FAILURE_MARKERS): + kind = "key_rejected" + break message = _MESSAGES[kind] if reset: message = "You've hit your session limit · resets " + reset diff --git a/tests/test_provider_resilience.py b/tests/test_provider_resilience.py index e89dc35..4d84f21 100644 --- a/tests/test_provider_resilience.py +++ b/tests/test_provider_resilience.py @@ -67,6 +67,24 @@ def test_failed_cli_does_not_expose_unknown_output(): assert 'private entire prompt' not in str(e) +@pytest.mark.parametrize('diagnostic', [ + 'Your authentication token has been invalidated. Please try signing in again.', + 'Your access token could not be refreshed because your refresh token has expired. ' + 'Please log out and sign in again.', +]) +def test_codex_auth_refresh_failure_is_classified_without_leaking_text(diagnostic): + e = cli_failure('', diagnostic, 1, provider='openai-codex', model='gpt-5.6-sol') + assert e.kind == 'key_rejected' + assert e.fallback_allowed is False + assert diagnostic not in str(e) + + +def test_unrelated_exit_one_remains_unknown(): + e = cli_failure('', 'unrelated local process crash', 1, + provider='openai-codex', model='gpt-5.6-sol') + assert e.kind == 'unknown' + + @pytest.mark.parametrize('backend', ['claude', 'hermes']) def test_probe_timeout_never_exposes_the_subprocess_prompt(tmp_path, monkeypatch, backend): import subprocess