Files
Bohan-J 2cd9ef8ffd fix(daemon): retire agent sessions that keep failing on resume (MUL-5503)
A conversation whose resume kept failing could only be recognised by
matching the provider's error text. That works for Anthropic's own
400 invalid_request_error shape and for the empty-message wordings
added since, but the compatible-gateway ecosystem has no contract on
error text: vendor codes live in the vendor's namespace, HTTP 400
covers both a poisoned transcript and plain misconfiguration, and any
of it can be reworded or localised at will.

So an unrecognised rejection meant "safe to resume" by omission, and
the (agent, issue) pair was bricked permanently — every later trigger
resumed the same dead conversation. Manual rerun did not help either:
that path resolves its session from the source task and applies the
same text-based predicate. Deleting the task's workdir on the host was
the only way out (GH #6143). This is the fourth report of this shape
after GH #5975, #6066 and #5760, each previously closed by adding
another pattern.

Conclude it from behaviour instead. Two consecutive non-transient
failures on one (agent, issue) pair with no success in between retire
the conversation, so the next run starts clean. No error text is
parsed, which is what makes it hold for a wording nobody has seen yet.

The first failure still resumes: an unrecognised error is not yet
evidence that the transcript is at fault, and restarting on every
first stumble would discard context whenever a provider hiccupped.
Transient reasons (daemon offline, rate limit, provider 5xx, queue
expiry) are skipped without resetting the count, so a flaky daemon can
neither trip the breaker nor mask a genuinely dead session.

Counting is keyed on (agent, issue) rather than session_id because ACP
backends may answer a resume with a different id, and a run that dies
early records none; keying on the id would restart the count in
exactly the case this exists for. Every id seen in the window is
retired together.

Decided server-side, in FailTask, ahead of the retry pre-compute and
the chat-pointer writes so both observe the upgraded reason. Daemons
upgrade on their own cadence, and the sibling fixes above have already
been reissued once for hosts that never picked them up.

Reuses the existing retired_session_id mechanism, so no migration.
Also closes two gaps the same behaviour would otherwise widen: the
manual-rerun path never consulted the retired-session set, and the
breaker would have dropped conversations silently — it now raises the
same PriorSessionResumeUnavailable disclosure as MUL-5305.

Scoped to issue tasks; chat sessions keep their existing handling.

Co-authored-by: multica-agent <github@multica.ai>
2026-07-31 14:21:52 +08:00
..