fix(cli): a WAF-blocked Kanban worker exits KANBAN_TERMINAL_PROVIDER_EXIT_CODE, not 1

upstream_blocked was in neither provider-reason set, so a worker hitting a User-Agent
firewall exited 1 and the dispatcher re-spawned it into the same wall until
kanban.failure_limit was spent (on base the same 403 classified auth and parked the card
after one spawn). A header change heals it, a retry never does: it is terminal.
This commit is contained in:
teknium1
2026-09-19 01:14:45 -07:00
committed by Teknium
parent 6f6ed01355
commit 64c703ee95
2 changed files with 17 additions and 4 deletions

4
cli.py
View File

@@ -4137,8 +4137,10 @@ _TRANSIENT_PROVIDER_REASONS = frozenset({
# ``KANBAN_TERMINAL_PROVIDER_EXIT_CODE`` so the dispatcher parks the card after ONE spawn with
# the provider's words as the reason, instead of re-spawning into the same wall until
# ``kanban.failure_limit`` is spent. ``billing`` stays transient: credit comes back.
# ``upstream_blocked`` (a WAF/CDN refusing the SDK's User-Agent) is terminal too: only a
# header change heals it, never a retry.
_TERMINAL_PROVIDER_REASONS = frozenset({
"auth", "auth_permanent", "model_not_found", "ssl_cert_verification",
"auth", "auth_permanent", "model_not_found", "ssl_cert_verification", "upstream_blocked",
})

View File

@@ -53,16 +53,27 @@ def test_dispatcher_spawned_worker_signals_a_provider_outage_not_a_protocol_viol
assert code == KANBAN_RATE_LIMIT_EXIT_CODE
@pytest.mark.parametrize("reason", ["auth", "auth_permanent", "model_not_found", "ssl_cert_verification"])
@pytest.mark.parametrize(
"reason", ["auth", "auth_permanent", "model_not_found", "ssl_cert_verification", "upstream_blocked"]
)
def test_dispatcher_spawned_worker_signals_a_terminal_provider_error(monkeypatch, reason):
"""A revoked credential / missing model cannot be retried into working: the worker says so
with EX_CONFIG so the dispatcher parks the card after one spawn. A person's run keeps 1."""
"""A revoked credential / missing model / WAF User-Agent block cannot be retried into working:
the worker says so with EX_CONFIG so the dispatcher parks the card after one spawn. A person's
run keeps 1."""
monkeypatch.setenv("HERMES_KANBAN_TASK", "t_abc123")
assert _run_non_quiet(monkeypatch, {"failed": True, "failure_reason": reason}) == KANBAN_TERMINAL_PROVIDER_EXIT_CODE
monkeypatch.delenv("HERMES_KANBAN_TASK")
assert _run_non_quiet(monkeypatch, {"failed": True, "failure_reason": reason}) == 1
def test_dispatcher_spawned_worker_keeps_a_plain_failure_at_one(monkeypatch):
"""Control: a task-level failure (or an unknown reason) is neither transient nor terminal —
the worker exits 1 and the dispatcher counts it against ``kanban.failure_limit`` as before."""
monkeypatch.setenv("HERMES_KANBAN_TASK", "t_abc123")
assert _run_non_quiet(monkeypatch, {"failed": True, "failure_reason": "some_unknown_reason"}) == 1
assert _run_non_quiet(monkeypatch, {"failed": True}) == 1
@pytest.mark.parametrize(
("turn_result", "expected"),
[