Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion open-sse/config/anthropicHeaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ export function claudeCliUserAgent(version: string): string {
return `claude-cli/${version} (external, ${getClaudeEntrypoint()})`;
}

export const CLAUDE_CLI_USER_AGENT = claudeCliUserAgent(CLAUDE_CLI_VERSION);
// Static registry constant — always "cli" regardless of CLAUDE_CC_ENTRYPOINT.
// getClaudeCliHeaders() uses this for API-key connections; the dynamic entrypoint
// only applies to native Claude OAuth call sites (see base.ts ccHeaders block).
export const CLAUDE_CLI_USER_AGENT = `claude-cli/${CLAUDE_CLI_VERSION} (external, cli)`;
export const CLAUDE_CLI_STAINLESS_PACKAGE_VERSION = "0.94.0";
export const CLAUDE_CLI_STAINLESS_RUNTIME_VERSION = "v24.3.0";
10 changes: 4 additions & 6 deletions open-sse/executors/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -939,14 +939,11 @@ export class BaseExecutor {

const seed = activeCredentials?.accessToken || activeCredentials?.apiKey || "anon";
const psd = activeCredentials?.providerSpecificData as
| Record<string, unknown>
| undefined;
Record<string, unknown> | undefined;

let identitySource:
| "upstream-metadata"
| "upstream-header"
| "synthesized"
| "synthesized-cloaked" = "synthesized";
"upstream-metadata" | "upstream-header" | "synthesized" | "synthesized-cloaked" =
"synthesized";
let sessionId: string;
let deviceId: string;
let accountUUID: string;
Expand Down Expand Up @@ -1181,6 +1178,7 @@ export class BaseExecutor {
}

mergeUpstreamExtraHeaders(finalHeaders, upstreamExtraHeaders);

const serializedBody = prl.parseBody(bodyString);
// #4307 — Preserve the non-enumerable tool-name cloak/remap reverse map
// (`_toolNameMap`, set on the live `transformedBody` by
Expand Down
33 changes: 33 additions & 0 deletions tests/unit/claude-entrypoint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import assert from "node:assert/strict";
import {
getClaudeEntrypoint,
claudeCliUserAgent,
CLAUDE_CLI_USER_AGENT,
CLAUDE_CLI_VERSION,
} from "../../open-sse/config/anthropicHeaders.ts";
import { mergeUpstreamExtraHeaders } from "../../open-sse/executors/base/headers.ts";

const ORIGINAL = process.env.CLAUDE_CC_ENTRYPOINT;

Expand Down Expand Up @@ -50,3 +53,33 @@ test("getClaudeEntrypoint falls back to cli on an invalid value", () => {
assert.equal(claudeCliUserAgent("2.1.158"), "claude-cli/2.1.158 (external, cli)");
});
});

// Regression guard: CLAUDE_CLI_USER_AGENT is used in getClaudeCliHeaders() for
// API-key connections. It must always be "cli" — never reflect CLAUDE_CC_ENTRYPOINT,
// which is an OAuth-only billing identity knob. Changing this to use claudeCliUserAgent()
// (dynamic) would leak the sdk-cli entrypoint into non-OAuth credential surfaces.
test("CLAUDE_CLI_USER_AGENT is always cli regardless of CLAUDE_CC_ENTRYPOINT", () => {
assert.equal(CLAUDE_CLI_USER_AGENT, `claude-cli/${CLAUDE_CLI_VERSION} (external, cli)`);
assert.ok(
!CLAUDE_CLI_USER_AGENT.includes("sdk-cli"),
"static registry UA must never carry sdk-cli"
);
});

// Upstream headers are an explicit operator override. OAuth uses the dynamic default
// UA, while mergeUpstreamExtraHeaders keeps its documented last-writer-wins behavior.
test("mergeUpstreamExtraHeaders preserves an explicit User-Agent override", () => {
const version = "4.0.0";
const headers: Record<string, string> = {
"User-Agent": claudeCliUserAgent(version),
};
const expected = `claude-cli/${version} (external, ${getClaudeEntrypoint()})`;
assert.equal(headers["User-Agent"], expected);

// Operator sets a custom User-Agent via upstream extra headers — mergeUpstreamExtraHeaders
// will apply it, overriding the OAuth billing UA.
mergeUpstreamExtraHeaders(headers, { "User-Agent": "custom-proxy/1.0" });
assert.equal(headers["User-Agent"], "custom-proxy/1.0");

assert.equal(headers["User-Agent"], "custom-proxy/1.0");
});
Loading