Skip to content

Commit ae5d763

Browse files
wiidzcursoragent
andcommitted
fix(sessions): align reply init revision with persisted skillsSnapshot
Compare reply session initialization revisions using the on-disk skillsSnapshot shape (promptRef) instead of hydrated writer-cache prompt text, which caused false stale-snapshot conflicts on the second inbound turn. Also await inbound meta recording before last-route updates to avoid concurrent session store writes. Fixes #96698 Co-authored-by: Cursor <[email protected]>
1 parent a96418c commit ae5d763

2 files changed

Lines changed: 23 additions & 3 deletions

File tree

src/channels/session.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export async function recordInboundSession(params: {
5555
})
5656
.catch(params.onRecordError);
5757
params.trackSessionMetaTask?.(metaTask);
58-
void metaTask;
58+
await metaTask;
5959

6060
const update = params.updateLastRoute;
6161
if (!update) {

src/config/sessions/session-accessor.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1381,7 +1381,14 @@ export function loadReplySessionInitializationSnapshot(params: {
13811381
storePath: string;
13821382
sessionKey: string;
13831383
}): ReplySessionInitializationSnapshot {
1384-
const store = loadSessionStore(params.storePath, { skipCache: true, clone: false });
1384+
// Use persisted shape (skills + promptRef), not hydrated in-memory prompt text.
1385+
// Writer-cache loads hydrate prompt blobs; comparing hydrated vs persisted revisions
1386+
// causes false stale-snapshot conflicts on the second inbound turn.
1387+
const store = loadSessionStore(params.storePath, {
1388+
skipCache: true,
1389+
clone: false,
1390+
hydrateSkillPromptRefs: false,
1391+
});
13851392
const resolved = resolveSessionStoreEntry({ store, sessionKey: params.sessionKey });
13861393
const currentEntry = resolved.existing ? { ...resolved.existing } : undefined;
13871394
const entries = cloneSessionEntries(store);
@@ -1422,7 +1429,20 @@ export async function commitReplySessionInitialization(params: {
14221429
async (store): Promise<ReplySessionInitializationCommitResult> => {
14231430
const resolved = resolveSessionStoreEntry({ store, sessionKey: params.sessionKey });
14241431
const currentEntry = resolved.existing ? { ...resolved.existing } : undefined;
1425-
const revision = createReplySessionInitializationRevision(currentEntry);
1432+
// Writer `store` may carry hydrated skillsSnapshot.prompt; revision must use the
1433+
// same persisted shape as loadReplySessionInitializationSnapshot().
1434+
const persistedStore = loadSessionStore(params.storePath, {
1435+
skipCache: true,
1436+
clone: false,
1437+
hydrateSkillPromptRefs: false,
1438+
});
1439+
const persistedEntry = resolveSessionStoreEntry({
1440+
store: persistedStore,
1441+
sessionKey: params.sessionKey,
1442+
}).existing;
1443+
const revision = createReplySessionInitializationRevision(
1444+
persistedEntry ? { ...persistedEntry } : undefined,
1445+
);
14261446
if (revision !== params.expectedRevision) {
14271447
return {
14281448
ok: false,

0 commit comments

Comments
 (0)