clawmoney 0.15.68 → 0.15.69
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 +107 -2
- package/package.json +1 -1
package/dist/relay/provider.js
CHANGED
|
@@ -167,6 +167,83 @@ function extractMessageText(content) {
|
|
|
167
167
|
function messagesToPrompt(messages) {
|
|
168
168
|
return messages.map((m) => extractMessageText(m.content)).join("\n");
|
|
169
169
|
}
|
|
170
|
+
// ── OAuth auto-pause (per-cli_type) ────────────────────────────────────
|
|
171
|
+
//
|
|
172
|
+
// When upstream keeps rejecting our OAuth token (Anthropic 403
|
|
173
|
+
// permission_error, ChatGPT auth failures, etc.), continuing to hammer
|
|
174
|
+
// it wastes buyer requests, surfaces errors the Hub has to failover
|
|
175
|
+
// around, and thrashes the Hub's 5xx ban / unban cycle every time the
|
|
176
|
+
// daemon reconnects. Track consecutive auth-broken errors per cli_type
|
|
177
|
+
// — after AUTH_ERROR_THRESHOLD hits in a row, stop accepting new
|
|
178
|
+
// requests for THAT cli_type until daemon restart. Every successful
|
|
179
|
+
// upstream response resets the counter.
|
|
180
|
+
//
|
|
181
|
+
// Key properties:
|
|
182
|
+
// - Per cli_type: a broken Claude OAuth doesn't take down Codex or
|
|
183
|
+
// Gemini on the same daemon, because each has its own counter and
|
|
184
|
+
// its own disable flag.
|
|
185
|
+
// - In-memory only: state resets on daemon restart. If the operator
|
|
186
|
+
// re-authed between restarts, the next request proves the token
|
|
187
|
+
// works and nothing happens; if they didn't, the counter fills
|
|
188
|
+
// back up within AUTH_ERROR_THRESHOLD requests and re-disables.
|
|
189
|
+
// - No WS lifecycle touched: the daemon stays connected to the Hub
|
|
190
|
+
// so other cli_types still serve. We just refuse to call upstream
|
|
191
|
+
// for the disabled one, returning a clean error the Hub can use
|
|
192
|
+
// to ban this provider row (its existing _is_auth_broken_error
|
|
193
|
+
// pattern catches our "OAuth authentication broken" message).
|
|
194
|
+
//
|
|
195
|
+
// Operator recovery: run `clawmoney login <cli>` (or re-auth the
|
|
196
|
+
// relevant CLI directly — `claude login`, `codex login`, etc.), then
|
|
197
|
+
// `clawmoney relay restart` to reset the counter.
|
|
198
|
+
const AUTH_ERROR_THRESHOLD = 3;
|
|
199
|
+
const consecutiveAuthErrorsByCli = new Map();
|
|
200
|
+
const cliAuthDisabled = new Set();
|
|
201
|
+
const AUTH_BROKEN_PATTERNS = [
|
|
202
|
+
// Anthropic 403: OAuth authentication is currently not allowed for
|
|
203
|
+
// this organization. The new prod signal from 2026-04-15 incident.
|
|
204
|
+
"permission_error",
|
|
205
|
+
"not allowed for this organization",
|
|
206
|
+
// Legacy Claude / Anthropic auth failures (also matched by Hub's
|
|
207
|
+
// _AUTH_BROKEN_PATTERNS, so the two sides agree on classification).
|
|
208
|
+
"token refresh failed",
|
|
209
|
+
"invalid_grant",
|
|
210
|
+
"request not allowed",
|
|
211
|
+
"oauth refresh",
|
|
212
|
+
// Generic OAuth HTTP signatures. Catches the one-off 401/403
|
|
213
|
+
// responses from codex / gemini / antigravity that carry the same
|
|
214
|
+
// meaning even when the upstream-specific message format differs.
|
|
215
|
+
"unauthorized",
|
|
216
|
+
];
|
|
217
|
+
function isAuthBrokenError(errMsg) {
|
|
218
|
+
const lower = errMsg.toLowerCase();
|
|
219
|
+
return AUTH_BROKEN_PATTERNS.some((p) => lower.includes(p));
|
|
220
|
+
}
|
|
221
|
+
function noteUpstreamAuthError(cliType) {
|
|
222
|
+
const next = (consecutiveAuthErrorsByCli.get(cliType) ?? 0) + 1;
|
|
223
|
+
consecutiveAuthErrorsByCli.set(cliType, next);
|
|
224
|
+
if (next >= AUTH_ERROR_THRESHOLD && !cliAuthDisabled.has(cliType)) {
|
|
225
|
+
cliAuthDisabled.add(cliType);
|
|
226
|
+
logger.error("");
|
|
227
|
+
logger.error(` ╔══════════════════════════════════════════════════════════════`);
|
|
228
|
+
logger.error(` ║ OAuth broken for cli_type='${cliType}' — ${next} consecutive`);
|
|
229
|
+
logger.error(` ║ auth-broken responses from upstream. Pausing relay for this`);
|
|
230
|
+
logger.error(` ║ cli_type to stop thrashing buyer requests + Hub ban state.`);
|
|
231
|
+
logger.error(` ║`);
|
|
232
|
+
logger.error(` ║ TO RESUME: re-authenticate your ${cliType} CLI locally, then`);
|
|
233
|
+
logger.error(` ║ run 'clawmoney relay restart'.`);
|
|
234
|
+
logger.error(` ║`);
|
|
235
|
+
logger.error(` ║ Other cli_types on this daemon continue to serve normally.`);
|
|
236
|
+
logger.error(` ╚══════════════════════════════════════════════════════════════`);
|
|
237
|
+
logger.error("");
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function noteUpstreamSuccess(cliType) {
|
|
241
|
+
// Successful request → reset the consecutive counter. The disabled
|
|
242
|
+
// flag is sticky until daemon restart on purpose — we never want to
|
|
243
|
+
// "heal" mid-run based on a single lucky response, which could be
|
|
244
|
+
// an upstream glitch rather than a real token refresh.
|
|
245
|
+
consecutiveAuthErrorsByCli.delete(cliType);
|
|
246
|
+
}
|
|
170
247
|
async function executeRelayRequest(request, config, sendChunk) {
|
|
171
248
|
const { request_id, max_budget_usd } = request;
|
|
172
249
|
const cliType = request.cli_type ?? config.relay.cli_type;
|
|
@@ -190,6 +267,21 @@ async function executeRelayRequest(request, config, sendChunk) {
|
|
|
190
267
|
logger.info(` │ CLI: ${cliType} / ${model} (${modeLabel})`);
|
|
191
268
|
logger.info(` │ Turns: ${turns}`);
|
|
192
269
|
logger.info(` │ Prompt: ${String(lastUserMsg).slice(0, 80)}`);
|
|
270
|
+
// Fast-fail if this cli_type was auto-paused by a run of auth-broken
|
|
271
|
+
// responses earlier in the session. Returning the error here instead
|
|
272
|
+
// of calling upstream saves the round-trip and keeps the Hub's ban
|
|
273
|
+
// pattern triggering (it matches "OAuth authentication" / "auth
|
|
274
|
+
// broken" in _is_auth_broken_error) so buyer requests go straight to
|
|
275
|
+
// a healthy provider.
|
|
276
|
+
if (cliAuthDisabled.has(cliType)) {
|
|
277
|
+
logger.warn(` └─ REFUSED: ${cliType} auth paused (restart relay after re-auth)`);
|
|
278
|
+
return {
|
|
279
|
+
event: "relay_response",
|
|
280
|
+
request_id,
|
|
281
|
+
content: "",
|
|
282
|
+
error: `OAuth authentication broken for cli_type='${cliType}'. Provider needs to re-authenticate locally and restart the daemon. (permission_error)`,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
193
285
|
try {
|
|
194
286
|
const startMs = Date.now();
|
|
195
287
|
let parsed;
|
|
@@ -300,6 +392,9 @@ async function executeRelayRequest(request, config, sendChunk) {
|
|
|
300
392
|
if (fakeModelUsed) {
|
|
301
393
|
logger.warn(` ! CLAWMONEY_FAKE_MODEL_USED=${fakeModelUsed} — reporting fake model to Hub (test mode)`);
|
|
302
394
|
}
|
|
395
|
+
// Successful upstream round-trip — reset the auth-error counter for
|
|
396
|
+
// this cli_type. One good response means the token currently works.
|
|
397
|
+
noteUpstreamSuccess(cliType);
|
|
303
398
|
return {
|
|
304
399
|
event: "relay_response",
|
|
305
400
|
request_id,
|
|
@@ -312,12 +407,22 @@ async function executeRelayRequest(request, config, sendChunk) {
|
|
|
312
407
|
};
|
|
313
408
|
}
|
|
314
409
|
catch (err) {
|
|
315
|
-
|
|
410
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
411
|
+
logger.error(` └─ ERROR: ${errMsg}`);
|
|
412
|
+
// If the upstream error looks like a persistent auth failure
|
|
413
|
+
// (OAuth rejected, token broken, permission_error, etc.), bump
|
|
414
|
+
// this cli_type's consecutive-auth-error counter. After
|
|
415
|
+
// AUTH_ERROR_THRESHOLD in a row, future requests for this
|
|
416
|
+
// cli_type short-circuit at the top of executeRelayRequest until
|
|
417
|
+
// daemon restart.
|
|
418
|
+
if (isAuthBrokenError(errMsg)) {
|
|
419
|
+
noteUpstreamAuthError(cliType);
|
|
420
|
+
}
|
|
316
421
|
return {
|
|
317
422
|
event: "relay_response",
|
|
318
423
|
request_id,
|
|
319
424
|
content: "",
|
|
320
|
-
error:
|
|
425
|
+
error: errMsg || "Unknown execution error",
|
|
321
426
|
};
|
|
322
427
|
}
|
|
323
428
|
}
|