@groupchatai/claude-runner 0.4.5 → 0.4.6
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/index.js +87 -106
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -203,6 +203,16 @@ function formatStreamEvent(event, pid) {
|
|
|
203
203
|
}
|
|
204
204
|
case "tool_result":
|
|
205
205
|
return null;
|
|
206
|
+
case "rate_limit_event": {
|
|
207
|
+
const info = event.rate_limit_info;
|
|
208
|
+
if (info && typeof info === "object" && !Array.isArray(info)) {
|
|
209
|
+
const payload = safeFormatRateLimitPayload(info);
|
|
210
|
+
const blocking = isRateLimitEventBlocking(event);
|
|
211
|
+
const icon = blocking ? "\u{1F6AB}" : "\u2139\uFE0F ";
|
|
212
|
+
return `${tag} ${C.dim}${icon} ${payload}${blocking ? "" : " (non-blocking)"}${C.reset}`;
|
|
213
|
+
}
|
|
214
|
+
return `${tag} ${C.dim}rate_limit_event (no info payload)${C.reset}`;
|
|
215
|
+
}
|
|
206
216
|
case "result": {
|
|
207
217
|
const lines = [];
|
|
208
218
|
if (event.cost_usd !== void 0 || event.total_cost_usd !== void 0) {
|
|
@@ -267,15 +277,6 @@ function clampUserFacingDetail(text, maxChars) {
|
|
|
267
277
|
function stripAnsi(text) {
|
|
268
278
|
return text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
269
279
|
}
|
|
270
|
-
function errorMessageFromUnknown(err) {
|
|
271
|
-
if (typeof err === "string" && err.trim()) return err.trim();
|
|
272
|
-
if (err && typeof err === "object") {
|
|
273
|
-
const o = err;
|
|
274
|
-
if (typeof o.message === "string" && o.message.trim()) return o.message.trim();
|
|
275
|
-
if (typeof o.error === "string" && o.error.trim()) return o.error.trim();
|
|
276
|
-
}
|
|
277
|
-
return null;
|
|
278
|
-
}
|
|
279
280
|
function formatAnthropicRateLimitPayload(o) {
|
|
280
281
|
const rateLimitTypeRaw = typeof o.rateLimitType === "string" ? o.rateLimitType.replace(/_/g, " ") : "unknown";
|
|
281
282
|
const rawReset = o.resetsAt;
|
|
@@ -326,26 +327,16 @@ function parseClaudeNdjsonEvents(rawOutput) {
|
|
|
326
327
|
}
|
|
327
328
|
return events;
|
|
328
329
|
}
|
|
329
|
-
function
|
|
330
|
-
const
|
|
331
|
-
if (!
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
const parts = [];
|
|
335
|
-
for (const block of content) {
|
|
336
|
-
if (!block || typeof block !== "object") continue;
|
|
337
|
-
const b = block;
|
|
338
|
-
if (b.type === "text" && typeof b.text === "string" && b.text.trim()) {
|
|
339
|
-
parts.push(b.text.trim());
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
if (parts.length === 0) return null;
|
|
343
|
-
return parts.join("\n");
|
|
330
|
+
function isRateLimitEventBlocking(ev) {
|
|
331
|
+
const info = ev.rate_limit_info;
|
|
332
|
+
if (!info || typeof info !== "object" || Array.isArray(info)) return true;
|
|
333
|
+
const status = info.status;
|
|
334
|
+
return !(typeof status === "string" && status === "allowed");
|
|
344
335
|
}
|
|
345
|
-
var USAGE_LIMIT_RE = /you['\u2019]?ve hit your limit/i;
|
|
346
336
|
function extractRateLimitRetryInfo(events) {
|
|
347
337
|
for (const ev of events) {
|
|
348
338
|
if (ev.type !== "rate_limit_event") continue;
|
|
339
|
+
if (!isRateLimitEventBlocking(ev)) continue;
|
|
349
340
|
const info = ev.rate_limit_info;
|
|
350
341
|
if (!info || typeof info !== "object" || Array.isArray(info)) continue;
|
|
351
342
|
const raw = info.resetsAt;
|
|
@@ -355,59 +346,23 @@ function extractRateLimitRetryInfo(events) {
|
|
|
355
346
|
}
|
|
356
347
|
return null;
|
|
357
348
|
}
|
|
358
|
-
function
|
|
359
|
-
return USAGE_LIMIT_RE.test(text);
|
|
360
|
-
}
|
|
361
|
-
function extractPlainTextCliErrorFromOutput(rawOutput) {
|
|
362
|
-
const limitMatch = rawOutput.match(new RegExp(USAGE_LIMIT_RE.source + "[^\\n\\r]*", "gi"));
|
|
363
|
-
if (limitMatch?.[0]) return limitMatch[0].trim();
|
|
364
|
-
for (const line of rawOutput.split("\n")) {
|
|
365
|
-
const t = line.trim();
|
|
366
|
-
if (!t || t.startsWith("{")) continue;
|
|
367
|
-
if (/^API error:/i.test(t) || /^Error:/i.test(t) || /^Anthropic/i.test(t)) {
|
|
368
|
-
return t.length <= 2e3 ? t : `${t.slice(0, 1999)}\u2026`;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
return null;
|
|
372
|
-
}
|
|
373
|
-
function deriveClaudeFailureSummary(exitCode, rawOutput, stderr, events) {
|
|
374
|
-
const stderrText = stripAnsi(stderr).trim();
|
|
375
|
-
if (stderrText.length > 0) {
|
|
376
|
-
return clampUserFacingDetail(stderrText, 2e3);
|
|
377
|
-
}
|
|
349
|
+
function deriveClaudeFailureSummary(exitCode, stderr, events) {
|
|
378
350
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
379
351
|
const ev = events[i];
|
|
380
352
|
if (ev.type !== "result") continue;
|
|
381
|
-
if (typeof ev.error === "string" && ev.error.trim()) {
|
|
382
|
-
return clampUserFacingDetail(ev.error.trim(), 2e3);
|
|
383
|
-
}
|
|
384
|
-
const nested = errorMessageFromUnknown(ev.error);
|
|
385
|
-
if (nested) return clampUserFacingDetail(nested, 2e3);
|
|
386
353
|
if (ev.is_error === true && typeof ev.result === "string" && ev.result.trim()) {
|
|
387
354
|
return clampUserFacingDetail(ev.result.trim(), 2e3);
|
|
388
355
|
}
|
|
389
|
-
}
|
|
390
|
-
for (let i = events.length - 1; i >= 0; i--) {
|
|
391
|
-
const ev = events[i];
|
|
392
|
-
if (ev.type !== "error") continue;
|
|
393
|
-
if (typeof ev.message === "string" && ev.message.trim()) {
|
|
394
|
-
return clampUserFacingDetail(ev.message.trim(), 2e3);
|
|
395
|
-
}
|
|
396
356
|
if (typeof ev.error === "string" && ev.error.trim()) {
|
|
397
357
|
return clampUserFacingDetail(ev.error.trim(), 2e3);
|
|
398
358
|
}
|
|
399
|
-
const nested = errorMessageFromUnknown(ev.error);
|
|
400
|
-
if (nested) return clampUserFacingDetail(nested, 2e3);
|
|
401
359
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
if (ev.error === void 0 || ev.error === null || ev.error === "") continue;
|
|
406
|
-
const assistantText = extractAssistantUserVisibleText(ev);
|
|
407
|
-
if (assistantText) return clampUserFacingDetail(assistantText, 2e3);
|
|
360
|
+
const stderrText = stripAnsi(stderr).trim();
|
|
361
|
+
if (stderrText.length > 0) {
|
|
362
|
+
return clampUserFacingDetail(stderrText, 2e3);
|
|
408
363
|
}
|
|
409
364
|
for (const ev of events) {
|
|
410
|
-
if (ev.type === "rate_limit_event") {
|
|
365
|
+
if (ev.type === "rate_limit_event" && isRateLimitEventBlocking(ev)) {
|
|
411
366
|
const info = ev.rate_limit_info;
|
|
412
367
|
if (info && typeof info === "object" && !Array.isArray(info)) {
|
|
413
368
|
return clampUserFacingDetail(
|
|
@@ -417,23 +372,13 @@ function deriveClaudeFailureSummary(exitCode, rawOutput, stderr, events) {
|
|
|
417
372
|
}
|
|
418
373
|
}
|
|
419
374
|
}
|
|
420
|
-
|
|
421
|
-
${rawOutput}`);
|
|
422
|
-
const plain = extractPlainTextCliErrorFromOutput(combined);
|
|
423
|
-
if (plain) return clampUserFacingDetail(plain, 2e3);
|
|
424
|
-
return `Claude Code ended with exit code ${exitCode}. No detailed error was returned; if this persists, run the runner with verbose logging or try again later.`;
|
|
375
|
+
return `Claude Code ended with exit code ${exitCode}`;
|
|
425
376
|
}
|
|
426
|
-
function claudeRunShouldReportAsError(
|
|
427
|
-
if (
|
|
428
|
-
for (
|
|
429
|
-
if (
|
|
430
|
-
if (ev.type === "assistant" && ev.error !== void 0 && ev.error !== null && ev.error !== "") {
|
|
431
|
-
return true;
|
|
432
|
-
}
|
|
433
|
-
if (ev.type === "result" && ev.is_error === true) return true;
|
|
377
|
+
function claudeRunShouldReportAsError(exitCode, events) {
|
|
378
|
+
if (exitCode !== 0) return true;
|
|
379
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
380
|
+
if (events[i].type === "result" && events[i].is_error === true) return true;
|
|
434
381
|
}
|
|
435
|
-
if (isClaudeUsageLimitText(options.resultSummaryText)) return true;
|
|
436
|
-
if (isClaudeUsageLimitText(options.combinedText)) return true;
|
|
437
382
|
return false;
|
|
438
383
|
}
|
|
439
384
|
function compactTaskErrorForApi(detail) {
|
|
@@ -892,23 +837,51 @@ Removing ${safe.length} safe worktree(s)\u2026`);
|
|
|
892
837
|
}
|
|
893
838
|
console.log();
|
|
894
839
|
}
|
|
895
|
-
function logClaudeRunFailureDiagnostics(log,
|
|
896
|
-
const se = stripAnsi(stderr).trimEnd();
|
|
897
|
-
const so = stripAnsi(rawOutput);
|
|
840
|
+
function logClaudeRunFailureDiagnostics(log, opts) {
|
|
841
|
+
const se = stripAnsi(opts.stderr).trimEnd();
|
|
842
|
+
const so = stripAnsi(opts.rawOutput);
|
|
898
843
|
log(`${C.dim}\u2500\u2500 Claude failure diagnostics (--verbose) \u2500\u2500${C.reset}`);
|
|
844
|
+
log(`${C.dim} exit code: ${opts.exitCode}${C.reset}`);
|
|
845
|
+
log(`${C.dim} session: ${opts.sessionId ?? "(none)"}${C.reset}`);
|
|
846
|
+
log(`${C.dim} derived error: ${opts.derivedError}${C.reset}`);
|
|
847
|
+
log(`${C.dim} api error: ${opts.apiError}${C.reset}`);
|
|
848
|
+
const rateLimitEvents = opts.streamEvents.filter((e) => e.type === "rate_limit_event");
|
|
849
|
+
if (rateLimitEvents.length > 0) {
|
|
850
|
+
log(`${C.dim} rate_limit_event(s): ${rateLimitEvents.length}${C.reset}`);
|
|
851
|
+
for (const rl of rateLimitEvents) {
|
|
852
|
+
const info = rl.rate_limit_info;
|
|
853
|
+
const blocking = isRateLimitEventBlocking(rl);
|
|
854
|
+
if (info && typeof info === "object" && !Array.isArray(info)) {
|
|
855
|
+
log(`${C.dim} blocking=${blocking} payload=${JSON.stringify(info)}${C.reset}`);
|
|
856
|
+
} else {
|
|
857
|
+
log(`${C.dim} blocking=${blocking} (no rate_limit_info)${C.reset}`);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
const typeCounts = /* @__PURE__ */ new Map();
|
|
862
|
+
for (const ev of opts.streamEvents) {
|
|
863
|
+
const t = typeof ev.type === "string" ? ev.type : "?";
|
|
864
|
+
typeCounts.set(t, (typeCounts.get(t) ?? 0) + 1);
|
|
865
|
+
}
|
|
866
|
+
if (typeCounts.size > 0) {
|
|
867
|
+
const timeline = Array.from(typeCounts.entries()).map(([t, c]) => c > 1 ? `${t}\xD7${c}` : t).join(", ");
|
|
868
|
+
log(`${C.dim} events: ${opts.streamEvents.length} total (${timeline})${C.reset}`);
|
|
869
|
+
} else {
|
|
870
|
+
log(`${C.dim} events: 0 parsed${C.reset}`);
|
|
871
|
+
}
|
|
899
872
|
if (se.length > 0) {
|
|
900
|
-
log(`${C.dim}stderr:${C.reset}`);
|
|
873
|
+
log(`${C.dim} stderr:${C.reset}`);
|
|
901
874
|
for (const ln of se.split("\n")) {
|
|
902
|
-
log(`${C.dim}
|
|
875
|
+
log(`${C.dim} ${ln}${C.reset}`);
|
|
903
876
|
}
|
|
904
877
|
} else {
|
|
905
|
-
log(`${C.dim}(
|
|
878
|
+
log(`${C.dim} stderr: (empty)${C.reset}`);
|
|
906
879
|
}
|
|
907
880
|
const lines = so.length > 0 ? so.split("\n") : [];
|
|
908
881
|
const tailLines = lines.length > 200 ? lines.slice(-200) : lines;
|
|
909
|
-
log(`${C.dim}stdout: ${lines.length} line(s), showing last ${tailLines.length}${C.reset}`);
|
|
882
|
+
log(`${C.dim} stdout: ${lines.length} line(s), showing last ${tailLines.length}${C.reset}`);
|
|
910
883
|
for (const ln of tailLines) {
|
|
911
|
-
log(`${C.dim}
|
|
884
|
+
log(`${C.dim} ${ln}${C.reset}`);
|
|
912
885
|
}
|
|
913
886
|
log(`${C.dim}\u2500\u2500 end diagnostics \u2500\u2500${C.reset}`);
|
|
914
887
|
}
|
|
@@ -1029,17 +1002,9 @@ async function processRun(client, run, config, worktreeDir, detail) {
|
|
|
1029
1002
|
}
|
|
1030
1003
|
const pullRequestUrl = streamPrUrl ?? await detectPullRequestUrl(effectiveCwd) ?? extractPullRequestUrlFromOutput(stdout) ?? extractPullRequestUrlFromOutput(rawOutput);
|
|
1031
1004
|
const streamEvents = parseClaudeNdjsonEvents(rawOutput);
|
|
1032
|
-
const
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
const usageCap = claudeRunShouldReportAsError({
|
|
1036
|
-
exitCode,
|
|
1037
|
-
events: streamEvents,
|
|
1038
|
-
combinedText: combinedForLimit,
|
|
1039
|
-
resultSummaryText
|
|
1040
|
-
});
|
|
1041
|
-
if (exitCode !== 0 || usageCap) {
|
|
1042
|
-
const detail2 = deriveClaudeFailureSummary(exitCode, rawOutput, stderr, streamEvents);
|
|
1005
|
+
const isError = claudeRunShouldReportAsError(exitCode, streamEvents);
|
|
1006
|
+
if (isError) {
|
|
1007
|
+
const detail2 = deriveClaudeFailureSummary(exitCode, stderr, streamEvents);
|
|
1043
1008
|
const errorMsg = compactTaskErrorForApi(detail2);
|
|
1044
1009
|
if (process.env.GCA_DEBUG_CLAUDE_RAW === "1") {
|
|
1045
1010
|
const stderrDbg = truncateClaudeDebugString(stripAnsi(stderr));
|
|
@@ -1050,7 +1015,6 @@ ${rawOutput}`);
|
|
|
1050
1015
|
taskId: run.taskId,
|
|
1051
1016
|
taskTitle: run.taskTitle,
|
|
1052
1017
|
exitCode,
|
|
1053
|
-
usageCap,
|
|
1054
1018
|
sessionId: sessionId ?? null,
|
|
1055
1019
|
streamPrUrl: streamPrUrl ?? null,
|
|
1056
1020
|
pullRequestUrl: pullRequestUrl ?? null,
|
|
@@ -1062,8 +1026,7 @@ ${rawOutput}`);
|
|
|
1062
1026
|
stdout: stdoutDbg.text,
|
|
1063
1027
|
stdoutMeta: { fullLength: stdoutDbg.fullLength, truncated: stdoutDbg.truncated },
|
|
1064
1028
|
rawOutput: rawDbg.text,
|
|
1065
|
-
rawOutputMeta: { fullLength: rawDbg.fullLength, truncated: rawDbg.truncated }
|
|
1066
|
-
resultTextFromExtract: resultSummaryText || null
|
|
1029
|
+
rawOutputMeta: { fullLength: rawDbg.fullLength, truncated: rawDbg.truncated }
|
|
1067
1030
|
});
|
|
1068
1031
|
}
|
|
1069
1032
|
const retryInfo = extractRateLimitRetryInfo(streamEvents);
|
|
@@ -1087,7 +1050,15 @@ ${rawOutput}`);
|
|
|
1087
1050
|
log(`${C.red} ${line}${C.reset}`);
|
|
1088
1051
|
}
|
|
1089
1052
|
if (config.verbose) {
|
|
1090
|
-
logClaudeRunFailureDiagnostics(log,
|
|
1053
|
+
logClaudeRunFailureDiagnostics(log, {
|
|
1054
|
+
exitCode,
|
|
1055
|
+
sessionId,
|
|
1056
|
+
stderr,
|
|
1057
|
+
rawOutput,
|
|
1058
|
+
streamEvents,
|
|
1059
|
+
derivedError: detail2,
|
|
1060
|
+
apiError: errorMsg
|
|
1061
|
+
});
|
|
1091
1062
|
}
|
|
1092
1063
|
return;
|
|
1093
1064
|
}
|
|
@@ -1540,10 +1511,20 @@ async function main() {
|
|
|
1540
1511
|
await runWithWebSocket(client, config, scheduler);
|
|
1541
1512
|
}
|
|
1542
1513
|
if (!scheduler.isEmpty()) {
|
|
1543
|
-
console.log(
|
|
1544
|
-
|
|
1514
|
+
console.log(
|
|
1515
|
+
`\u23F3 Waiting for ${scheduler.activeTaskCount} in-flight task(s)\u2026 (Ctrl+C to force quit)`
|
|
1516
|
+
);
|
|
1517
|
+
let forceQuit = false;
|
|
1518
|
+
const forceHandler = () => {
|
|
1519
|
+
forceQuit = true;
|
|
1520
|
+
};
|
|
1521
|
+
process.once("SIGINT", forceHandler);
|
|
1522
|
+
process.once("SIGTERM", forceHandler);
|
|
1523
|
+
while (!scheduler.isEmpty() && !forceQuit) {
|
|
1545
1524
|
await sleep(1e3);
|
|
1546
1525
|
}
|
|
1526
|
+
process.removeListener("SIGINT", forceHandler);
|
|
1527
|
+
process.removeListener("SIGTERM", forceHandler);
|
|
1547
1528
|
}
|
|
1548
1529
|
console.log("\u{1F44B} Goodbye.");
|
|
1549
1530
|
}
|