-
Notifications
You must be signed in to change notification settings - Fork 13
Description
Severity: High
Category: Logic Bug
Files: src/session.ts, src/ralph-loop.ts
Description
session.sendInput() launches runPrompt() as a detached, unawaited promise and returns immediately. When the Ralph Loop calls await session.sendInput(task.prompt), it receives a resolved promise the instant the input is dispatched — not when Claude finishes. If runPrompt() subsequently fails, the error is caught inside sendInput() and emitted as a generic 'error' event on the session. By then, assignTaskToSession() has already returned successfully and its catch block is unreachable.
Code
src/session.ts:2121–2128:
async sendInput(input: string): Promise<void> {
this._status = 'busy';
this._lastActivityAt = Date.now();
this.runPrompt(input).catch(err => {
const errorMsg = err instanceof Error ? err.message : String(err);
this.emit('error', errorMsg);
// task.fail() never called
// session.clearTask() never called
});
// returns here — caller thinks everything is fine
}src/ralph-loop.ts:314–329:
private async assignTaskToSession(task: Task, session: Session): Promise<void> {
try {
task.assign(session.id);
session.assignTask(task.id);
await session.sendInput(task.prompt); // resolves immediately
this.emit('taskAssigned', task.id, session.id);
} catch (err) {
task.fail(getErrorMessage(err)); // never reached on runPrompt failure
session.clearTask();
...
}
}Impact
When runPrompt() fails asynchronously (Claude CLI unavailable, spawn error, PTY crash):
taskis already markedin_progresswithtask.assign().sessionis already markedbusywithsession.assignTask().- Neither is ever cleaned up by the error path.
- The task stays
in_progressuntilcheckTimeouts()fires — up to the full task timeout (potentially hours). - The session is unavailable to the Ralph Loop for the entire timeout window.
This makes the system effectively unusable for the affected session until the timeout expires, with no visible error in the UI.