Skip to content

Commit 9daea40

Browse files
committed
fix(cli-runner): drop volatile systemPromptHash from claude-cli live fingerprint
1 parent 64e6ea0 commit 9daea40

2 files changed

Lines changed: 120 additions & 2 deletions

File tree

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { describe, expect, it } from "vitest";
2+
import { buildClaudeLiveFingerprint } from "./claude-live-session.js";
3+
import type { PreparedCliRunContext } from "./types.js";
4+
5+
function buildContext(overrides?: {
6+
systemPrompt?: string;
7+
extraSystemPromptHash?: string;
8+
normalizedModel?: string;
9+
workspaceDir?: string;
10+
}): PreparedCliRunContext {
11+
const backend = {
12+
command: "claude",
13+
args: [],
14+
output: "jsonl" as const,
15+
input: "stdin" as const,
16+
serialize: true,
17+
liveSession: "claude-stdio" as const,
18+
sessionArg: "--session",
19+
systemPromptArg: "--system-prompt",
20+
systemPromptFileArg: "--system-prompt-file",
21+
};
22+
return {
23+
params: {
24+
sessionId: "session-1",
25+
sessionFile: "/tmp/session.jsonl",
26+
workspaceDir: overrides?.workspaceDir ?? "/tmp/workspace",
27+
prompt: "hi",
28+
provider: "claude-cli",
29+
model: "model",
30+
timeoutMs: 1_000,
31+
runId: "run-1",
32+
},
33+
started: Date.now(),
34+
workspaceDir: overrides?.workspaceDir ?? "/tmp/workspace",
35+
backendResolved: {
36+
id: "claude-cli",
37+
config: backend,
38+
bundleMcp: false,
39+
},
40+
preparedBackend: {
41+
backend,
42+
env: {},
43+
},
44+
reusableCliSession: {},
45+
modelId: "model",
46+
normalizedModel: overrides?.normalizedModel ?? "model",
47+
systemPrompt: overrides?.systemPrompt ?? "you are an agent",
48+
systemPromptReport: {} as PreparedCliRunContext["systemPromptReport"],
49+
bootstrapPromptWarningLines: [],
50+
authEpochVersion: 2,
51+
extraSystemPromptHash: overrides?.extraSystemPromptHash ?? "static-hash-a",
52+
};
53+
}
54+
55+
describe("buildClaudeLiveFingerprint", () => {
56+
it("ignores volatile changes to the per-turn system prompt", () => {
57+
// Per-turn inbound metadata (timestamps, sender envelope, heartbeat, channel
58+
// guidance) is folded into `context.systemPrompt`. The fingerprint must not
59+
// diverge on these volatile chunks — otherwise the runtime rotates the live
60+
// claude-cli subprocess on every turn and loses prior context.
61+
const baselineArgv = ["claude", "--model", "model"];
62+
const a = buildClaudeLiveFingerprint({
63+
context: buildContext({ systemPrompt: "[turn 1 metadata]\nyou are an agent" }),
64+
argv: baselineArgv,
65+
env: {},
66+
});
67+
const b = buildClaudeLiveFingerprint({
68+
context: buildContext({ systemPrompt: "[turn 2 different metadata]\nyou are an agent" }),
69+
argv: baselineArgv,
70+
env: {},
71+
});
72+
expect(a).toBe(b);
73+
});
74+
75+
it("diverges when extraSystemPromptHash changes (static config still gates session reuse)", () => {
76+
const argv = ["claude", "--model", "model"];
77+
const a = buildClaudeLiveFingerprint({
78+
context: buildContext({ extraSystemPromptHash: "static-hash-a" }),
79+
argv,
80+
env: {},
81+
});
82+
const b = buildClaudeLiveFingerprint({
83+
context: buildContext({ extraSystemPromptHash: "static-hash-b" }),
84+
argv,
85+
env: {},
86+
});
87+
expect(a).not.toBe(b);
88+
});
89+
90+
it("diverges when the normalized model changes", () => {
91+
const argv = ["claude", "--model", "model"];
92+
const a = buildClaudeLiveFingerprint({
93+
context: buildContext({ normalizedModel: "sonnet-4.6" }),
94+
argv,
95+
env: {},
96+
});
97+
const b = buildClaudeLiveFingerprint({
98+
context: buildContext({ normalizedModel: "opus-4.7" }),
99+
argv,
100+
env: {},
101+
});
102+
expect(a).not.toBe(b);
103+
});
104+
105+
it("diverges when the workspace directory changes", () => {
106+
const argv = ["claude", "--model", "model"];
107+
const a = buildClaudeLiveFingerprint({
108+
context: buildContext({ workspaceDir: "/tmp/one" }),
109+
argv,
110+
env: {},
111+
});
112+
const b = buildClaudeLiveFingerprint({
113+
context: buildContext({ workspaceDir: "/tmp/two" }),
114+
argv,
115+
env: {},
116+
});
117+
expect(a).not.toBe(b);
118+
});
119+
});

src/agents/cli-runner/claude-live-session.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ function buildClaudeLiveKey(context: PreparedCliRunContext): string {
259259
)}`;
260260
}
261261

262-
function buildClaudeLiveFingerprint(params: {
262+
export function buildClaudeLiveFingerprint(params: {
263263
context: PreparedCliRunContext;
264264
argv: string[];
265265
env: Record<string, string>;
@@ -326,7 +326,6 @@ function buildClaudeLiveFingerprint(params: {
326326
cwdHash: params.context.cwdHash ?? sha256(params.context.cwd ?? params.context.workspaceDir),
327327
provider: params.context.params.provider,
328328
model: params.context.normalizedModel,
329-
systemPromptHash: sha256(params.context.systemPrompt),
330329
authProfileIdHash: params.context.effectiveAuthProfileId
331330
? sha256(params.context.effectiveAuthProfileId)
332331
: undefined,

0 commit comments

Comments
 (0)