Skip to content

Commit 85acdd4

Browse files
fix: stabilize claude-cli extraSystemPromptHash across group turns (#69118)
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 66b91d7 commit 85acdd4

2 files changed

Lines changed: 68 additions & 2 deletions

File tree

src/auto-reply/reply/get-reply-run.media-only.test.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2431,6 +2431,71 @@ describe("runPreparedReply media-only handling", () => {
24312431
expect(call?.followupRun.run.extraSystemPromptStatic).toBe("group:discord:channel:#ops");
24322432
});
24332433

2434+
it("keeps the CLI session-reuse static prompt stable across group turns despite first-turn groupIntro", async () => {
2435+
const { buildGroupIntro } = await import("./groups.js");
2436+
// groupIntro is injected only on the first turn; groupChatContext is persistent.
2437+
vi.mocked(buildGroupChatContext).mockReturnValue("GROUP-CHAT-CONTEXT");
2438+
vi.mocked(buildGroupIntro).mockReturnValue("GROUP-INTRO");
2439+
const groupCtx = {
2440+
Body: "hello team",
2441+
RawBody: "hello team",
2442+
CommandBody: "hello team",
2443+
Provider: "discord",
2444+
ChatType: "group" as const,
2445+
SessionKey: "agent:main:discord:guild-1:channel-1",
2446+
};
2447+
const groupSessionCtx = {
2448+
Body: "hello team",
2449+
BodyStripped: "hello team",
2450+
Provider: "discord",
2451+
ChatType: "group" as const,
2452+
};
2453+
try {
2454+
// Turn 1: first turn in the session injects the behavioral group intro.
2455+
await runPreparedReply(
2456+
baseParams({
2457+
isNewSession: true,
2458+
systemSent: false,
2459+
ctx: groupCtx,
2460+
sessionCtx: groupSessionCtx,
2461+
}),
2462+
);
2463+
const firstTurn = requireRunReplyAgentCall(0);
2464+
// Turn 2: established session no longer re-injects the intro.
2465+
await runPreparedReply(
2466+
baseParams({
2467+
isNewSession: false,
2468+
systemSent: true,
2469+
ctx: groupCtx,
2470+
sessionCtx: groupSessionCtx,
2471+
sessionEntry: {
2472+
sessionId: "session-1",
2473+
updatedAt: 1,
2474+
systemSent: true,
2475+
chatType: "group",
2476+
channel: "discord",
2477+
} as SessionEntry,
2478+
}),
2479+
);
2480+
const secondTurn = requireRunReplyAgentCall(1);
2481+
2482+
// The live prompt still gets the intro on the first turn only.
2483+
expect(firstTurn.followupRun.run.extraSystemPrompt).toContain("GROUP-INTRO");
2484+
expect(secondTurn.followupRun.run.extraSystemPrompt).not.toContain("GROUP-INTRO");
2485+
2486+
// The session-reuse hash input excludes the volatile intro and stays identical across
2487+
// turns, so claude-cli reuses the session instead of resetting it every group turn (#69118).
2488+
expect(firstTurn.followupRun.run.extraSystemPromptStatic).not.toContain("GROUP-INTRO");
2489+
expect(firstTurn.followupRun.run.extraSystemPromptStatic).toContain("GROUP-CHAT-CONTEXT");
2490+
expect(secondTurn.followupRun.run.extraSystemPromptStatic).toBe(
2491+
firstTurn.followupRun.run.extraSystemPromptStatic,
2492+
);
2493+
} finally {
2494+
vi.mocked(buildGroupChatContext).mockReturnValue("");
2495+
vi.mocked(buildGroupIntro).mockReturnValue("");
2496+
}
2497+
});
2498+
24342499
it.each([
24352500
["/new", "new"],
24362501
["/reset", "reset"],

src/auto-reply/reply/get-reply-run.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -626,11 +626,12 @@ export async function runPreparedReply(
626626
fullAccessBlockedReason: fullAccessState.blockedReason,
627627
}),
628628
].filter(Boolean);
629-
// Static parts only (no per-message inbound metadata) for CLI session reuse hashing.
629+
// Static parts for CLI session-reuse hashing. Excludes per-message inbound metadata and the
630+
// first-turn-only groupIntro (still injected into the live prompt above): hashing groupIntro
631+
// would drift the fingerprint between turn 1 and later turns, resetting the session (#69118).
630632
const extraSystemPromptStaticParts = [
631633
directChatContext,
632634
groupChatContext,
633-
groupIntro,
634635
groupSystemPrompt,
635636
buildExecOverridePromptHint({
636637
execOverrides,

0 commit comments

Comments
 (0)