Skip to content

Commit a80c481

Browse files
insertion-normalization: never strip the tail — a final-message duplicate is payload (insertion slice of fork e0f8fcb)
Path-scoped slice of fork commit e0f8fcb: the insertion-normalization tail guard and its tests. The same commit's output-guard assistant-terminal invariant (proxy/extensions/output-guard.mjs, test/output-guard.test.mjs) belongs to the output-guard slice (cnighswonger#278) and is not part of this PR. Co-Authored-By: Claude opus-5 <noreply@anthropic.com>
1 parent a270da0 commit a80c481

3 files changed

Lines changed: 98 additions & 3 deletions

File tree

proxy/extensions/insertion-normalization.mjs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -854,11 +854,24 @@ export function classifyPinned(messages, priorCanonical) {
854854
// findSuppressibleDuplicate returns null, the entry is untouched here,
855855
// and whatever the existing rules above already decided (append/splice/
856856
// edit-shaped reset) stands — no new reset path is introduced.
857+
// TAIL GUARD (BACKLOG.md, "suppression can strip a request's FINAL
858+
// message", 2026-07-30). Three real 400s ("must end with a user
859+
// message"): report-enforcer injects identical instruction bytes at
860+
// every SubagentStop; the first occurrence gets pinned, and when the
861+
// SAME bytes arrive again as the resume request's ONLY/new final
862+
// message, suppressing it left the forwarded array ending on the prior
863+
// assistant turn. A tail-position duplicate is never a stray migration
864+
// copy of already-pinned content — CC just sent it as the live,
865+
// load-bearing final entry of THIS request, and the model needs to see
866+
// it. Applies uniformly to both single-block and join-hash matches: the
867+
// guard is positional, not about which hash set matched.
868+
const lastIdx = messages.length - 1;
857869
const pinnedHashes = pinnedBlockHashes(priorCanonical);
858870
const pinnedJoin = pinnedJoinHashes(priorCanonical);
859871
const suppressions = [];
860872
for (const e of newEntries) {
861873
if (e.r === "assistant") continue;
874+
if (e.index === lastIdx) continue;
862875
const h = findSuppressibleDuplicate(messages[e.index], pinnedHashes, pinnedJoin);
863876
if (h !== null) suppressions.push({ index: e.index, hash: h });
864877
}

test/insertion-merge-suppression.test.mjs

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,19 +100,54 @@ test("GREEN: the real merged standalone matches the join-hash of its pinned entr
100100
assert.notEqual(h, null, "the real merged standalone must be recognized as a suppressible duplicate");
101101
});
102102

103-
test("classifyPinned end-to-end: the real merged standalone is suppressed as a new entry, not forwarded twice", () => {
103+
test("classifyPinned end-to-end: the real merged standalone is suppressed as a new entry, not forwarded twice (MID-HISTORY — matches the real capture, which had dozens of messages after msg864)", () => {
104104
const toolUseId = REAL_MSG863.content[0].tool_use_id;
105105
const canon = pinCanon([assistantToolUse(toolUseId), REAL_MSG863]);
106106

107-
const messages = [assistantToolUse(toolUseId), REAL_MSG863, { ...REAL_MERGED_STANDALONE }];
107+
// A trailing turn after the standalone, so it sits at a genuine
108+
// mid-history position (index 2 of 4) rather than the array's final
109+
// index — the real capture had ~57 more messages after msg864. The
110+
// tail-guard test below covers the DIFFERENT real incident where the
111+
// duplicate IS the final message.
112+
const messages = [
113+
assistantToolUse(toolUseId),
114+
REAL_MSG863,
115+
{ ...REAL_MERGED_STANDALONE },
116+
{ role: "assistant", content: [{ type: "text", text: "a-after" }] },
117+
];
108118
const result = classifyPinned(messages, canon);
109119

110120
assert.equal(result.suppressed, 1, "the merged standalone must be counted as a suppression");
111121
assert.equal(result.suppressions.length, 1);
112122
assert.equal(result.suppressions[0].index, 2);
113123
// The pinned inline form (index 1) already carries both reminders; the
114124
// standalone must not also appear in the forwarded array.
115-
assert.equal(result.messages.length, 2, "the standalone must not be forwarded alongside the pinned inline form");
125+
assert.equal(result.messages.length, 3, "the standalone must not be forwarded alongside the pinned inline form");
126+
});
127+
128+
// =====================================================================
129+
// TAIL GUARD (BACKLOG.md, "suppression can strip a request's FINAL
130+
// message", 2026-07-30) — the join-hash-specific case. Three real 400s
131+
// traced to suppression removing a resume request's ONLY/new final
132+
// message, leaving the forwarded array ending on the prior assistant
133+
// turn -> upstream "must end with a user message". A tail-position
134+
// duplicate is the request's live payload, not a stray migration copy.
135+
// =====================================================================
136+
137+
test("TAIL GUARD: the real merged standalone as the FINAL message is never suppressed", () => {
138+
const toolUseId = REAL_MSG863.content[0].tool_use_id;
139+
const canon = pinCanon([assistantToolUse(toolUseId), REAL_MSG863]);
140+
141+
const messages = [assistantToolUse(toolUseId), REAL_MSG863, { ...REAL_MERGED_STANDALONE }];
142+
const result = classifyPinned(messages, canon);
143+
144+
assert.equal(result.suppressed, 0, "a final-position merged duplicate must be forwarded, not suppressed");
145+
assert.equal(result.messages.length, 3, "the standalone must remain on the wire as the live final message");
146+
assert.equal(
147+
result.messages[result.messages.length - 1].content,
148+
REAL_MERGED_STANDALONE.content,
149+
"the final message content is unchanged",
150+
);
116151
});
117152

118153
// =====================================================================

test/insertion-suppression.test.mjs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,53 @@ test("classifyPinned: a standalone duplicate of a pinned block is suppressed; th
155155
assert.deepEqual(result.messages[result.messages.length - 1], userMsg("continue"));
156156
});
157157

158+
// =====================================================================
159+
// TAIL GUARD (BACKLOG.md, "suppression can strip a request's FINAL
160+
// message", 2026-07-30). Three real 400s ("must end with a user
161+
// message"): report-enforcer injects identical instruction bytes at
162+
// every SubagentStop; the first occurrence is pinned, and when the SAME
163+
// bytes arrive again as a resume request's ONLY/new final message,
164+
// suppressing it left the forwarded array ending on the prior assistant
165+
// turn. A tail-position duplicate is CC's live payload for THIS request,
166+
// not a migration copy of already-pinned content, regardless of role or
167+
// which hash set (single-block or join) matched it.
168+
// =====================================================================
169+
170+
test("TAIL GUARD: a standalone duplicate at the FINAL index is never suppressed — it is live payload, not a migration", () => {
171+
const orig = [withReminderMsg("tool result"), assistantMsg("a1")];
172+
const canon = pinCanon(orig);
173+
174+
const strippedTail = { role: "user", content: [{ type: "text", text: "tool result" }] };
175+
const standaloneDuplicate = { role: "system", content: [{ type: "text", text: REMINDER_INNER }] };
176+
// No trailing entry after the duplicate — it IS the array's final
177+
// message, mirroring the real resume-request shape.
178+
const next = [strippedTail, assistantMsg("a1"), standaloneDuplicate];
179+
180+
const result = classifyPinned(next, canon);
181+
assert.equal(result.suppressed, 0, "a final-position duplicate must never be suppressed");
182+
assert.equal(result.suppressions.length, 0);
183+
assert.deepEqual(
184+
result.messages[result.messages.length - 1],
185+
standaloneDuplicate,
186+
"the final message must be forwarded intact — this is exactly what would otherwise strip a resume's last turn",
187+
);
188+
});
189+
190+
test("REGRESSION: the same standalone duplicate, mid-history (not final), is still suppressed", () => {
191+
const orig = [withReminderMsg("tool result"), assistantMsg("a1")];
192+
const canon = pinCanon(orig);
193+
194+
const strippedTail = { role: "user", content: [{ type: "text", text: "tool result" }] };
195+
const standaloneDuplicate = { role: "system", content: [{ type: "text", text: REMINDER_INNER }] };
196+
// Same duplicate, same position (index 2) as the tail-guard test above,
197+
// but with a trailing turn after it — no longer the final index.
198+
const next = [strippedTail, assistantMsg("a1"), standaloneDuplicate, userMsg("continue")];
199+
200+
const result = classifyPinned(next, canon);
201+
assert.equal(result.suppressed, 1, "mid-history duplicates are suppressed exactly as before the tail guard");
202+
assert.equal(result.suppressions[0].index, 2);
203+
});
204+
158205
test("classifyPinned: suppression is stable across a THIRD request — CC keeps resending the duplicate, it keeps getting suppressed, with no persisted marker needed", () => {
159206
const orig = [withReminderMsg("tool result"), assistantMsg("a1")];
160207
let canon = pinCanon(orig);

0 commit comments

Comments
 (0)