From 5e4f0de093fe58295afadf4e4c5a58c40e7f01d1 Mon Sep 17 00:00:00 2001 From: justrach <54503978+justrach@users.noreply.github.com> Date: Mon, 2 Nov 2026 09:17:40 +0800 Subject: [PATCH] test: Claude Agent compaction sends its summary request as the prompt Compaction runs on a fresh tools-off Claude Code process. Cut mid tool loop, its history ends on tool results (or a call) followed by the summary request. freshContent used to fold the request into the text checkpoint and add "Continue from where the checkpoint ends.", so the model carried on with the task or asked the user a question, and that reply was installed as the summary. #1792's change to freshContent fixed this; this pins both shapes. --- src/claude_agent_tests.zig | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/claude_agent_tests.zig b/src/claude_agent_tests.zig index c5e450c3..00f7a7c7 100644 --- a/src/claude_agent_tests.zig +++ b/src/claude_agent_tests.zig @@ -147,6 +147,38 @@ test "claude-agent version floor" { try std.testing.expect(wire.effortFlag("none") == null); } +test "claude-agent compaction mid tool loop: the summary request is the prompt, never checkpoint to continue" { + // Compaction runs on a fresh tools-off process. Cut mid loop, its history + // ends on tool results (or a call) and then the summary request. Folding + // the request into the checkpoint and adding "Continue from where the + // checkpoint ends." made the model carry on with the task, or ask the + // user a question, and that text was installed as the "summary". + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const a = arena.allocator(); + const request = "Write a handoff summary of the conversation so far."; + const shapes = [_][]const u8{ + \\[{"role":"user","content":"fix the bug"}, + \\ {"role":"assistant","content":[{"type":"tool_use","id":"t1","name":"read_file","input":{"path":"a.zig"}}]}, + \\ {"role":"user","content":[{"type":"tool_result","tool_use_id":"t1","content":"fn main() {}"}]}, + \\ {"role":"user","content":"Write a handoff summary of the conversation so far."}] + , + \\[{"role":"user","content":"fix the bug"}, + \\ {"role":"assistant","content":[{"type":"tool_use","id":"t1","name":"read_file","input":{"path":"a.zig"}}]}, + \\ {"role":"user","content":"Write a handoff summary of the conversation so far."}] + , + }; + for (shapes) |shape| { + const msgs = try parse(a, shape); + const blocks = (try history.freshContent(a, msgs.array.items)).array.items; + try std.testing.expectEqual(@as(usize, 2), blocks.len); + const cp = blocks[0].object.get("text").?.string; + try std.testing.expect(std.mem.indexOf(u8, cp, "read_file") != null); + try std.testing.expect(std.mem.indexOf(u8, cp, request) == null); + try std.testing.expectEqualStrings(request, blocks[1].object.get("text").?.string); + } +} + test "claude-agent fresh session: earlier history is a checkpoint, the prompt keeps its blocks" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit();