A rejected API key made an agent run look like it had simply run out of steps: every request was retried, the budget drained, and the report blamed the iteration limit.
Fixed
- A 4xx from the provider was retried until the iteration budget ran out.
agentChat threw a bare Error for any failed HTTP response, so the retry loop's "never retry a 4xx" branch — which tests err instanceof ApiError && err.status — never applied. An expired key was retried once per iteration and the run then reported Exceeded maximum of N iterations: the one explanation with nothing to do with the cause. A run that used to burn twenty-five requests over six minutes now stops on the first, saying 401.
review --fixnever loaded the API keys.getApiKeyis synchronous and
reads a cache that only loadAllApiKeys fills; it does not consult the environment itself. The fix agent started without that call, so every request went out with an empty bearer token. In CI this looked exactly like a model that would not do the work, with a perfectly valid key sitting in the environment the whole time.
- A fix that changed nothing said nothing about why. The summary reported
what did not happen and never what did. It now counts the agent's tool calls, separates failures from successes, and quotes the first failure — usually a refusal, and usually the entire explanation.
Changed
src/utils/agentChat.tsis a text file again. It contained a literal NUL
byte — a deliberate separator written as a raw byte rather than \u0000 — which made grep, file and diff viewers treat the whole file as binary and silently skip it. Same behaviour, same separator, now searchable.