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
179 return profile179 return profile
180}180}
181 181
182// normalizeClaudeDeviceProfile keeps stabilized profiles pinned to the current
183// baseline platform and enforces the baseline software fingerprint as a floor.
184func 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
182func extractClaudeDeviceProfile(headers http.Header, cfg *config.Config) (claudeDeviceProfile, bool) {195func extractClaudeDeviceProfile(headers http.Header, cfg *config.Config) (claudeDeviceProfile, bool) {
183 if headers == nil {196 if headers == nil {
184 return claudeDeviceProfile{}, false197 return claudeDeviceProfile{}, false
@@ -277,7 +290,7 @@ func resolveClaudeDeviceProfile(auth *cliproxyauth.Auth, apiKey string, headers · 92 unmodified lines
277 entry, hasCached = claudeDeviceProfileCache[cacheKey]290 entry, hasCached = claudeDeviceProfileCache[cacheKey]
278 cachedValid = hasCached && entry.expire.After(now) && entry.profile.UserAgent != ""291 cachedValid = hasCached && entry.expire.After(now) && entry.profile.UserAgent != ""
279 if cachedValid {292 if cachedValid {
280 entry.profile = pinClaudeDeviceProfilePlatform(entry.profile, baseline)293 entry.profile = normalizeClaudeDeviceProfile(entry.profile, baseline)
281 }294 }
282 if cachedValid && !shouldUpgradeClaudeDeviceProfile(candidate, entry.profile) {295 if cachedValid && !shouldUpgradeClaudeDeviceProfile(candidate, entry.profile) {
283 entry.expire = now.Add(claudeDeviceProfileTTL)296 entry.expire = now.Add(claudeDeviceProfileTTL)
@@ -298,7 +311,7 @@ func resolveClaudeDeviceProfile(auth *cliproxyauth.Auth, apiKey string, headers · 14 unmodified lines
298 claudeDeviceProfileCacheMu.Lock()311 claudeDeviceProfileCacheMu.Lock()
299 entry = claudeDeviceProfileCache[cacheKey]312 entry = claudeDeviceProfileCache[cacheKey]
300 if entry.expire.After(now) && entry.profile.UserAgent != "" {313 if entry.expire.After(now) && entry.profile.UserAgent != "" {
301 entry.profile = pinClaudeDeviceProfilePlatform(entry.profile, baseline)314 entry.profile = normalizeClaudeDeviceProfile(entry.profile, baseline)
302 entry.expire = now.Add(claudeDeviceProfileTTL)315 entry.expire = now.Add(claudeDeviceProfileTTL)
303 claudeDeviceProfileCache[cacheKey] = entry316 claudeDeviceProfileCache[cacheKey] = entry
304 claudeDeviceProfileCacheMu.Unlock()317 claudeDeviceProfileCacheMu.Unlock()
internal/runtime/executor/claude_executor_test.go+52−0
@@ -208,6 +208,58 @@ func TestApplyClaudeHeaders_DoesNotDowngradeConfiguredBaselineOnFirstClaudeClien · 207 unmodified lines
208 assertClaudeFingerprint(t, newerClaudeReq.Header, "claude-cli/2.1.71 (external, cli)", "0.81.0", "v24.6.0", "MacOS", "arm64")208 assertClaudeFingerprint(t, newerClaudeReq.Header, "claude-cli/2.1.71 (external, cli)", "0.81.0", "v24.6.0", "MacOS", "arm64")
209}209}
210 210
211func 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
211func TestResolveClaudeDeviceProfile_RechecksCacheBeforeStoringCandidate(t *testing.T) {263func TestResolveClaudeDeviceProfile_RechecksCacheBeforeStoringCandidate(t *testing.T) {
212 resetClaudeDeviceProfileCache()264 resetClaudeDeviceProfileCache()
213 stabilize := true265 stabilize := true