fix: recover missing Reasonix sessions

This commit is contained in:
liujing
2026-07-17 17:02:49 +08:00
parent fb39ab289a
commit 717d4344ee
6 changed files with 179 additions and 8 deletions
+29 -4
View File
@@ -23,7 +23,12 @@ import { execFileSync } from "node:child_process";
import { resolveWorkflowConfig, validateHostDecision, validateWorkflowPlan, WorkflowConfigError } from "./workflow-config.js";
import { checkConvergence, decisionSignatures } from "./workflow-convergence.js";
import type { FindingSignature } from "./workflow-convergence.js";
import { createExecutorAdapter, probeExecutor, validateClaudeSessionForWorkflow } from "./workflow-executor.js";
import {
createExecutorAdapter,
findReasonixSessionForWorkflow,
probeExecutor,
validateClaudeSessionForWorkflow,
} from "./workflow-executor.js";
import {
WorkflowLockError,
acquireLock,
@@ -1269,7 +1274,8 @@ export async function retryExecuteWorkflow(opts: RetryExecuteWorkflowOpts): Prom
const retryableFailure = staleExecution || (state.status === "blocked"
&& (state.stopDescription?.startsWith("Executor exited with failure:") === true
|| state.stopDescription?.startsWith("Executor failed to resume:") === true
|| state.stopDescription?.startsWith("Executor resume failed:") === true));
|| state.stopDescription?.startsWith("Executor resume failed:") === true
|| state.stopDescription === "No session handle available for resume."));
if (!retryableFailure) {
throw new WorkflowEngineError(
`Run '${opts.runId}' cannot retry execution from status '${state.status}'. Only failed executor runs with a resumable session are eligible.`,
@@ -1282,7 +1288,15 @@ export async function retryExecuteWorkflow(opts: RetryExecuteWorkflowOpts): Prom
if (!cycle || cycle.diffFile || cycle.hostReviewFile || cycle.decisionFile || cycle.scopeViolations) {
throw new WorkflowEngineError("Cannot retry execution: the failed cycle already contains review artifacts.", 4);
}
if (!state.sessionHandle && !opts.sessionId) {
const recoveredReasonixSession = !state.sessionHandle && state.executor === "reasonix"
? findReasonixSessionForWorkflow(
state.repoRoot,
state.plan.title,
previousCycle?.startedAt ?? cycle.startedAt,
previousCycle?.completedAt,
)
: undefined;
if (!state.sessionHandle && !opts.sessionId && !recoveredReasonixSession) {
throw new WorkflowEngineError("Cannot retry execution: no executor session handle is available.", 4);
}
const recoveringInitialCycle = state.currentCycle === 1;
@@ -1365,6 +1379,8 @@ export async function retryExecuteWorkflow(opts: RetryExecuteWorkflowOpts): Prom
: undefined;
if (opts.sessionId) {
state.sessionHandle = { kind: "claude", sessionId: opts.sessionId };
} else if (recoveredReasonixSession) {
state.sessionHandle = { kind: "reasonix", sessionFilePath: recoveredReasonixSession };
}
state.status = "executing";
@@ -1382,6 +1398,14 @@ export async function retryExecuteWorkflow(opts: RetryExecuteWorkflowOpts): Prom
cycleIndex: state.currentCycle,
data: { previousSessionId, sessionId: opts.sessionId },
});
} else if (recoveredReasonixSession) {
appendEvent(dir, {
kind: "executor_session_rebound",
runId: state.runId,
timestamp: state.updatedAt,
cycleIndex: state.currentCycle,
data: { sessionFilePath: recoveredReasonixSession, recovery: "reasonix_project_session" },
});
}
// Determine recovery mode: stale_execution trumps initial_continuation
@@ -1405,7 +1429,8 @@ export async function retryExecuteWorkflow(opts: RetryExecuteWorkflowOpts): Prom
},
});
process.stdout.write(`retrying executor cycle ${state.currentCycle} with the ${opts.sessionId ? "rebound" : "existing"} ${state.executor} session\n`);
const sessionMode = opts.sessionId || recoveredReasonixSession ? "recovered" : "existing";
process.stdout.write(`retrying executor cycle ${state.currentCycle} with the ${sessionMode} ${state.executor} session\n`);
await resumeExecutorCycle(state, dir, cycle, acceptedFindings);
}