diff --git a/scripts/codex_audit_service.py b/scripts/codex_audit_service.py index 8448e3e..29e2b43 100644 --- a/scripts/codex_audit_service.py +++ b/scripts/codex_audit_service.py @@ -672,7 +672,23 @@ def _job_dedupe_key(payload: dict[str, Any]) -> str: def _classify_failure(error: str) -> str: text = error.lower() - if any(word in text for word in ("permission", "unauth", "forbidden", "oidc", "token", "allow", "secret")): + auth_config_signals = ( + "permission denied", + "unauthorized", + "forbidden", + "oidc", + "missing bearer", + "missing token", + "invalid token", + "bad credentials", + "not allowed", + "allowlist", + "api key is required", + "no api key configured", + "secret is missing", + "secret not configured", + ) + if any(signal in text for signal in auth_config_signals): return "auth_or_config_failure" if any(word in text for word in ("quota", "rate limit", "too many active", "budget")): return "quota_or_capacity_failure" diff --git a/scripts/run_monthly_codex_audit.py b/scripts/run_monthly_codex_audit.py index 1f298a9..6a4529a 100644 --- a/scripts/run_monthly_codex_audit.py +++ b/scripts/run_monthly_codex_audit.py @@ -171,7 +171,23 @@ class BridgeError(RuntimeError): def classify_service_failure(error: str) -> str: text = error.lower() - if any(word in text for word in ("permission", "unauth", "forbidden", "oidc", "token", "allow", "secret")): + auth_config_signals = ( + "permission denied", + "unauthorized", + "forbidden", + "oidc", + "missing bearer", + "missing token", + "invalid token", + "bad credentials", + "not allowed", + "allowlist", + "api key is required", + "no api key configured", + "secret is missing", + "secret not configured", + ) + if any(signal in text for signal in auth_config_signals): return "auth_or_config_failure" if any(word in text for word in ("quota", "rate limit", "too many active", "budget")): return "quota_or_capacity_failure" diff --git a/service/ai_gateway_service.py b/service/ai_gateway_service.py index 41981ae..65ffd5f 100644 --- a/service/ai_gateway_service.py +++ b/service/ai_gateway_service.py @@ -436,7 +436,23 @@ def _job_dedupe_key(payload: dict[str, Any]) -> str: def _classify_failure(error: str) -> str: text = error.lower() - if any(word in text for word in ("permission", "unauth", "forbidden", "oidc", "token", "allow", "secret")): + auth_config_signals = ( + "permission denied", + "unauthorized", + "forbidden", + "oidc", + "missing bearer", + "missing token", + "invalid token", + "bad credentials", + "not allowed", + "allowlist", + "api key is required", + "no api key configured", + "secret is missing", + "secret not configured", + ) + if any(signal in text for signal in auth_config_signals): return "auth_or_config_failure" if any(word in text for word in ("quota", "rate limit", "too many active", "budget")): return "quota_or_capacity_failure" diff --git a/tests/test_run_monthly_codex_audit.py b/tests/test_run_monthly_codex_audit.py index 8ee999c..25121ff 100644 --- a/tests/test_run_monthly_codex_audit.py +++ b/tests/test_run_monthly_codex_audit.py @@ -535,6 +535,10 @@ def test_service_failure_classification_identifies_infra_failures(self) -> None: self.assertTrue(is_service_infrastructure_failure("Codex audit service job failed [transient_service_failure]: timed out")) self.assertFalse(is_service_infrastructure_failure("Codex audit service job failed [patch_contract_failure]: invalid JSON")) + def test_service_failure_classification_ignores_source_code_secret_words(self) -> None: + message = "codex exec failed: BLOCKED_PATH_RE = r'.*token.*|.*secret.*'" + self.assertEqual(classify_service_failure(message), "unknown_failure") + def test_codex_audit_service_async_job_lifecycle(self) -> None: with tempfile.TemporaryDirectory() as tmp: env = {