fix(ralph-loop): clear orphaned state when original session no longer exists (#446)

* fix(ralph-loop): clear orphaned state when original session no longer exists

When a session with an active ralph-loop terminates abnormally (abort, window close),
the state file remains with active: true. Previously, when a new session started,
the hook would skip the orphaned state without cleaning it up.

This fix adds session existence validation:
- Before skipping a loop owned by a different session, check if that session still exists
- If the original session no longer exists, clear the orphan state and log
- If the original session still exists, skip as before (it's another active session's loop)

Changes:
- Add checkSessionExists option to RalphLoopOptions for dependency injection
- Wire up sessionExists from session-manager as the default implementation
- Add tests for orphan state cleanup and active session preservation

* fix(ralph-loop): add error handling around checkSessionExists call

Wraps the async checkSessionExists call in try/catch for consistency
with other async operations in this file. If the check throws, logs
the error and falls back to the original behavior (not clearing state).

---------

Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
This commit is contained in:
Victor Jaepyo Jo
2026-01-03 14:11:59 +09:00
committed by GitHub
parent 00b8f622d5
commit 95645effd7
5 changed files with 98 additions and 1 deletions
+19
View File
@@ -64,6 +64,7 @@ export function createRalphLoopHook(
const stateDir = config?.state_dir
const getTranscriptPath = options?.getTranscriptPath ?? getDefaultTranscriptPath
const apiTimeout = options?.apiTimeout ?? DEFAULT_API_TIMEOUT
const checkSessionExists = options?.checkSessionExists
function getSessionState(sessionID: string): SessionState {
let state = sessions.get(sessionID)
@@ -199,6 +200,24 @@ export function createRalphLoopHook(
}
if (state.session_id && state.session_id !== sessionID) {
if (checkSessionExists) {
try {
const originalSessionExists = await checkSessionExists(state.session_id)
if (!originalSessionExists) {
clearState(ctx.directory, stateDir)
log(`[${HOOK_NAME}] Cleared orphaned state from deleted session`, {
orphanedSessionId: state.session_id,
currentSessionId: sessionID,
})
return
}
} catch (err) {
log(`[${HOOK_NAME}] Failed to check session existence`, {
sessionId: state.session_id,
error: String(err),
})
}
}
return
}