fix(headless): remove legacy v1 fallback path

This commit is contained in:
Mikael Hugo 2026-05-15 20:12:00 +02:00
parent e2e096c5c7
commit 0b187b9f62
2 changed files with 20 additions and 40 deletions

View file

@ -105,7 +105,6 @@ import {
} from "./resources/extensions/sf/trace-collector.js";
const HEADLESS_HEARTBEAT_INTERVAL_MS = 60_000;
const HEADLESS_LEGACY_FALLBACK_ENV = "SF_HEADLESS_ALLOW_V1_FALLBACK";
interface HeadlessTimeoutSolverEvalRecord {
runId: string;
@ -1983,27 +1982,22 @@ async function runHeadlessOnce(
process.exit(1);
}
// v2 protocol negotiation — attempt init for structured completion events
let _v2Enabled = false;
try {
await client.init({ clientId: "sf-headless" });
_v2Enabled = true;
} catch (initErr) {
const reason = initErr instanceof Error ? initErr.message : String(initErr);
if (process.env[HEADLESS_LEGACY_FALLBACK_ENV] !== "1") {
process.stderr.write(
`[headless] v2 init failed (${reason}); refusing legacy v1 string-matching fallback. Set ${HEADLESS_LEGACY_FALLBACK_ENV}=1 only for explicit recovery.\n`,
);
await client.stop().catch(() => {});
if (timeoutTimer) clearTimeout(timeoutTimer);
if (idleTimer) clearTimeout(idleTimer);
if (heartbeatTimer) clearInterval(heartbeatTimer);
process.exit(EXIT_ERROR);
}
process.stderr.write(
`[headless] Warning: v2 init failed (${reason}), falling back to v1 string-matching because ${HEADLESS_LEGACY_FALLBACK_ENV}=1\n`,
);
}
// v2 protocol negotiation — attempt init for structured completion events
let _v2Enabled = false;
try {
await client.init({ clientId: "sf-headless" });
_v2Enabled = true;
} catch (initErr) {
const reason = initErr instanceof Error ? initErr.message : String(initErr);
process.stderr.write(
`[headless] v2 init failed (${reason}); refusing legacy v1 string-matching fallback.\n`,
);
await client.stop().catch(() => {});
if (timeoutTimer) clearTimeout(timeoutTimer);
if (idleTimer) clearTimeout(idleTimer);
if (heartbeatTimer) clearInterval(heartbeatTimer);
process.exit(EXIT_ERROR);
}
clientStarted = true;

View file

@ -97,17 +97,13 @@ class MockRpcClient {
async function negotiateV2ForTest(
client: MockRpcClient,
allowLegacyFallback = false,
): Promise<"v2" | "legacy" | "fatal"> {
): Promise<"v2" | "fatal"> {
try {
await client.init({ clientId: "sf-headless" });
return "v2";
} catch {
if (!allowLegacyFallback) {
await client.stop();
return "fatal";
}
return "legacy";
await client.stop();
return "fatal";
}
}
@ -526,7 +522,7 @@ test("v2 init success sets v2Enabled", async () => {
await client.init({ clientId: "sf-headless" });
v2Enabled = true;
} catch {
// fall back to v1
await client.stop();
}
assert.equal(client.initCalled, true);
@ -543,16 +539,6 @@ test("v2 init failure is fatal by default", async () => {
assert.equal(mode, "fatal");
});
test("v2 init failure uses v1 only with explicit legacy fallback opt-in", async () => {
const client = new MockRpcClient();
client.initShouldFail = true;
const mode = await negotiateV2ForTest(client, true);
assert.equal(client.initCalled, true);
assert.equal(client.stopCalled, false);
assert.equal(mode, "legacy");
});
// ─── injector adapter ───────────────────────────────────────────────────────
test("injector adapter parses serialized JSONL and calls sendUIResponse", () => {