create-claude-workspace 1.1.78 → 1.1.80
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.
|
@@ -321,6 +321,13 @@ export function runClaude(opts, log, runOpts = {}) {
|
|
|
321
321
|
const event = parseStreamEvent(line);
|
|
322
322
|
if (!event) {
|
|
323
323
|
log.debug(`Non-JSON: ${line.slice(0, 200)}`);
|
|
324
|
+
// Check non-JSON stdout lines for error signals (e.g. "You've hit your limit" printed as plain text)
|
|
325
|
+
const lower = line.toLowerCase();
|
|
326
|
+
detectTextSignals(lower);
|
|
327
|
+
// Append to stderr so parseUsageLimitResetMs can find reset time
|
|
328
|
+
if (USAGE_LIMIT_RE.test(lower) || RATE_LIMIT_RE.test(lower) || AUTH_ERROR_RE.test(lower)) {
|
|
329
|
+
stderr += line + '\n';
|
|
330
|
+
}
|
|
324
331
|
continue;
|
|
325
332
|
}
|
|
326
333
|
// Extract session ID
|
|
@@ -400,7 +407,8 @@ export function runClaude(opts, log, runOpts = {}) {
|
|
|
400
407
|
}
|
|
401
408
|
// Flush remaining buffer
|
|
402
409
|
if (buffer.trim()) {
|
|
403
|
-
const
|
|
410
|
+
const cleaned = buffer.replace(/\r/g, '');
|
|
411
|
+
const event = parseStreamEvent(cleaned);
|
|
404
412
|
if (event) {
|
|
405
413
|
if (event.session_id && !sessionId)
|
|
406
414
|
sessionId = event.session_id;
|
|
@@ -409,6 +417,14 @@ export function runClaude(opts, log, runOpts = {}) {
|
|
|
409
417
|
resultReceived = true;
|
|
410
418
|
}
|
|
411
419
|
}
|
|
420
|
+
else {
|
|
421
|
+
// Non-JSON buffer (e.g. "You've hit your limit" without trailing newline)
|
|
422
|
+
const lower = cleaned.toLowerCase();
|
|
423
|
+
detectTextSignals(lower);
|
|
424
|
+
if (USAGE_LIMIT_RE.test(lower) || RATE_LIMIT_RE.test(lower) || AUTH_ERROR_RE.test(lower)) {
|
|
425
|
+
stderr += cleaned + '\n';
|
|
426
|
+
}
|
|
427
|
+
}
|
|
412
428
|
}
|
|
413
429
|
// Final stderr-based detection
|
|
414
430
|
detectTextSignals(stderr.toLowerCase());
|
|
@@ -424,6 +440,7 @@ export function runClaude(opts, log, runOpts = {}) {
|
|
|
424
440
|
timedOut: killReason === 'process_timeout',
|
|
425
441
|
activityTimedOut: killReason === 'activity_timeout',
|
|
426
442
|
hasResult: resultReceived,
|
|
443
|
+
durationMs,
|
|
427
444
|
});
|
|
428
445
|
if (resolved)
|
|
429
446
|
return;
|
|
@@ -49,7 +49,10 @@ export function classifyError(signals) {
|
|
|
49
49
|
return category;
|
|
50
50
|
}
|
|
51
51
|
// Exit code 1 with no error indicators → likely max-turns
|
|
52
|
-
if
|
|
52
|
+
// But only if process ran long enough (>10s). Instant exit (e.g. usage limit printed
|
|
53
|
+
// as plain text that wasn't caught) should NOT be classified as max-turns to prevent
|
|
54
|
+
// infinite restart loops with no backoff.
|
|
55
|
+
if (signals.code === 1 && stderr.trim().length < 50 && (signals.durationMs ?? 60_000) > 10_000)
|
|
53
56
|
return 'max_turns';
|
|
54
57
|
// Non-zero exit with content
|
|
55
58
|
if (signals.code !== null && signals.code !== 0)
|
|
@@ -56,8 +56,11 @@ describe('classifyError', () => {
|
|
|
56
56
|
it('detects context exhaustion from stderr', () => {
|
|
57
57
|
expect(classifyError({ ...base, code: 1, stderr: 'context_length_exceeded' })).toBe('context_exhausted');
|
|
58
58
|
});
|
|
59
|
-
it('classifies exit code 1 with minimal stderr as max_turns', () => {
|
|
60
|
-
expect(classifyError({ ...base, code: 1, stderr: '' })).toBe('max_turns');
|
|
59
|
+
it('classifies exit code 1 with minimal stderr as max_turns when process ran >10s', () => {
|
|
60
|
+
expect(classifyError({ ...base, code: 1, stderr: '', durationMs: 60_000 })).toBe('max_turns');
|
|
61
|
+
});
|
|
62
|
+
it('classifies instant exit (code 1, <10s, no stderr) as cli_crash not max_turns', () => {
|
|
63
|
+
expect(classifyError({ ...base, code: 1, stderr: '', durationMs: 1_000 })).toBe('cli_crash');
|
|
61
64
|
});
|
|
62
65
|
it('classifies exit code 1 with content as cli_crash', () => {
|
|
63
66
|
expect(classifyError({ ...base, code: 1, stderr: 'Some unexpected internal error happened during execution blah blah' })).toBe('cli_crash');
|
|
@@ -674,9 +674,12 @@ describe('Loop integration: classifyError → getErrorAction → checkpoint', ()
|
|
|
674
674
|
expect(classifyError({ ...base, code: 1, stderr: 'ENOTFOUND' })).toBe('network_error');
|
|
675
675
|
expect(classifyError({ ...base, code: 1, stderr: 'context_length_exceeded' })).toBe('context_exhausted');
|
|
676
676
|
});
|
|
677
|
-
it('exit 1 with minimal stderr → max_turns', () => {
|
|
678
|
-
expect(classifyError({ ...base, code: 1, stderr: '' })).toBe('max_turns');
|
|
679
|
-
expect(classifyError({ ...base, code: 1, stderr: 'short' })).toBe('max_turns');
|
|
677
|
+
it('exit 1 with minimal stderr and long duration → max_turns', () => {
|
|
678
|
+
expect(classifyError({ ...base, code: 1, stderr: '', durationMs: 60_000 })).toBe('max_turns');
|
|
679
|
+
expect(classifyError({ ...base, code: 1, stderr: 'short', durationMs: 60_000 })).toBe('max_turns');
|
|
680
|
+
});
|
|
681
|
+
it('exit 1 with minimal stderr and instant exit → cli_crash (not max_turns)', () => {
|
|
682
|
+
expect(classifyError({ ...base, code: 1, stderr: '', durationMs: 2_000 })).toBe('cli_crash');
|
|
680
683
|
});
|
|
681
684
|
it('exit 1 with long stderr → cli_crash', () => {
|
|
682
685
|
expect(classifyError({ ...base, code: 1, stderr: 'a'.repeat(60) })).toBe('cli_crash');
|