fix(executors): rotate on upstream 400 empty-body rejections (opencode) (#11158)

Validated on the combined batch board over tip 92ef3c71: static gates clean (changelog, file-size, complexity 2624<=2774, cognitive 1182<=1223, dead-code 411<=416), typecheck:core clean, focused tests green.

Empty-envelope 400 (no error field, empty content, finish_reason null) now rotates/retries instead of propagating as success; 200/streaming path never buffered; real-error 400s untouched. account-rotation + new rotation suite 34/34 on the board. Thank you @maxmad64bis!
This commit is contained in:
Dizzle
2026-08-23 02:51:22 +02:00
committed by GitHub
parent b3844550d0
commit 1058426120
5 changed files with 638 additions and 5 deletions

View File

@@ -0,0 +1 @@
- **fix(executors):** OpencodeExecutor rotates (or retries once on a single-account direct path) on upstream 400 empty-body rejections — malformed completion envelopes with no error field were propagated as success and killed client sessions. Bounded +1 attempt per request; body reads are conditioned on status 400 so successful/streaming responses are never buffered. 400s carrying an error field keep propagating immediately.

View File

@@ -1,7 +1,7 @@
/**
* Shared multi-account rotation mechanics for noauth executors that round-robin
* across several "accounts" (fingerprints), each with an optional dedicated
* proxy — currently `OpencodeExecutor` and `MimocodeExecutor`.
* proxy — currently `OpencodeExecutor`.
*
* Extracted after both executors independently implemented the same
* pickAccount/markCooldown/markSuccess skeleton with the same exponential
@@ -120,3 +120,58 @@ export function maskAccountId(fingerprint: string): string {
export function isNetworkErrorRotatable(account: RotatableAccount): boolean {
return account.proxy !== null;
}
/**
* Detect an *empty* upstream rejection: a 400 whose body carries no usable
* completion — the kind `OpencodeExecutor` must rotate/retry on instead of
* propagating as a fatal success.
*
* Signature is deliberately strict and scoped to the observed malformed
* envelope (`choices[0].message` with no `error`, no real `content`,
* `finish_reason: null`):
* - status must be exactly 400 (anything else → false);
* - body must parse and contain a `choices` array with at least one entry
* holding a `message` object;
* - an `error` field (present or empty) → false, so genuine 400s keep
* propagating immediately (#10460 precedent: classify by signature before
* rotating);
* - `tool_calls` / `reasoning_content` → false (real content);
* - `message.content` absent / null / "" → eligible; any other value
* (non-empty text, number, block array…) → false (conservative);
* - a literal `finish_reason` (not null) → false (a completed, if empty, turn).
*
* Does NOT reuse `detectMalformedNonStream` (diagnostics.ts): that classifier
* also flags `{error:{…}}` bodies as `empty_choices`, which would rotate on
* real errors — a false-positive class with a history here.
*/
export function isEmptyUpstreamRejection(status: number, bodyText: string): boolean {
if (status !== 400) return false;
let parsed: unknown;
try {
parsed = JSON.parse(bodyText);
} catch {
return false;
}
const choices = (parsed as { choices?: unknown })?.choices;
if (!Array.isArray(choices) || choices.length === 0) return false;
const first = choices[0] as { message?: unknown; finish_reason?: unknown };
if (typeof first !== "object" || first === null) return false;
const rawMessage = (first as { message?: unknown }).message;
if (typeof rawMessage === "undefined" || rawMessage === null) return false;
if (typeof parsed !== "object" || parsed === null) return false;
if ("error" in (parsed as Record<string, unknown>)) return false;
const msg = rawMessage as Record<string, unknown>;
if ("tool_calls" in msg) return false;
if ("reasoning_content" in msg) return false;
const content = msg.content;
if (content !== undefined && content !== null && content !== "") return false;
if (first.finish_reason !== null && first.finish_reason !== undefined) return false;
return true;
}
/** Best-effort extraction of the upstream `chatcmpl_*` id from a response body,
* for observability logging. Returns `"unknown"` when absent or unparseable. */
export function extractChatcmplId(bodyText: string): string {
const match = /"id"\s*:\s*"(chatcmpl_[^"]+)"/.exec(bodyText);
return match ? match[1] : "unknown";
}

View File

@@ -15,6 +15,8 @@ import {
markSuccess as markAccountSuccess,
maskAccountId,
isNetworkErrorRotatable,
isEmptyUpstreamRejection,
extractChatcmplId,
} from "./accountRotation.ts";
import { isNetworkRotationSharedEgressGuardEnabled } from "@/shared/utils/featureFlags";
@@ -253,14 +255,41 @@ export class OpencodeExecutor extends BaseExecutor {
try {
this.syncAccountsFromCredentials(input.credentials);
const { log } = input;
const hasProxies = this.accounts.some((a) => a.proxy !== null);
// Fast path: no multi-account proxy wiring configured → original behavior.
// Fast path: no multi-account proxy wiring configured → original behavior,
// plus exactly ONE bounded retry when the upstream answers a 400 empty
// rejection (same predicate and logging as the rotation loop). Everything
// else passes untouched: this path deliberately preserves BaseExecutor's
// intra-URL 429 retries (no skipUpstreamRetry here).
if (this.accounts.length === 1 && !hasProxies) {
return await super.execute(input);
const single = (await super.execute(input)) as HttpExecuteResult;
if (single.response.status === 400) {
let bodyText: string | null = null;
try {
bodyText = await single.response.clone().text();
} catch {
log?.debug?.("OPENCODE", "body read failed on direct account");
}
if (bodyText !== null) {
if (isEmptyUpstreamRejection(400, bodyText)) {
const chatcmplId = extractChatcmplId(bodyText);
log?.warn?.(
"OPENCODE",
`upstream empty rejection on direct account (${chatcmplId}), retrying once…`
);
return await super.execute(input);
}
log?.debug?.(
"OPENCODE",
"400 without error field, signature not matched on direct account — observing"
);
}
}
return single;
}
const { log } = input;
// This loop only ever dispatches through super.execute() (the HTTP request
// path), which always resolves the object-shaped arm of ExecutorExecuteResult
// — the bare-Response arm belongs to web/scraping executors only (base.ts:290).
@@ -277,8 +306,13 @@ export class OpencodeExecutor extends BaseExecutor {
// network call, but proxied accounts (independent egress) are still
// tried normally.
let sharedEgressDown = false;
// Bounded extra attempts for empty upstream rejections: +1 for a single
// account (retry the same one), none for a multi-account fleet (rotation
// through the accounts is the retry). Avoids an unbounded loop on a
// persistently malformed upstream.
const emptyRejectionBudget = this.accounts.length === 1 ? 1 : 0;
for (let attempt = 0; attempt < this.accounts.length; attempt++) {
for (let attempt = 0; attempt < this.accounts.length + emptyRejectionBudget; attempt++) {
const account = this.pickAccount();
const masked = maskAccountId(account.fingerprint);
@@ -354,6 +388,34 @@ export class OpencodeExecutor extends BaseExecutor {
continue;
}
// Empty upstream rejection (malformed 400: no error field, no real
// content, finish_reason null — see isEmptyUpstreamRejection). Rotate/
// retry instead of propagating it as a fatal success: the observed
// envelope was marking subagent sessions as failed. Read the body ONLY
// for a 400 (never a 200/streaming — that would buffer the good path);
// classify, log, and continue. Neitheries markCooldown nor markSuccess:
// the failure is upstream's, not this account's.
if (status === 400) {
let bodyText: string | null = null;
try {
bodyText = await result.response.clone().text();
} catch {
log?.debug?.("OPENCODE", "body read failed on empty rejection check");
}
if (bodyText !== null && isEmptyUpstreamRejection(400, bodyText)) {
const chatcmplId = extractChatcmplId(bodyText);
log?.warn?.(
"OPENCODE",
`upstream empty rejection on account ${masked} (${chatcmplId}), rotating to next…`
);
continue;
}
// A 400 carrying a real error (or non-empty content): propagate
// immediately, untouched — same as before this change.
this.markSuccess(account);
return result;
}
this.markSuccess(account);
return result;
}

View File

@@ -7,6 +7,8 @@ import {
markSuccess,
maskAccountId,
isNetworkErrorRotatable,
isEmptyUpstreamRejection,
extractChatcmplId,
type RotatableAccount,
} from "../../open-sse/executors/accountRotation.ts";
@@ -114,3 +116,100 @@ describe("accountRotation", () => {
assert.strictEqual(isNetworkErrorRotatable(withoutProxy), false);
});
});
describe("isEmptyUpstreamRejection", () => {
it("matches the observed malformed completion envelope (no error field, empty content, null finish_reason)", () => {
const observed =
'{"id":"chatcmpl_44fn2g6e7kk","object":"chat.completion","created":1787419957,"model":"muse-spark-1.2-contributor-free","choices":[{"index":0,"message":{"role":"assistant"},"finish_reason":null}]}';
assert.strictEqual(isEmptyUpstreamRejection(400, observed), true);
});
it("does not match a non-400 status", () => {
const observed =
'{"id":"chatcmpl_44fn2g6e7kk","object":"chat.completion","created":1787419957,"model":"muse-spark-1.2-contributor-free","choices":[{"index":0,"message":{"role":"assistant"},"finish_reason":null}]}';
assert.strictEqual(isEmptyUpstreamRejection(200, observed), false);
assert.strictEqual(isEmptyUpstreamRejection(429, observed), false);
assert.strictEqual(isEmptyUpstreamRejection(502, observed), false);
});
it("does not match when an error field is present", () => {
const withError = JSON.stringify({
error: { message: "bad request", type: "invalid_request_error" },
});
assert.strictEqual(isEmptyUpstreamRejection(400, withError), false);
const emptyError = JSON.stringify({ error: {} });
assert.strictEqual(isEmptyUpstreamRejection(400, emptyError), false);
});
it("does not match when content is non-empty or tool_calls present", () => {
const nonEmpty = JSON.stringify({
choices: [{ message: { role: "assistant", content: "hi" }, finish_reason: "stop" }],
});
assert.strictEqual(isEmptyUpstreamRejection(400, nonEmpty), false);
const toolCalls = JSON.stringify({
choices: [
{ message: { role: "assistant", tool_calls: [{ id: "x" }] }, finish_reason: "tool_calls" },
],
});
assert.strictEqual(isEmptyUpstreamRejection(400, toolCalls), false);
});
it("does not match when content is a non-string non-null value (number, block array)", () => {
const numericContent = JSON.stringify({
choices: [{ message: { role: "assistant", content: 123 }, finish_reason: null }],
});
assert.strictEqual(
isEmptyUpstreamRejection(400, numericContent),
false,
"non-string non-null content is not eligible"
);
const reasoningContent = JSON.stringify({
choices: [
{ message: { role: "assistant", reasoning_content: "thinking" }, finish_reason: null },
],
});
assert.strictEqual(isEmptyUpstreamRejection(400, reasoningContent), false);
});
it("does not match when choices or message are absent", () => {
const noChoices = JSON.stringify({ id: "chatcmpl_x", model: "muse" });
assert.strictEqual(isEmptyUpstreamRejection(400, noChoices), false);
const noMessage = JSON.stringify({ choices: [{ finish_reason: null }] });
assert.strictEqual(isEmptyUpstreamRejection(400, noMessage), false);
});
it("does not match when finish_reason is a literal value (not null)", () => {
const stopReason = JSON.stringify({
choices: [{ message: { role: "assistant" }, finish_reason: "stop" }],
});
assert.strictEqual(isEmptyUpstreamRejection(400, stopReason), false);
});
it("matches an empty string content (treated as eligible)", () => {
const emptyContent = JSON.stringify({
choices: [{ message: { role: "assistant", content: "" }, finish_reason: null }],
});
assert.strictEqual(isEmptyUpstreamRejection(400, emptyContent), true);
});
it("returns false for unparseable JSON rather than throwing", () => {
assert.strictEqual(isEmptyUpstreamRejection(400, "not json"), false);
assert.strictEqual(isEmptyUpstreamRejection(400, ""), false);
});
});
describe("extractChatcmplId", () => {
it("extracts the chatcmpl id from an observed envelope", () => {
const observed =
'{"id":"chatcmpl_44fn2g6e7kk","object":"chat.completion","created":1787419957,"model":"muse-spark-1.2-contributor-free","choices":[{"index":0,"message":{"role":"assistant"},"finish_reason":null}]}';
assert.strictEqual(extractChatcmplId(observed), "chatcmpl_44fn2g6e7kk");
});
it("falls back to 'unknown' when no id is present", () => {
assert.strictEqual(extractChatcmplId("{choices:[]}"), "unknown");
assert.strictEqual(extractChatcmplId(""), "unknown");
assert.strictEqual(extractChatcmplId("not json"), "unknown");
});
});

View File

@@ -0,0 +1,416 @@
import { describe, it, beforeEach, afterEach, before, after } from "node:test";
import assert from "node:assert";
import net from "node:net";
import { OpencodeExecutor } from "../../open-sse/executors/opencode.ts";
import type { ExecutorLog, ProviderCredentials } from "../../open-sse/executors/base.ts";
import { resolveProxyForRequest } from "../../open-sse/utils/proxyFetch.ts";
import {
isEmptyUpstreamRejection,
extractChatcmplId,
} from "../../open-sse/executors/accountRotation.ts";
/**
* Empty-upstream-rejection rotation (#design opencode-empty-rejection-rotation).
*
* An upstream 400 whose body carries no usable completion (the observed malformed
* envelope: `choices[0].message` with no error field, no real content,
* `finish_reason: null`) must be rotated/retried instead of propagated as a fatal
* success — that was killing subagent sessions. These tests pin the wiring:
*
* 1. A 400 empty rejection rotates to the next account (and its proxy).
* 2. The retry budget is bounded: +1 attempt for a single account, exactly N
* for an N-account all-empty run (propagate the last 400, never loop forever).
* 3. A 400 carrying a real error field (or non-empty content) still propagates
* immediately — no cooldown, no success, no rotation.
* 4. The 200/success path is never cloned or read (anti-bufferisation).
*
* The dispatch layer is mocked by stubbing globalThis.fetch (exactly what the
* #4954 proxy integration test does). Three throwaway TCP listeners stand in for
* the per-account proxies so runWithProxyContext's reachability probe passes.
*/
const log: ExecutorLog = { debug() {}, info() {}, warn() {}, error() {} };
const ACCOUNT_A = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
const ACCOUNT_B = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
const ACCOUNT_C = "cccccccccccccccccccccccccccccccc";
const EMPTY_BODY =
'{"id":"chatcmpl_44fn2g6e7kk","object":"chat.completion","created":1787419957,"model":"muse-spark-1.2-contributor-free","choices":[{"index":0,"message":{"role":"assistant"},"finish_reason":null}]}';
const ERROR_BODY = JSON.stringify({
error: { message: "bad request", type: "invalid_request_error" },
});
let serverA: net.Server;
let serverB: net.Server;
let serverC: net.Server;
let portA = 0;
let portB = 0;
let portC = 0;
function listen(server: net.Server): Promise<number> {
return new Promise((resolve) => {
server.listen(0, "127.0.0.1", () => {
resolve((server.address() as net.AddressInfo).port);
});
});
}
before(async () => {
serverA = net.createServer((s) => s.destroy());
serverB = net.createServer((s) => s.destroy());
serverC = net.createServer((s) => s.destroy());
portA = await listen(serverA);
portB = await listen(serverB);
portC = await listen(serverC);
});
after(() => {
serverA?.close();
serverB?.close();
serverC?.close();
});
function portFor(fp: string): number {
if (fp === ACCOUNT_A) return portA;
if (fp === ACCOUNT_B) return portB;
return portC;
}
/** `fingerprints` accounts; `proxied` is the subset that get a dedicated proxy
* (defaults to all). A proxy-less account shares the default egress. */
function credentialsFor(
fingerprints: string[],
proxied: string[] = [...fingerprints]
): ProviderCredentials {
return {
apiKey: null,
accessToken: null,
connectionId: "noauth",
providerSpecificData: {
fingerprints,
...(proxied.length > 0 && {
accountProxies: proxied.map((fp) => ({
fingerprint: fp,
proxy: { type: "http", host: "127.0.0.1", port: portFor(fp) },
})),
}),
},
};
}
/** A Response subclass that counts clone() so we can assert the executor never
* buffers a 200/streaming response. Note: `clone()` returns a plain Response, so
* only `clone()` is reliably counted (a read on the clone hits the native
* method, not this override) — counting clones is the meaningful invariant. */
class SpyResponse extends Response {
static clones = 0;
clone(): Response {
SpyResponse.clones++;
return super.clone();
}
}
interface PlanStep {
status: number;
body?: string;
throw?: Error;
}
describe("OpencodeExecutor empty-rejection rotation", () => {
let originalFetch: typeof globalThis.fetch;
let observed: Array<{ source: string; host: string | null; port: string | null }>;
const GUARD_FLAG = "NETWORK_ROTATION_SHARED_EGRESS_GUARD";
let savedGuardFlag: string | undefined;
beforeEach(() => {
originalFetch = globalThis.fetch;
observed = [];
SpyResponse.clones = 0;
savedGuardFlag = process.env[GUARD_FLAG];
delete process.env[GUARD_FLAG];
});
afterEach(() => {
globalThis.fetch = originalFetch;
if (savedGuardFlag === undefined) delete process.env[GUARD_FLAG];
else process.env[GUARD_FLAG] = savedGuardFlag;
});
function installFetch(plan: PlanStep[]) {
let call = 0;
globalThis.fetch = (async (input: RequestInfo | URL) => {
const url =
typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
const resolved = resolveProxyForRequest(url);
observed.push({
source: resolved.source,
host: resolved.proxyUrl ? new URL(resolved.proxyUrl).hostname : null,
port: resolved.proxyUrl ? new URL(resolved.proxyUrl).port : null,
});
const step = plan[Math.min(call, plan.length - 1)];
call++;
if (step.throw) throw step.throw;
return new SpyResponse(step.body ?? JSON.stringify({ ok: step.status === 200 }), {
status: step.status,
headers: { "Content-Type": "application/json" },
});
}) as typeof globalThis.fetch;
}
/**
* Launches the executor. Asserts the predicate itself behaves (regression guard
* for the design's signature — the wiring tests below depend on it).
*/
it("predicate matches the observed envelope and rejects real errors", () => {
assert.strictEqual(isEmptyUpstreamRejection(400, EMPTY_BODY), true);
assert.strictEqual(isEmptyUpstreamRejection(200, EMPTY_BODY), false);
assert.strictEqual(isEmptyUpstreamRejection(400, ERROR_BODY), false);
assert.strictEqual(extractChatcmplId(EMPTY_BODY), "chatcmpl_44fn2g6e7kk");
});
it("rotates to the next account on an empty 400 rejection (loop)", async () => {
const exec = new OpencodeExecutor("opencode-zen");
installFetch([{ status: 400, body: EMPTY_BODY }, { status: 200 }]);
const result = await exec.execute({
model: "deepseek-v4-flash-free",
body: { messages: [{ role: "user", content: "hi" }], stream: false },
stream: false,
signal: null,
credentials: credentialsFor([ACCOUNT_A, ACCOUNT_B]),
log,
});
assert.strictEqual(
(result as { response: Response }).response.status,
200,
"must rotate past the empty 400"
);
assert.ok(observed.length >= 2, "should have dispatched on a second account");
assert.ok(
observed.some((o) => o.port === String(portA)),
"first attempt on account A"
);
assert.ok(
observed.some((o) => o.port === String(portB)),
"rotated attempt on account B"
);
});
it("caps an all-empty N-account run at N attempts and propagates the last 400 intact", async () => {
const exec = new OpencodeExecutor("opencode-zen");
installFetch([
{ status: 400, body: EMPTY_BODY },
{ status: 400, body: EMPTY_BODY },
{ status: 400, body: EMPTY_BODY },
{ status: 200 },
]);
const result = await exec.execute({
model: "deepseek-v4-flash-free",
body: { messages: [{ role: "user", content: "hi" }], stream: false },
stream: false,
signal: null,
credentials: credentialsFor([ACCOUNT_A, ACCOUNT_B, ACCOUNT_C]),
log,
});
assert.strictEqual(
(result as { response: Response }).response.status,
400,
"must propagate the last empty 400"
);
assert.strictEqual(observed.length, 3, "must NOT exceed N attempts (no infinite loop)");
assert.ok(SpyResponse.clones >= 1, "the empty 400 path must read the body to classify it");
const propagated = await (result as { response: Response }).response.clone().text();
assert.strictEqual(propagated, EMPTY_BODY, "propagated 400 body must stay intact");
for (const p of observed) {
assert.strictEqual(p.source, "context", "every dispatch must egress through a proxy context");
}
});
it("retries the same proxied account once when it is the only account", async () => {
const exec = new OpencodeExecutor("opencode-zen");
installFetch([
{ status: 400, body: EMPTY_BODY },
{ status: 400, body: EMPTY_BODY },
]);
const result = await exec.execute({
model: "deepseek-v4-flash-free",
body: { messages: [{ role: "user", content: "hi" }], stream: false },
stream: false,
signal: null,
credentials: credentialsFor([ACCOUNT_A]),
log,
});
assert.strictEqual((result as { response: Response }).response.status, 400);
assert.strictEqual(observed.length, 2, "exactly one bounded retry on the sole account");
assert.ok(
observed.every((o) => o.port === String(portA)),
"both attempts egress through the single account's proxy"
);
});
it("coexists with 429 rotation and 200 success in the same request", async () => {
const exec = new OpencodeExecutor("opencode-zen");
installFetch([{ status: 429 }, { status: 400, body: EMPTY_BODY }, { status: 200 }]);
const result = await exec.execute({
model: "deepseek-v4-flash-free",
body: { messages: [{ role: "user", content: "hi" }], stream: false },
stream: false,
signal: null,
credentials: credentialsFor([ACCOUNT_A, ACCOUNT_B, ACCOUNT_C]),
log,
});
assert.strictEqual(
(result as { response: Response }).response.status,
200,
"final response should succeed"
);
assert.strictEqual(observed.length, 3, "429 + empty-400 + success across three accounts");
assert.ok(
observed.some((o) => o.port === String(portA)),
"account A (429)"
);
assert.ok(
observed.some((o) => o.port === String(portB)),
"account B (empty 400)"
);
assert.ok(
observed.some((o) => o.port === String(portC)),
"account C (200)"
);
});
it("propagates a 400 carrying an error field immediately (no rotation)", async () => {
const exec = new OpencodeExecutor("opencode-zen");
installFetch([{ status: 400, body: ERROR_BODY }]);
const result = await exec.execute({
model: "deepseek-v4-flash-free",
body: { messages: [{ role: "user", content: "hi" }], stream: false },
stream: false,
signal: null,
credentials: credentialsFor([ACCOUNT_A, ACCOUNT_B]),
log,
});
assert.strictEqual(
(result as { response: Response }).response.status,
400,
"real error 400 must propagate"
);
assert.strictEqual(observed.length, 1, "must NOT rotate on a genuine error 400");
});
it("never clones or reads the body of a 200 via the loop", async () => {
const exec = new OpencodeExecutor("opencode-zen");
installFetch([{ status: 200 }, { status: 200 }, { status: 200 }]);
const result = await exec.execute({
model: "deepseek-v4-flash-free",
body: { messages: [{ role: "user", content: "hi" }], stream: false },
stream: false,
signal: null,
credentials: credentialsFor([ACCOUNT_A, ACCOUNT_B, ACCOUNT_C]),
log,
});
assert.strictEqual((result as { response: Response }).response.status, 200);
assert.strictEqual(SpyResponse.clones, 0, "loop 200 must never be cloned");
});
it("retries once via the fast path when a direct account answers an empty 400", async () => {
const exec = new OpencodeExecutor("opencode-zen");
installFetch([
{ status: 400, body: EMPTY_BODY },
{ status: 400, body: EMPTY_BODY },
]);
const result = await exec.execute({
model: "deepseek-v4-flash-free",
body: { messages: [{ role: "user", content: "hi" }], stream: false },
stream: false,
signal: null,
credentials: credentialsFor([ACCOUNT_A], []),
log,
});
assert.strictEqual((result as { response: Response }).response.status, 400);
assert.strictEqual(observed.length, 2, "fast path must retry the direct account exactly once");
});
it("propagates the second 400 intact when the fast path retries and empty-rejects again", async () => {
const exec = new OpencodeExecutor("opencode-zen");
installFetch([
{ status: 400, body: EMPTY_BODY },
{ status: 400, body: EMPTY_BODY },
]);
const result = await exec.execute({
model: "deepseek-v4-flash-free",
body: { messages: [{ role: "user", content: "hi" }], stream: false },
stream: false,
signal: null,
credentials: credentialsFor([ACCOUNT_A], []),
log,
});
assert.strictEqual((result as { response: Response }).response.status, 400);
const propagated = await (result as { response: Response }).response.clone().text();
assert.strictEqual(propagated, EMPTY_BODY, "second rejection propagates with intact body");
assert.strictEqual(observed.length, 2, "exactly one retry, no loop");
});
it("never clones or reads the body of a 200 via the fast path", async () => {
const exec = new OpencodeExecutor("opencode-zen");
installFetch([{ status: 200 }]);
const result = await exec.execute({
model: "deepseek-v4-flash-free",
body: { messages: [{ role: "user", content: "hi" }], stream: false },
stream: false,
signal: null,
credentials: credentialsFor([ACCOUNT_A], []),
log,
});
assert.strictEqual((result as { response: Response }).response.status, 200);
assert.strictEqual(SpyResponse.clones, 0, "fast path 200 must never be cloned");
});
it("rotates to a proxied account after a proxy-less account empty-rejects (shared-egress guard on by default)", async () => {
const exec = new OpencodeExecutor("opencode-zen");
installFetch([{ status: 400, body: EMPTY_BODY }, { status: 200 }]);
const result = await exec.execute({
model: "deepseek-v4-flash-free",
body: { messages: [{ role: "user", content: "hi" }], stream: false },
stream: false,
signal: null,
// A proxy-less, B proxied: B must still be tried and succeed.
credentials: credentialsFor([ACCOUNT_A, ACCOUNT_B], [ACCOUNT_B]),
log,
});
assert.strictEqual(
(result as { response: { status: number } }).response.status,
200,
"the proxied account (B) must still be tried and must succeed"
);
assert.strictEqual(observed.length, 2, "exactly one empty rejection (A) then one success (B)");
assert.ok(
observed.some((o) => o.source === "direct"),
"first dispatch on the proxy-less account"
);
assert.ok(
observed.some((o) => o.port === String(portB)),
"rotated dispatch on the proxied account"
);
});
});