From ed3ff54f0c9370b34df3e25212abf2af0f6ffac0 Mon Sep 17 00:00:00 2001 From: matevip Date: Sun, 26 Apr 2026 08:34:11 +0800 Subject: [PATCH] =?UTF-8?q?fix(anthropic):=20drop=20(external,=20cli)=20UA?= =?UTF-8?q?=20suffix=20=E2=80=94=20it's=20the=20anti-abuse=20fingerprint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AgentClaudeCodeChatModelBuilder.java | 7 ++++--- .../anthropic/oauth/ClaudeCodeApiHeaders.java | 18 +++++++++++++----- .../oauth/ClaudeCodeTokenRefresher.java | 5 ++++- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/mateclaw-server/src/main/java/vip/mate/agent/chatmodel/AgentClaudeCodeChatModelBuilder.java b/mateclaw-server/src/main/java/vip/mate/agent/chatmodel/AgentClaudeCodeChatModelBuilder.java index d8a5eca4..d3f83027 100644 --- a/mateclaw-server/src/main/java/vip/mate/agent/chatmodel/AgentClaudeCodeChatModelBuilder.java +++ b/mateclaw-server/src/main/java/vip/mate/agent/chatmodel/AgentClaudeCodeChatModelBuilder.java @@ -38,9 +38,10 @@ import vip.mate.llm.model.ModelProviderEntity; * {@code oauth-2025-04-20} or Anthropic's edge intermittently 500s. * We push these via {@link AnthropicApi.Builder#anthropicBetaFeatures} * so Spring AI's existing header-merging logic still applies. - *
  • {@code User-Agent: claude-cli/ (external, cli)} and - * {@code x-app: cli} masquerade as the Claude Code CLI — Anthropic - * rejects unrecognised UAs on Bearer-auth requests with HTTP 400.
  • + *
  • {@code User-Agent: claude-cli/} (bare — no suffix) and + * {@code x-app: cli} masquerade as the Claude Code CLI. Suffix variants + * like {@code (external, cli)} are anti-abuse fingerprints; see + * {@link ClaudeCodeApiHeaders#userAgent()}.
  • * * *

    Token lifecycle

    diff --git a/mateclaw-server/src/main/java/vip/mate/llm/anthropic/oauth/ClaudeCodeApiHeaders.java b/mateclaw-server/src/main/java/vip/mate/llm/anthropic/oauth/ClaudeCodeApiHeaders.java index f3306cf8..60864e00 100644 --- a/mateclaw-server/src/main/java/vip/mate/llm/anthropic/oauth/ClaudeCodeApiHeaders.java +++ b/mateclaw-server/src/main/java/vip/mate/llm/anthropic/oauth/ClaudeCodeApiHeaders.java @@ -20,7 +20,7 @@ import java.util.List; * Header set sent on OAuth requests * HeaderValueWhy * {@code Authorization}{@code Bearer }OAuth path uses Bearer; non-OAuth uses {@code x-api-key} - * {@code User-Agent}{@code claude-cli/ (external, cli)}Anthropic routes OAuth by UA; spoof identity + * {@code User-Agent}{@code claude-cli/}bare form — suffix triggers anti-abuse fingerprint, see {@link #userAgent()} * {@code x-app}{@code cli}Claude Code identity flag * {@code anthropic-beta}(comma-joined list — see {@link #allBetas()})OAuth-only + common feature betas * @@ -58,12 +58,20 @@ public class ClaudeCodeApiHeaders { /** * User-Agent string Anthropic OAuth infrastructure expects. - * Format: {@code claude-cli/ (external, cli)}. - * The {@code (external, cli)} suffix is the canonical hermes / OpenCode / - * Cline identity — drop it and Anthropic returns 400. + * Format: {@code claude-cli/} — bare, no suffix. + * + *

    History note: we previously appended {@code (external, cli)} + * after hermes-agent's pattern. That turned out to be wrong: Anthropic's + * anti-abuse gate uses the suffix to fingerprint third-party clients + * (hermes / OpenCode / Cline) and rate-limits them harder. Real Claude + * Code (Electron + Node + official Anthropic JS SDK) emits the bare + * {@code claude-cli/} form, which is what openclaw + * ({@code anthropic-transport-stream.ts:30,572}) also uses. Verified by + * reproducing 429 with the suffix and {@code anthropic-ratelimit-*} + * headers absent — the diagnostic signature of the anti-abuse path. */ public String userAgent() { - return "claude-cli/" + versionDetector.get() + " (external, cli)"; + return "claude-cli/" + versionDetector.get(); } /** {@code x-app} header value. Constant. */ diff --git a/mateclaw-server/src/main/java/vip/mate/llm/anthropic/oauth/ClaudeCodeTokenRefresher.java b/mateclaw-server/src/main/java/vip/mate/llm/anthropic/oauth/ClaudeCodeTokenRefresher.java index 504a3a9b..0ce3cc04 100644 --- a/mateclaw-server/src/main/java/vip/mate/llm/anthropic/oauth/ClaudeCodeTokenRefresher.java +++ b/mateclaw-server/src/main/java/vip/mate/llm/anthropic/oauth/ClaudeCodeTokenRefresher.java @@ -80,8 +80,11 @@ public class ClaudeCodeTokenRefresher { .uri(endpoint) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) + // Bare UA — see ClaudeCodeApiHeaders.userAgent() javadoc + // for why the (external, cli) suffix would trip Anthropic's + // anti-abuse fingerprint. .header(HttpHeaders.USER_AGENT, - "claude-cli/" + versionDetector.get() + " (external, cli)") + "claude-cli/" + versionDetector.get()) .body(body) .retrieve() .body(String.class);