clawmoney 0.15.61 → 0.15.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/relay/provider.js
CHANGED
|
@@ -94,6 +94,11 @@ function loadRelayConfig(cliOverride) {
|
|
|
94
94
|
const userRelay = (raw.relay ?? {});
|
|
95
95
|
const relay = {
|
|
96
96
|
cli_type: cliOverride ?? userRelay.cli_type ?? DEFAULT_RELAY.cli_type,
|
|
97
|
+
// Pass rate_guard through verbatim. Per-field merging happens
|
|
98
|
+
// inside each upstream module's configureRateGuard() which
|
|
99
|
+
// already knows its own defaults — we don't want to double-
|
|
100
|
+
// default here.
|
|
101
|
+
rate_guard: userRelay.rate_guard,
|
|
97
102
|
model: userRelay.model ?? DEFAULT_RELAY.model,
|
|
98
103
|
mode: userRelay.mode ?? DEFAULT_RELAY.mode,
|
|
99
104
|
concurrency: userRelay.concurrency ?? DEFAULT_RELAY.concurrency,
|
|
@@ -519,6 +519,38 @@ async function refreshUpstreamToken(refreshToken) {
|
|
|
519
519
|
let cachedCreds = null;
|
|
520
520
|
let refreshInflight = null;
|
|
521
521
|
const REFRESH_SKEW_MS = 3 * 60 * 1000;
|
|
522
|
+
// ── Auth-broken circuit breaker ─────────────────────────────────────────
|
|
523
|
+
//
|
|
524
|
+
// If Anthropic's OAuth refresh endpoint rejects our refresh_token as
|
|
525
|
+
// invalid (400 invalid_grant, 401, 403 "Request not allowed", etc.),
|
|
526
|
+
// that's a persistent condition — the token is not going to start
|
|
527
|
+
// working again on its own. Every subsequent buyer request would burn
|
|
528
|
+
// another refresh attempt on Anthropic, which looks like brute-forcing
|
|
529
|
+
// from their anti-abuse side and risks getting the provider's account
|
|
530
|
+
// flagged.
|
|
531
|
+
//
|
|
532
|
+
// Cache the "broken" state for AUTH_BROKEN_CACHE_MS. During that window
|
|
533
|
+
// ALL calls to getFreshCreds() short-circuit with the cached error
|
|
534
|
+
// WITHOUT hitting Anthropic. After the window expires we allow exactly
|
|
535
|
+
// one probe refresh — if it succeeds, we're unbroken; if it fails
|
|
536
|
+
// again, the window is extended by another interval.
|
|
537
|
+
//
|
|
538
|
+
// Transient 5xx responses are NOT cached — those are "maybe the server
|
|
539
|
+
// is having a moment" and worth retrying. Only 4xx "no, really, the
|
|
540
|
+
// token is bad" responses trip the breaker.
|
|
541
|
+
const AUTH_BROKEN_CACHE_MS = 5 * 60 * 1000;
|
|
542
|
+
let authBrokenUntilMs = 0;
|
|
543
|
+
let authBrokenError = null;
|
|
544
|
+
function isAuthBrokenError(err) {
|
|
545
|
+
const msg = err.message.toLowerCase();
|
|
546
|
+
// Matches messages produced by refreshUpstreamToken:
|
|
547
|
+
// "Token refresh failed: 400 ...invalid_grant..."
|
|
548
|
+
// "Token refresh failed: 401 ..."
|
|
549
|
+
// "Token refresh failed: 403 ...Request not allowed..."
|
|
550
|
+
return (msg.includes("invalid_grant") ||
|
|
551
|
+
msg.includes("request not allowed") ||
|
|
552
|
+
/token refresh failed:\s*40[0134]/.test(msg));
|
|
553
|
+
}
|
|
522
554
|
async function doRefreshAndPersist(current) {
|
|
523
555
|
logger.info("[claude-api] refreshing OAuth token...");
|
|
524
556
|
const fresh = await refreshUpstreamToken(current.refreshToken);
|
|
@@ -569,6 +601,14 @@ async function doRefreshAndPersist(current) {
|
|
|
569
601
|
return next;
|
|
570
602
|
}
|
|
571
603
|
async function getFreshCreds() {
|
|
604
|
+
// Circuit breaker: if the OAuth endpoint is known-broken for this
|
|
605
|
+
// daemon, throw the cached error immediately without touching
|
|
606
|
+
// Anthropic again. This is what keeps a retry storm from burning
|
|
607
|
+
// one refresh attempt per buyer request and getting the account
|
|
608
|
+
// flagged.
|
|
609
|
+
if (authBrokenUntilMs && Date.now() < authBrokenUntilMs && authBrokenError) {
|
|
610
|
+
throw authBrokenError;
|
|
611
|
+
}
|
|
572
612
|
if (!cachedCreds) {
|
|
573
613
|
cachedCreds = loadClaudeOAuth();
|
|
574
614
|
}
|
|
@@ -582,7 +622,24 @@ async function getFreshCreds() {
|
|
|
582
622
|
refreshInflight = null;
|
|
583
623
|
});
|
|
584
624
|
}
|
|
585
|
-
|
|
625
|
+
try {
|
|
626
|
+
cachedCreds = await refreshInflight;
|
|
627
|
+
}
|
|
628
|
+
catch (err) {
|
|
629
|
+
const e = err;
|
|
630
|
+
if (isAuthBrokenError(e)) {
|
|
631
|
+
authBrokenUntilMs = Date.now() + AUTH_BROKEN_CACHE_MS;
|
|
632
|
+
authBrokenError = e;
|
|
633
|
+
logger.error(`[claude-api] OAuth refresh rejected by Anthropic — caching auth-broken state for ${AUTH_BROKEN_CACHE_MS / 1000}s. Subsequent requests will fail fast without hitting the OAuth endpoint. Fix: re-login with 'claude /login' and restart the daemon. Root cause: ${e.message.slice(0, 200)}`);
|
|
634
|
+
}
|
|
635
|
+
throw err;
|
|
636
|
+
}
|
|
637
|
+
// Successful refresh — clear any cached broken state.
|
|
638
|
+
if (authBrokenUntilMs) {
|
|
639
|
+
authBrokenUntilMs = 0;
|
|
640
|
+
authBrokenError = null;
|
|
641
|
+
logger.info("[claude-api] OAuth refresh recovered — auth-broken cache cleared");
|
|
642
|
+
}
|
|
586
643
|
return cachedCreds;
|
|
587
644
|
}
|
|
588
645
|
// ── Version drift check ──
|