Skip to content

Commit 3aa770f

Browse files
committed
fix: classify codex deactivated workspace codes
1 parent a488cbe commit 3aa770f

4 files changed

Lines changed: 34 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ Docs: https://docs.openclaw.ai
5151
- CLI/perf: serve `doctor`, `gateway`, `models`, and `plugins` parent help from startup metadata so common subcommand help avoids full CLI program construction. (#84786) Thanks @frankekn.
5252
- Codex/Lossless: keep context-engine history on the canonical run session when Telegram DMs use per-peer runtime policy keys. Fixes #84936. (#84954) Thanks @neeravmakwana.
5353
- Auth/OAuth: skip the refresh adapter when a stored OAuth credential has no refresh token so agent turns fail fast on missing-key instead of waiting on the 120s refresh timeout. Thanks @romneyda.
54+
- Codex/failover: classify `deactivated_workspace` as a permanent auth failure so configured fallback models can advance when a Codex workspace is deactivated. (#55893) Thanks @litang9.
5455

5556
## 2026.5.20
5657

@@ -858,7 +859,6 @@ Docs: https://docs.openclaw.ai
858859
- CLI: route `plugins list --json` through the parsed command fast path and cover it in response budgets so plugin JSON inventory avoids full CLI registration work.
859860
- Control UI/Overview: render recent session rows through the shared session display resolver so label/displayName priority, key-equivalent labels, and channel fallbacks stay consistent with the chat selector. (#50696) Thanks @Maple778 and @BunsDev.
860861
- Gateway/network: keep OpenClaw-installed undici dispatchers on HTTP/1.1 and treat destroyed HTTP/2 session errors as recoverable network teardown, preventing `ERR_HTTP2_INVALID_SESSION` from crashing active gateway turns. Fixes #81627. (#81838) Thanks @joshavant.
861-
- Codex/failover: classify `deactivated_workspace` as a permanent auth failure so configured fallback models can advance when a Codex workspace is deactivated.
862862
- Memory/daily-files: widen the daily-memory file matcher used by Dreaming, rem-backfill, rem-harness, the doctor sweep, and short-term promotion so `memory/YYYY-MM-DD-<slug>.md` files written by the bundled session-memory hook (and any future slugged variants) are discovered alongside the date-only `memory/YYYY-MM-DD.md` shape. Date extraction still uses the leading `YYYY-MM-DD` capture group, so per-day ingestion/promotion semantics are unchanged for existing date-only files; slugged files now flow through the same paths instead of being silently skipped. Fixes #69536. Thanks @jack-stormentswe.
863863
- macOS/Gateway: fail managed LaunchAgent stop and restart when the configured gateway port remains busy after cleanup instead of reporting success while a listener survives. Fixes #73132. Thanks @BunsDev.
864864
- Telegram: reuse the sticky IPv4 Bot API transport for periodic getMe health checks, so IPv4-working hosts with broken IPv6 egress stop logging repeated probe timeouts. Fixes #76852. (#76856) Thanks @SymbolStar.

src/agents/failover-error.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,28 @@ describe("failover-error", () => {
950950
expect(resolveFailoverReasonFromError({ message: "deactivated workspace" })).toBe(
951951
"auth_permanent",
952952
);
953+
expect(resolveFailoverReasonFromError({ code: "deactivated_workspace" })).toBe(
954+
"auth_permanent",
955+
);
956+
expect(
957+
resolveFailoverReasonFromError({
958+
detail: { code: "deactivated_workspace" },
959+
}),
960+
).toBe("auth_permanent");
961+
expect(
962+
resolveFailoverReasonFromError({
963+
status: 403,
964+
message: "Forbidden",
965+
detail: { code: "deactivated_workspace" },
966+
}),
967+
).toBe("auth_permanent");
968+
expect(
969+
resolveFailoverReasonFromError({
970+
status: 400,
971+
message: "Bad request",
972+
detail: { code: "deactivated_workspace" },
973+
}),
974+
).toBe("auth_permanent");
953975
});
954976

955977
it("403 OpenRouter 'Key limit exceeded' returns billing (model fallback trigger)", () => {

src/agents/failover-error.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,11 @@ function readDirectErrorCode(err: unknown): string | undefined {
151151
const trimmed = directCode.trim();
152152
return trimmed ? trimmed : undefined;
153153
}
154+
const detailCode = (err as { detail?: { code?: unknown } }).detail?.code;
155+
if (typeof detailCode === "string") {
156+
const trimmed = detailCode.trim();
157+
return trimmed ? trimmed : undefined;
158+
}
154159
const status = (err as { status?: unknown }).status;
155160
if (typeof status !== "string" || /^\d+$/.test(status)) {
156161
return undefined;

src/agents/pi-embedded-helpers/errors.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,8 @@ function classifyFailoverReasonFromCode(raw: string | undefined): FailoverReason
758758
case "THROTTLINGEXCEPTION":
759759
case "THROTTLING_EXCEPTION":
760760
return "rate_limit";
761+
case "DEACTIVATED_WORKSPACE":
762+
return "auth_permanent";
761763
case "OVERLOADED":
762764
case "OVERLOADED_ERROR":
763765
return "overloaded";
@@ -919,6 +921,10 @@ export function classifyFailoverSignal(signal: FailoverSignal): FailoverClassifi
919921
const messageClassification = signal.message
920922
? classifyFailoverClassificationFromMessage(signal.message, signal.provider)
921923
: null;
924+
const codeReason = classifyFailoverReasonFromCode(signal.code);
925+
if (codeReason === "auth_permanent") {
926+
return toReasonClassification(codeReason);
927+
}
922928
const statusClassification = classifyFailoverClassificationFromHttpStatus(
923929
inferredStatus,
924930
signal.message,
@@ -929,7 +935,6 @@ export function classifyFailoverSignal(signal: FailoverSignal): FailoverClassifi
929935
if (statusClassification) {
930936
return statusClassification;
931937
}
932-
const codeReason = classifyFailoverReasonFromCode(signal.code);
933938
if (codeReason) {
934939
return toReasonClassification(codeReason);
935940
}

0 commit comments

Comments
 (0)