Skip to content

Commit 420c3fa

Browse files
fix: prevent placeholders from overwriting real checkpoints
ProviderRuntimeIngestion fires multiple turn.diff.updated events per turn. Each dispatches a placeholder checkpoint that the projector would blindly replace by turnId, clobbering the real git capture from CheckpointReactor. Also, using thread.checkpoints.length + 1 for checkpointTurnCount produced unstable values after dedup. Two fixes: 1. Projector: skip incoming "missing" checkpoints when a non-placeholder entry already exists for that turnId 2. ProviderRuntimeIngestion: skip placeholder dispatch entirely if a checkpoint already exists for the turnId, and use max turnCount + 1 instead of length + 1
1 parent 1ef7d20 commit 420c3fa

2 files changed

Lines changed: 38 additions & 16 deletions

File tree

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

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,22 +1064,34 @@ const make = Effect.gen(function* () {
10641064
if (event.type === "turn.diff.updated") {
10651065
const turnId = toTurnId(event.turnId);
10661066
if (turnId && (yield* isGitRepoForThread(thread.id))) {
1067-
const assistantMessageId = MessageId.makeUnsafe(
1068-
`assistant:${event.itemId ?? event.turnId ?? event.eventId}`,
1069-
);
1070-
yield* orchestrationEngine.dispatch({
1071-
type: "thread.turn.diff.complete",
1072-
commandId: providerCommandId(event, "thread-turn-diff-complete"),
1073-
threadId: thread.id,
1074-
turnId,
1075-
completedAt: now,
1076-
checkpointRef: CheckpointRef.makeUnsafe(`provider-diff:${event.eventId}`),
1077-
status: "missing",
1078-
files: [],
1079-
assistantMessageId,
1080-
checkpointTurnCount: thread.checkpoints.length + 1,
1081-
createdAt: now,
1082-
});
1067+
// Skip if a checkpoint already exists for this turn. A real
1068+
// (non-placeholder) capture from CheckpointReactor should not
1069+
// be clobbered, and dispatching a duplicate placeholder for the
1070+
// same turnId would produce an unstable checkpointTurnCount.
1071+
if (thread.checkpoints.some((c) => c.turnId === turnId)) {
1072+
// Already tracked; no-op.
1073+
} else {
1074+
const assistantMessageId = MessageId.makeUnsafe(
1075+
`assistant:${event.itemId ?? event.turnId ?? event.eventId}`,
1076+
);
1077+
const maxTurnCount = thread.checkpoints.reduce(
1078+
(max, c) => Math.max(max, c.checkpointTurnCount),
1079+
0,
1080+
);
1081+
yield* orchestrationEngine.dispatch({
1082+
type: "thread.turn.diff.complete",
1083+
commandId: providerCommandId(event, "thread-turn-diff-complete"),
1084+
threadId: thread.id,
1085+
turnId,
1086+
completedAt: now,
1087+
checkpointRef: CheckpointRef.makeUnsafe(`provider-diff:${event.eventId}`),
1088+
status: "missing",
1089+
files: [],
1090+
assistantMessageId,
1091+
checkpointTurnCount: maxTurnCount + 1,
1092+
createdAt: now,
1093+
});
1094+
}
10831095
}
10841096
}
10851097

apps/server/src/orchestration/projector.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,16 @@ export function projectEvent(
500500
"checkpoint",
501501
);
502502

503+
// Do not let a placeholder (status "missing") overwrite a checkpoint
504+
// that has already been captured with a real git ref (status "ready").
505+
// ProviderRuntimeIngestion may fire multiple turn.diff.updated events
506+
// per turn; without this guard later placeholders would clobber the
507+
// real capture dispatched by CheckpointReactor.
508+
const existing = thread.checkpoints.find((entry) => entry.turnId === checkpoint.turnId);
509+
if (existing && existing.status !== "missing" && checkpoint.status === "missing") {
510+
return nextBase;
511+
}
512+
503513
const checkpoints = [
504514
...thread.checkpoints.filter((entry) => entry.turnId !== checkpoint.turnId),
505515
checkpoint,

0 commit comments

Comments
 (0)