Recognize sign-in failures for every agent and fall through
Agents / agent (pull_request) Has been cancelled
Check / deno (pull_request) Failing after 35s

Codex re-runs device login on revoked, expired, or reused sessions and
token_revoked/401 errors, not only invalid_refresh_token. Claude and
OpenCode credential rejections are classified too. Sign-in failures fall
through to the next profile, and the failure comment names each tried
profile with a fixed reason label.
This commit is contained in:
Danny Kim
2026-10-12 11:38:21 +09:00
parent ea20f6d629
commit 3565988249
11 changed files with 411 additions and 36 deletions
+47 -9
View File
@@ -10,7 +10,7 @@ const context: PostContext = {
};
async function fixture(
source: { ok: false } | { ok: true; body: string },
source: Extract<RuntimeOutcome, { ok: false }> | { ok: true; body: string },
review?: string,
anchors?: unknown,
): Promise<{
@@ -138,8 +138,15 @@ Deno.test("retries a rejected inline review without anchors", async () => {
assert(await missing(result.runtimeDir));
});
Deno.test("reports a generic agent failure and keeps the action failed", async () => {
const result = await fixture({ ok: false });
Deno.test("reports each tried profile and keeps the action failed", async () => {
const result = await fixture({
ok: false,
failures: [
{ type: "codex", model: "gpt-test", reason: "sign-in failed" },
{ type: "claude", model: "claude-test", reason: "usage exhausted" },
{ type: "opencode", model: "openrouter/test", reason: "failed" },
],
});
const bodies: unknown[] = [];
await assertRejects(
@@ -153,14 +160,47 @@ Deno.test("reports a generic agent failure and keeps the action failed", async (
);
assertEquals(bodies, [{
body: "AI bot failed.\n\n> Profile: none · all profiles failed",
body: "AI bot failed.\n\n> Tried: `codex` · `gpt-test` (sign-in failed), " +
"`claude` · `claude-test` (usage exhausted), " +
"`opencode` · `openrouter/test` (failed)",
}]);
assert(!JSON.stringify(bodies).includes("reviewer-secret"));
assert(await missing(result.runtimeDir));
});
Deno.test("drops failure entries that are not known labels", async () => {
const result = await fixture({ ok: false, failures: [] });
await Deno.writeTextFile(
result.outcomeFile,
JSON.stringify({
ok: false,
failures: [
{ type: "codex", model: "gpt-test", reason: "token sk-secret leaked" },
{ type: "shell", model: "x", reason: "failed" },
{ type: "claude", model: "claude-test", reason: "sign-in failed" },
],
}),
);
const bodies: unknown[] = [];
await assertRejects(
() =>
postRuntime(result.manifestPath, context, (_url, init) => {
bodies.push(JSON.parse(String(init?.body)));
return Promise.resolve(new Response(null, { status: 201 }));
}),
Error,
"agent phase failed",
);
assertEquals(bodies, [{
body:
"AI bot failed.\n\n> Tried: `claude` · `claude-test` (sign-in failed)",
}]);
});
Deno.test("reports a generic failure when the runner wrote no outcome", async () => {
const result = await fixture({ ok: false });
const result = await fixture({ ok: false, failures: [] });
await Deno.remove(result.outcomeFile);
const bodies: unknown[] = [];
@@ -174,9 +214,7 @@ Deno.test("reports a generic failure when the runner wrote no outcome", async ()
"agent phase failed",
);
assertEquals(bodies, [{
body: "AI bot failed.\n\n> Profile: none · all profiles failed",
}]);
assertEquals(bodies, [{ body: "AI bot failed.\n\n> Profile: none" }]);
assert(await missing(result.runtimeDir));
});
@@ -252,7 +290,7 @@ Deno.test("reports an attributed failure when a pull request review is missing",
);
assertEquals(bodies, [{
body: "AI bot failed.\n\n> Profile: none · all profiles failed",
body: "AI bot failed.\n\n> Tried: `codex` · `gpt-test` (failed)",
}]);
assert(await missing(result.runtimeDir));
});