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