Skip to content

Commit ea7ac4e

Browse files
authored
Merge pull request #26 from asfires/t3code/diagnose-rollback-history-error
fix(server): silence legacy retained-boundary retraction failures
2 parents df49b7d + 872aec9 commit ea7ac4e

2 files changed

Lines changed: 76 additions & 4 deletions

File tree

apps/server/src/orchestration/ActivityPayloadProjection.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,45 @@ describe("projectActivityPayload agent-field survival", () => {
115115
expect(projected.payload).toEqual(source.payload);
116116
});
117117
});
118+
119+
describe("projectActivityPayload legacy retraction compatibility", () => {
120+
function retractionFailure(detail: string): OrchestrationThreadActivity {
121+
return {
122+
id: "retraction-failure-1",
123+
tone: "error",
124+
kind: "turn.retract.failed",
125+
summary: "Message retract failed",
126+
payload: {
127+
requestId: "request-1",
128+
stage: "provider-rollback",
129+
retryable: false,
130+
detail,
131+
},
132+
turnId: "turn-1",
133+
createdAt: "2026-08-12T18:49:47.397Z",
134+
} as unknown as OrchestrationThreadActivity;
135+
}
136+
137+
it("marks replayed unavailable-boundary failures silent", () => {
138+
const projected = projectActivityPayload(
139+
retractionFailure(
140+
"Provider adapter validation failed (claudeAgent) in rollbackThreadTo: Provider history has 3 turns, below retained boundary 11.",
141+
),
142+
);
143+
144+
expect(projected.payload).toMatchObject({
145+
requestId: "request-1",
146+
detail:
147+
"Provider adapter validation failed (claudeAgent) in rollbackThreadTo: Provider history has 3 turns, below retained boundary 11.",
148+
silent: true,
149+
});
150+
});
151+
152+
it("keeps unrelated retraction failures visible", () => {
153+
const source = retractionFailure(
154+
"Provider adapter validation failed (claudeAgent) in rollbackThreadTo: retainedTurnCount must be an integer >= 0.",
155+
);
156+
157+
expect(projectActivityPayload(source)).toBe(source);
158+
});
159+
});

apps/server/src/orchestration/ActivityPayloadProjection.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,35 @@ function asTrimmedString(value: unknown): string | null {
1818
return trimmed.length > 0 ? trimmed : null;
1919
}
2020

21+
const UNAVAILABLE_RETAINED_BOUNDARY_DETAIL =
22+
/^Provider adapter validation failed \([^)]+\) in rollbackThreadTo: Provider history has \d+ turns, below retained boundary \d+\.$/;
23+
24+
function normalizeLegacyRetractionFailure(
25+
activity: OrchestrationThreadActivity,
26+
): OrchestrationThreadActivity {
27+
const payload = asRecord(activity.payload);
28+
if (
29+
activity.kind !== "turn.retract.failed" ||
30+
!payload ||
31+
typeof payload.detail !== "string" ||
32+
!UNAVAILABLE_RETAINED_BOUNDARY_DETAIL.test(payload.detail)
33+
) {
34+
return activity;
35+
}
36+
37+
// Boundary failures emitted before stale Esc retractions became silent are
38+
// immutable event history. Normalize them at the wire boundary so every
39+
// projection rebuild keeps the compatibility behavior without rewriting
40+
// the event store or the persisted activity payload.
41+
return {
42+
...activity,
43+
payload: {
44+
...payload,
45+
silent: true,
46+
},
47+
};
48+
}
49+
2150
export const MAX_PROJECTED_TOOL_RESULT_CHARS = 50_000;
2251

2352
const PROJECTED_TOOL_RESULT_TRUNCATION_MARKER = "…[truncated]";
@@ -326,15 +355,16 @@ function projectMcpToolCallData(data: Record<string, unknown>): Record<string, u
326355
export function projectActivityPayload(
327356
activity: OrchestrationThreadActivity,
328357
): OrchestrationThreadActivity {
329-
const payload = asRecord(activity.payload);
358+
const normalizedActivity = normalizeLegacyRetractionFailure(activity);
359+
const payload = asRecord(normalizedActivity.payload);
330360
const data = asRecord(payload?.data);
331361
if (!payload || !data) {
332-
return activity;
362+
return normalizedActivity;
333363
}
334364

335365
if (payload.itemType === "mcp_tool_call") {
336366
return {
337-
...activity,
367+
...normalizedActivity,
338368
payload: {
339369
...payload,
340370
data: projectMcpToolCallData(data),
@@ -370,7 +400,7 @@ export function projectActivityPayload(
370400
}
371401

372402
return {
373-
...activity,
403+
...normalizedActivity,
374404
payload: {
375405
...payload,
376406
data: projectedData,

0 commit comments

Comments
 (0)