fix(sse): stop force-injecting advanced-tool-use beta via the effort-2025-11-24 gate; forward client-negotiated effort through the allowlist (#9505)

Closes #9505
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-08-05 16:48:33 -03:00
committed by GitHub
parent ee4cd0d795
commit 5f3dad4da6
4 changed files with 85 additions and 3 deletions

View File

@@ -0,0 +1 @@
- fix(sse): stop force-injecting advanced-tool-use beta via the effort-2025-11-24 gate; forward client-negotiated effort through the allowlist (#9505)

View File

@@ -57,6 +57,11 @@ export const FORWARDABLE_CLIENT_BETAS = Object.freeze([
"context-1m-2025-08-07",
"code-execution-2025-08-25",
"skills-2025-10-02",
// effort-2025-11-24 is a client-negotiated beta (Claude Code sends it on every
// request). selectBetaFlags no longer force-adds it as a side-effect of the ATU
// gate (#9505), so a client that sent it must keep it through the merge —
// otherwise its effort negotiation is silently dropped.
"effort-2025-11-24",
]);
/**

View File

@@ -357,10 +357,12 @@ export function selectBetaFlags(
// betas it actually asked for. Opaque clients (clientBetaSet === null) keep them all.
const allowThinking =
clientBetaSet === null || clientBetaSet.has("interleaved-thinking-2025-05-14");
// effort-2025-11-24 must NOT imply advanced-tool-use-2025-11-20 (#9505): Claude
// Code sends effort on every request and never sends ATU, so treating effort as
// a proxy for ATU force-injects the heavy-agent pair the client never negotiated —
// the same class of mutation #3415 closed. Opaque clients keep the full set.
const allowHeavy =
clientBetaSet === null ||
clientBetaSet.has("advanced-tool-use-2025-11-20") ||
clientBetaSet.has("effort-2025-11-24");
clientBetaSet === null || clientBetaSet.has("advanced-tool-use-2025-11-20");
const hasSystem =
!!b.system &&
(typeof b.system === "string" || (Array.isArray(b.system) && b.system.length > 0));

View File

@@ -0,0 +1,74 @@
// Regression guard for #9505 — forced advanced-tool-use beta must not survive
// the effort-2025-11-24 gate; client-negotiated effort must survive the merge.
import test from "node:test";
import assert from "node:assert/strict";
const { selectBetaFlags } = await import(
"../../open-sse/executors/claudeIdentity.ts"
);
const { mergeClientAnthropicBeta } = await import(
"../../open-sse/config/anthropicHeaders.ts"
);
function fullAgentBody(model: string) {
return {
model,
system: "You are a coding agent.",
tools: [
{ name: "read_file", description: "x", input_schema: { type: "object" } },
],
};
}
function pipeline(model: string, clientBeta: string | null) {
return mergeClientAnthropicBeta(
selectBetaFlags(fullAgentBody(model), null, clientBeta),
clientBeta
);
}
test("#9505 client sends effort but NOT advanced-tool-use -> ATU must NOT be forced", () => {
const out = pipeline("claude-opus-5", "claude-code-20250219,effort-2025-11-24");
assert.ok(
!out.split(",").includes("advanced-tool-use-2025-11-20"),
"must NOT force ATU when client only sent effort"
);
});
test("#9505 client sends effort only -> effort still survives the merge", () => {
const out = pipeline("claude-opus-5", "claude-code-20250219,effort-2025-11-24");
assert.ok(
out.split(",").includes("effort-2025-11-24"),
"client-sent effort must survive the allowlist merge"
);
});
test("#9505 client sends advanced-tool-use explicitly -> ATU preserved", () => {
const out = pipeline(
"claude-opus-5",
"claude-code-20250219,advanced-tool-use-2025-11-20"
);
assert.ok(
out.split(",").includes("advanced-tool-use-2025-11-20"),
"must keep ATU when client requested it"
);
assert.ok(
out.split(",").includes("effort-2025-11-24"),
"ATU+effort pair stays together when ATU requested"
);
});
test("#9505 client sends BOTH effort and ATU -> both preserved", () => {
const out = pipeline(
"claude-sonnet-5",
"claude-code-20250219,advanced-tool-use-2025-11-20,effort-2025-11-24"
);
assert.ok(out.split(",").includes("advanced-tool-use-2025-11-20"));
assert.ok(out.split(",").includes("effort-2025-11-24"));
});
test("#9505 opaque client (no clientBeta) still gets full heavy-agent set", () => {
const flags = selectBetaFlags(fullAgentBody("claude-opus-5"));
assert.ok(flags.includes("advanced-tool-use-2025-11-20"));
assert.ok(flags.includes("effort-2025-11-24"));
});