Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 29 additions & 26 deletions cron/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,34 +98,37 @@ def _summarize_cron_failure_for_delivery(job: dict, error: str | None) -> str:
text = (error or "unknown error").strip()
lower = text.lower()

# Provider/API failures are the common noisy path. Keep these short.
if "429" in text or "rate limit" in lower or "usage limit" in lower:
reason = "rate limit"
if "weekly usage limit" in lower:
reason = "weekly usage limit"
elif "quota" in lower:
reason = "quota limit"
return (
f"⚠️ Cron '{job_name}' failed: provider {reason}. "
"Fallback chain was exhausted or unavailable. "
"Full details saved in cron output."
)
# Script-only jobs never call a model provider. Their stdout may still
# mention status codes, quotas, or timeout tests, so provider-specific
# classification would mislabel an unrelated script failure.
if not job.get("no_agent"):
if "429" in text or "rate limit" in lower or "usage limit" in lower:
reason = "rate limit"
if "weekly usage limit" in lower:
reason = "weekly usage limit"
elif "quota" in lower:
reason = "quota limit"
return (
f"⚠️ Cron '{job_name}' failed: provider {reason}. "
"Fallback chain was exhausted or unavailable. "
"Full details saved in cron output."
)

if "readtimeout" in lower or "timed out" in lower or "timeout" in lower:
return (
f"⚠️ Cron '{job_name}' failed: provider timeout. "
"Fallback chain was exhausted or unavailable. "
"Full details saved in cron output."
)
if "readtimeout" in lower or "timed out" in lower or "timeout" in lower:
return (
f"⚠️ Cron '{job_name}' failed: provider timeout. "
"Fallback chain was exhausted or unavailable. "
"Full details saved in cron output."
)

# Match authentication/authorization wording at a word boundary and the
# 401/403 status codes as whole tokens, so "oauth", "4015" and similar do
# not trip a misleading auth message.
if re.search(r"authenticat|authoriz", lower) or re.search(r"\b(401|403)\b", text):
return (
f"⚠️ Cron '{job_name}' failed: provider authentication error. "
"Full details saved in cron output."
)
# Match authentication/authorization wording at a word boundary and the
# 401/403 status codes as whole tokens, so "oauth", "4015" and similar do
# not trip a misleading auth message.
if re.search(r"authenticat|authoriz", lower) or re.search(r"\b(401|403)\b", text):
return (
f"⚠️ Cron '{job_name}' failed: provider authentication error. "
"Full details saved in cron output."
)

# Strip common exception wrappers and collapse provider payloads. Bound
# the input first so a multi-KB provider blob cannot slow the
Expand Down
30 changes: 30 additions & 0 deletions tests/cron/test_failure_summary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Behavioral coverage for cron delivery failure classification."""

import pytest

from cron.scheduler import _summarize_cron_failure_for_delivery


@pytest.mark.parametrize(
"output",
[
"PASS auth gate returns 401\nFAIL broken file link",
"PASS quota endpoint returns 429\nFAIL broken file link",
"PASS timeout handler test\nFAIL broken file link",
],
)
def test_no_agent_failure_does_not_use_provider_classifiers(output):
message = _summarize_cron_failure_for_delivery(
{"name": "verify", "no_agent": True}, output
)

assert "provider" not in message.lower()
assert "broken file link" in message


def test_agent_failure_still_classifies_provider_authentication():
message = _summarize_cron_failure_for_delivery(
{"name": "agent job", "no_agent": False}, "HTTP 401 unauthorized"
)

assert "provider authentication error" in message
Loading