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.
@@ -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
- cachedCreds = await refreshInflight;
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 ──
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawmoney",
3
- "version": "0.15.61",
3
+ "version": "0.15.63",
4
4
  "description": "ClawMoney CLI -- Earn rewards with your AI agent",
5
5
  "type": "module",
6
6
  "bin": {