Skip to content

Commit d18e43b

Browse files
fix: lost provider session recovery (#1938)
1 parent 6f69934 commit d18e43b

2 files changed

Lines changed: 57 additions & 2 deletions

File tree

apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,6 +1241,61 @@ describe("ProviderCommandReactor", () => {
12411241
});
12421242
});
12431243

1244+
it("starts a fresh session when only projected session state exists", async () => {
1245+
const harness = await createHarness();
1246+
const now = new Date().toISOString();
1247+
1248+
await Effect.runPromise(
1249+
harness.engine.dispatch({
1250+
type: "thread.session.set",
1251+
commandId: CommandId.make("cmd-session-set-stale"),
1252+
threadId: ThreadId.make("thread-1"),
1253+
session: {
1254+
threadId: ThreadId.make("thread-1"),
1255+
status: "ready",
1256+
providerName: "codex",
1257+
runtimeMode: "approval-required",
1258+
activeTurnId: null,
1259+
lastError: null,
1260+
updatedAt: now,
1261+
},
1262+
createdAt: now,
1263+
}),
1264+
);
1265+
1266+
await Effect.runPromise(
1267+
harness.engine.dispatch({
1268+
type: "thread.turn.start",
1269+
commandId: CommandId.make("cmd-turn-start-stale"),
1270+
threadId: ThreadId.make("thread-1"),
1271+
message: {
1272+
messageId: asMessageId("user-message-stale"),
1273+
role: "user",
1274+
text: "resume codex",
1275+
attachments: [],
1276+
},
1277+
interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
1278+
runtimeMode: "approval-required",
1279+
createdAt: now,
1280+
}),
1281+
);
1282+
1283+
await waitFor(() => harness.startSession.mock.calls.length === 1);
1284+
await waitFor(() => harness.sendTurn.mock.calls.length === 1);
1285+
1286+
expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({
1287+
threadId: ThreadId.make("thread-1"),
1288+
modelSelection: {
1289+
provider: "codex",
1290+
model: "gpt-5-codex",
1291+
},
1292+
runtimeMode: "approval-required",
1293+
});
1294+
expect(harness.sendTurn.mock.calls[0]?.[0]).toMatchObject({
1295+
threadId: ThreadId.make("thread-1"),
1296+
});
1297+
});
1298+
12441299
it("reacts to thread.approval.respond by forwarding provider approval response", async () => {
12451300
const harness = await createHarness();
12461301
const now = new Date().toISOString();

apps/server/src/orchestration/Layers/ProviderCommandReactor.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,14 +285,14 @@ const make = Effect.gen(function* () {
285285
createdAt,
286286
});
287287

288+
const activeSession = yield* resolveActiveSession(threadId);
288289
const existingSessionThreadId =
289-
thread.session && thread.session.status !== "stopped" ? thread.id : null;
290+
thread.session && thread.session.status !== "stopped" && activeSession ? thread.id : null;
290291
if (existingSessionThreadId) {
291292
const runtimeModeChanged = thread.runtimeMode !== thread.session?.runtimeMode;
292293
const providerChanged =
293294
requestedModelSelection !== undefined &&
294295
requestedModelSelection.provider !== currentProvider;
295-
const activeSession = yield* resolveActiveSession(existingSessionThreadId);
296296
const sessionModelSwitch =
297297
currentProvider === undefined
298298
? "in-session"

0 commit comments

Comments
 (0)