History

fix(claude): refresh cached fingerprint after baseline upgrades

tpob committed 7 months ago

Commit ed0e2971 parent 74730aa2 files · +67 −2Browse files

Unified · Split

2 files
  1. internal/runtime/executor/claude_device_profile.go +15 −2
  2. internal/runtime/executor/claude_executor_test.go +52 −0
internal/runtime/executor/claude_device_profile.go+15−2
@@ -179,6 +179,19 @@ func pinClaudeDeviceProfilePlatform(profile, baseline claudeDeviceProfile) claud · 178 unmodified lines
179179 return profile
180180 }
181181
182+// normalizeClaudeDeviceProfile keeps stabilized profiles pinned to the current
183+// baseline platform and enforces the baseline software fingerprint as a floor.
184+func normalizeClaudeDeviceProfile(profile, baseline claudeDeviceProfile) claudeDeviceProfile {
185+ profile = pinClaudeDeviceProfilePlatform(profile, baseline)
186+ if profile.UserAgent == "" || profile.Version == (claudeCLIVersion{}) || shouldUpgradeClaudeDeviceProfile(baseline, profile) {
187+ profile.UserAgent = baseline.UserAgent
188+ profile.PackageVersion = baseline.PackageVersion
189+ profile.RuntimeVersion = baseline.RuntimeVersion
190+ profile.Version = baseline.Version
191+ }
192+ return profile
193+}
194+
182195 func extractClaudeDeviceProfile(headers http.Header, cfg *config.Config) (claudeDeviceProfile, bool) {
183196 if headers == nil {
184197 return claudeDeviceProfile{}, false
@@ -277,7 +290,7 @@ func resolveClaudeDeviceProfile(auth *cliproxyauth.Auth, apiKey string, headers · 92 unmodified lines
277290 entry, hasCached = claudeDeviceProfileCache[cacheKey]
278291 cachedValid = hasCached && entry.expire.After(now) && entry.profile.UserAgent != ""
279292 if cachedValid {
280− entry.profile = pinClaudeDeviceProfilePlatform(entry.profile, baseline)
293+ entry.profile = normalizeClaudeDeviceProfile(entry.profile, baseline)
281294 }
282295 if cachedValid && !shouldUpgradeClaudeDeviceProfile(candidate, entry.profile) {
283296 entry.expire = now.Add(claudeDeviceProfileTTL)
@@ -298,7 +311,7 @@ func resolveClaudeDeviceProfile(auth *cliproxyauth.Auth, apiKey string, headers · 14 unmodified lines
298311 claudeDeviceProfileCacheMu.Lock()
299312 entry = claudeDeviceProfileCache[cacheKey]
300313 if entry.expire.After(now) && entry.profile.UserAgent != "" {
301− entry.profile = pinClaudeDeviceProfilePlatform(entry.profile, baseline)
314+ entry.profile = normalizeClaudeDeviceProfile(entry.profile, baseline)
302315 entry.expire = now.Add(claudeDeviceProfileTTL)
303316 claudeDeviceProfileCache[cacheKey] = entry
304317 claudeDeviceProfileCacheMu.Unlock()
internal/runtime/executor/claude_executor_test.go+52−0
@@ -208,6 +208,58 @@ func TestApplyClaudeHeaders_DoesNotDowngradeConfiguredBaselineOnFirstClaudeClien · 207 unmodified lines
208208 assertClaudeFingerprint(t, newerClaudeReq.Header, "claude-cli/2.1.71 (external, cli)", "0.81.0", "v24.6.0", "MacOS", "arm64")
209209 }
210210
211+func TestApplyClaudeHeaders_UpgradesCachedSoftwareFingerprintWhenBaselineAdvances(t *testing.T) {
212+ resetClaudeDeviceProfileCache()
213+ stabilize := true
214+
215+ oldCfg := &config.Config{
216+ ClaudeHeaderDefaults: config.ClaudeHeaderDefaults{
217+ UserAgent: "claude-cli/2.1.70 (external, cli)",
218+ PackageVersion: "0.80.0",
219+ RuntimeVersion: "v24.5.0",
220+ OS: "MacOS",
221+ Arch: "arm64",
222+ StabilizeDeviceProfile: &stabilize,
223+ },
224+ }
225+ newCfg := &config.Config{
226+ ClaudeHeaderDefaults: config.ClaudeHeaderDefaults{
227+ UserAgent: "claude-cli/2.1.77 (external, cli)",
228+ PackageVersion: "0.87.0",
229+ RuntimeVersion: "v24.8.0",
230+ OS: "MacOS",
231+ Arch: "arm64",
232+ StabilizeDeviceProfile: &stabilize,
233+ },
234+ }
235+ auth := &cliproxyauth.Auth{
236+ ID: "auth-baseline-reload",
237+ Attributes: map[string]string{
238+ "api_key": "key-baseline-reload",
239+ },
240+ }
241+
242+ officialReq := newClaudeHeaderTestRequest(t, http.Header{
243+ "User-Agent": []string{"claude-cli/2.1.71 (external, cli)"},
244+ "X-Stainless-Package-Version": []string{"0.81.0"},
245+ "X-Stainless-Runtime-Version": []string{"v24.6.0"},
246+ "X-Stainless-Os": []string{"Linux"},
247+ "X-Stainless-Arch": []string{"x64"},
248+ })
249+ applyClaudeHeaders(officialReq, auth, "key-baseline-reload", false, nil, oldCfg)
250+ assertClaudeFingerprint(t, officialReq.Header, "claude-cli/2.1.71 (external, cli)", "0.81.0", "v24.6.0", "MacOS", "arm64")
251+
252+ thirdPartyReq := newClaudeHeaderTestRequest(t, http.Header{
253+ "User-Agent": []string{"curl/8.7.1"},
254+ "X-Stainless-Package-Version": []string{"0.10.0"},
255+ "X-Stainless-Runtime-Version": []string{"v18.0.0"},
256+ "X-Stainless-Os": []string{"Linux"},
257+ "X-Stainless-Arch": []string{"x64"},
258+ })
259+ applyClaudeHeaders(thirdPartyReq, auth, "key-baseline-reload", false, nil, newCfg)
260+ assertClaudeFingerprint(t, thirdPartyReq.Header, "claude-cli/2.1.77 (external, cli)", "0.87.0", "v24.8.0", "MacOS", "arm64")
261+}
262+
211263 func TestResolveClaudeDeviceProfile_RechecksCacheBeforeStoringCandidate(t *testing.T) {
212264 resetClaudeDeviceProfileCache()
213265 stabilize := true