Recognize sign-in failures for every agent and fall through
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:
+34
-9
@@ -4,6 +4,8 @@ import { TextLineStream } from "jsr:@std/streams@1/text-line-stream";
|
||||
import {
|
||||
type CodexAuthentication,
|
||||
codexAuthentication,
|
||||
isClaudeAuthFailure,
|
||||
isCodexAuthFailure,
|
||||
persistCodexAuth,
|
||||
retryInvalidToken,
|
||||
stageCodexAuth,
|
||||
@@ -14,7 +16,9 @@ import {
|
||||
readProfileFile,
|
||||
} from "./isolation.ts";
|
||||
import {
|
||||
AgentAuthError,
|
||||
agentEnvironment,
|
||||
type ProfileFailure,
|
||||
runProfiles,
|
||||
sanitizedEnvironment,
|
||||
withProfile,
|
||||
@@ -22,6 +26,7 @@ import {
|
||||
import {
|
||||
collectOpenCodeOutput,
|
||||
openCodeArgs,
|
||||
openCodeError,
|
||||
parseOpenCodeEvents,
|
||||
} from "./opencode.ts";
|
||||
import {
|
||||
@@ -195,7 +200,12 @@ async function runClaude(
|
||||
stderr: "piped",
|
||||
}).spawn();
|
||||
let result:
|
||||
| { is_error?: boolean; result?: string; subtype: string }
|
||||
| {
|
||||
is_error?: boolean;
|
||||
result?: string;
|
||||
subtype: string;
|
||||
api_error_status?: number | null;
|
||||
}
|
||||
| undefined;
|
||||
let error = "";
|
||||
const copyError = claude.stderr.pipeThrough(new TextDecoderStream()).pipeTo(
|
||||
@@ -220,7 +230,13 @@ async function runClaude(
|
||||
}
|
||||
const [status] = await Promise.all([claude.status, copyError]);
|
||||
if (result?.is_error) {
|
||||
throw new Error(result.result ?? `claude ended with ${result.subtype}`);
|
||||
const failure = new Error(
|
||||
result.result ?? `claude ended with ${result.subtype}`,
|
||||
);
|
||||
if (isClaudeAuthFailure(result)) {
|
||||
throw new AgentAuthError("claude sign-in failed", { cause: failure });
|
||||
}
|
||||
throw failure;
|
||||
}
|
||||
if (result?.result === undefined) {
|
||||
throw new Error(
|
||||
@@ -257,12 +273,14 @@ async function codexDeviceLogin(
|
||||
status.then(() => true),
|
||||
new Promise<boolean>((resolve) => setTimeout(() => resolve(false), 100)),
|
||||
]);
|
||||
if (exited) throw new Error("AI bot login produced no instructions");
|
||||
if (exited) {
|
||||
throw new AgentAuthError("AI bot login produced no instructions");
|
||||
}
|
||||
}
|
||||
await postAuthorComment(withProfile(shown, profile));
|
||||
const [result] = await Promise.all([status, drained]);
|
||||
if (!result.success) {
|
||||
throw new Error(`codex login exited with ${result.code}`);
|
||||
throw new AgentAuthError(`codex login exited with ${result.code}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,7 +365,8 @@ async function runCodex(
|
||||
error += decoder.decode(chunk, { stream: true });
|
||||
}
|
||||
error += decoder.decode();
|
||||
return { status: await codex.status, error };
|
||||
// Codex echoes the prompt, whose comments may quote auth errors.
|
||||
return { status: await codex.status, error: error.replaceAll(prompt, "") };
|
||||
};
|
||||
const result = authentication.kind === "api-key"
|
||||
? await attempt()
|
||||
@@ -356,9 +375,13 @@ async function runCodex(
|
||||
await codexDeviceLogin(profile, environment);
|
||||
});
|
||||
if (!result.status.success) {
|
||||
throw new Error(
|
||||
const failure = new Error(
|
||||
result.error.trim() || `codex exited with ${result.status.code}`,
|
||||
);
|
||||
if (isCodexAuthFailure(result.error)) {
|
||||
throw new AgentAuthError("codex sign-in failed", { cause: failure });
|
||||
}
|
||||
throw failure;
|
||||
}
|
||||
return outputPath;
|
||||
}
|
||||
@@ -393,7 +416,8 @@ async function runOpenCode(
|
||||
const [status, streams] = await Promise.all([process.status, output]);
|
||||
const error = streams.stderr.trim();
|
||||
if (!status.success) {
|
||||
throw new Error(error || `opencode exited with ${status.code}`);
|
||||
throw openCodeError(streams.stdout) ??
|
||||
new Error(error || `opencode exited with ${status.code}`);
|
||||
}
|
||||
return parseOpenCodeEvents(streams.stdout);
|
||||
}
|
||||
@@ -455,14 +479,15 @@ async function writeOutcome(outcome: RuntimeOutcome): Promise<void> {
|
||||
await Deno.chmod(MANIFEST.outcomeFile, 0o600);
|
||||
}
|
||||
|
||||
const failures: ProfileFailure[] = [];
|
||||
try {
|
||||
await configureGitAuthor();
|
||||
await writeOutcome({
|
||||
ok: true,
|
||||
...await runProfiles(MANIFEST.profiles, runProfile),
|
||||
...await runProfiles(MANIFEST.profiles, runProfile, failures),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
await writeOutcome({ ok: false });
|
||||
await writeOutcome({ ok: false, failures });
|
||||
throw error;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user