fix: add 10s fallback timeout for /clear step in respawn controller

After sending /clear, if no prompt indicator is detected within 10 seconds,
proceed to send /init anyway. This fixes respawn getting stuck when Claude
doesn't show the expected prompt characters after /clear completes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-01-21 18:19:22 +01:00
co-authored by Claude Opus 4.5
parent ae7c003089
commit 6bfc7f9027
+31 -1
View File
@@ -314,6 +314,12 @@ export class RespawnController extends EventEmitter {
/** Reference to terminal event handler (for cleanup) */ /** Reference to terminal event handler (for cleanup) */
private terminalHandler: ((data: string) => void) | null = null; private terminalHandler: ((data: string) => void) | null = null;
/** Timer for /clear step fallback (sends /init if no prompt detected) */
private clearFallbackTimer: NodeJS.Timeout | null = null;
/** Fallback timeout for /clear step (ms) - sends /init without waiting for prompt */
private static readonly CLEAR_FALLBACK_TIMEOUT_MS = 10000;
/** /**
* Patterns indicating Claude is ready for input. * Patterns indicating Claude is ready for input.
* Primary: `↵ send` (suggestion prompt) * Primary: `↵ send` (suggestion prompt)
@@ -604,6 +610,11 @@ export class RespawnController extends EventEmitter {
*/ */
private checkClearComplete(): void { private checkClearComplete(): void {
this.clearIdleTimer(); this.clearIdleTimer();
// Clear the fallback timer since we got prompt detection
if (this.clearFallbackTimer) {
clearTimeout(this.clearFallbackTimer);
this.clearFallbackTimer = null;
}
this.log('/clear completed (ready indicator)'); this.log('/clear completed (ready indicator)');
this.emit('stepCompleted', 'clear'); this.emit('stepCompleted', 'clear');
@@ -724,13 +735,17 @@ export class RespawnController extends EventEmitter {
} }
} }
/** Clear all timers (idle and step) */ /** Clear all timers (idle, step, and clear fallback) */
private clearTimers(): void { private clearTimers(): void {
this.clearIdleTimer(); this.clearIdleTimer();
if (this.stepTimer) { if (this.stepTimer) {
clearTimeout(this.stepTimer); clearTimeout(this.stepTimer);
this.stepTimer = null; this.stepTimer = null;
} }
if (this.clearFallbackTimer) {
clearTimeout(this.clearFallbackTimer);
this.clearFallbackTimer = null;
}
} }
/** /**
@@ -772,6 +787,8 @@ export class RespawnController extends EventEmitter {
/** /**
* Send /clear command. * Send /clear command.
* Starts a 10-second fallback timer - if no prompt is detected after /clear,
* proceeds to /init anyway (workaround for when Claude doesn't show prompt after /clear).
* @fires stepSent - With step 'clear' * @fires stepSent - With step 'clear'
*/ */
private sendClear(): void { private sendClear(): void {
@@ -784,6 +801,19 @@ export class RespawnController extends EventEmitter {
this.emit('stepSent', 'clear', '/clear'); this.emit('stepSent', 'clear', '/clear');
this.setState('waiting_clear'); this.setState('waiting_clear');
this.promptDetected = false; this.promptDetected = false;
// Start fallback timer - if no prompt detected after 10s, proceed to /init anyway
this.clearFallbackTimer = setTimeout(() => {
if (this._state === 'waiting_clear') {
this.log('/clear fallback: no prompt detected after 10s, proceeding to /init');
this.emit('stepCompleted', 'clear');
if (this.config.sendInit) {
this.sendInit();
} else {
this.completeCycle();
}
}
}, RespawnController.CLEAR_FALLBACK_TIMEOUT_MS);
}, this.config.interStepDelayMs); }, this.config.interStepDelayMs);
} }