Skip to content

Commit e3df84a

Browse files
committed
fix(command): probe claude transcript from cli cwd
1 parent 7409717 commit e3df84a

4 files changed

Lines changed: 102 additions & 2 deletions

File tree

src/agents/cli-runner/prepare.test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1494,6 +1494,60 @@ describe("shouldSkipLocalCliCredentialEpoch", () => {
14941494
}
14951495
});
14961496

1497+
it("checks claude-cli transcript content under the resolved cwd", async () => {
1498+
const { dir, sessionFile } = createSessionFile();
1499+
const taskDir = path.join(dir, "task");
1500+
fs.mkdirSync(taskDir, { recursive: true });
1501+
try {
1502+
cliBackendsTesting.setDepsForTest({
1503+
resolvePluginSetupCliBackend: () => undefined,
1504+
resolveRuntimeCliBackends: () => [
1505+
{
1506+
id: "claude-cli",
1507+
pluginId: "anthropic",
1508+
bundleMcp: false,
1509+
config: {
1510+
command: "claude",
1511+
args: ["--print"],
1512+
resumeArgs: ["--resume", "{sessionId}"],
1513+
output: "jsonl",
1514+
input: "stdin",
1515+
sessionMode: "existing",
1516+
},
1517+
},
1518+
],
1519+
});
1520+
const transcriptCheck = vi.fn(async () => true);
1521+
setCliRunnerPrepareTestDeps({
1522+
claudeCliSessionTranscriptHasContent: transcriptCheck,
1523+
});
1524+
1525+
const context = await prepareCliRunContext({
1526+
sessionId: "session-test",
1527+
sessionKey: "agent:main:telegram:direct:peer",
1528+
sessionFile,
1529+
workspaceDir: dir,
1530+
cwd: taskDir,
1531+
prompt: "follow-up",
1532+
provider: "claude-cli",
1533+
model: "opus",
1534+
timeoutMs: 1_000,
1535+
runId: "run-77011-cwd",
1536+
cliSessionBinding: { sessionId: "live-claude-sid", cwdHash: hashCliSessionText(taskDir) },
1537+
cliSessionId: "live-claude-sid",
1538+
config: createCliBackendConfig(),
1539+
});
1540+
1541+
expect(transcriptCheck).toHaveBeenCalledWith({
1542+
sessionId: "live-claude-sid",
1543+
workspaceDir: taskDir,
1544+
});
1545+
expect(context.reusableCliSession).toEqual({ sessionId: "live-claude-sid" });
1546+
} finally {
1547+
fs.rmSync(dir, { recursive: true, force: true });
1548+
}
1549+
});
1550+
14971551
it("omits Claude CLI prompt skills when the native skills plugin can carry them", async () => {
14981552
const { dir, sessionFile } = createSessionFile();
14991553
const skillDir = path.join(dir, "skills", "weather");

src/agents/cli-runner/prepare.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ export async function prepareCliRunContext(
375375
isClaudeCliProvider(params.provider) &&
376376
!(await prepareDeps.claudeCliSessionTranscriptHasContent({
377377
sessionId: candidateClaudeCliSessionId,
378-
workspaceDir,
378+
workspaceDir: cwd,
379379
}));
380380
const reusableCliSession: CliReusableSession = claudeCliTranscriptMissing
381381
? { invalidatedReason: "missing-transcript" }

src/agents/command/attempt-execution.cli.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ describe("CLI attempt execution", () => {
180180
sessionStore: Record<string, SessionEntry>;
181181
body: string;
182182
runId: string;
183+
cwd?: string;
183184
}) {
184185
await runAgentAttempt({
185186
providerOverride: "claude-cli",
@@ -192,6 +193,7 @@ describe("CLI attempt execution", () => {
192193
sessionAgentId: "main",
193194
sessionFile: path.join(tmpDir, "session.jsonl"),
194195
workspaceDir: tmpDir,
196+
cwd: params.cwd,
195197
body: params.body,
196198
isFallbackRetry: false,
197199
resolvedThinkLevel: "medium",
@@ -495,6 +497,48 @@ describe("CLI attempt execution", () => {
495497
expect(sessionStore[sessionKey]?.claudeCliSessionId).toBe(cliSessionId);
496498
});
497499

500+
it("checks Claude CLI transcript content under the process cwd", async () => {
501+
const sessionKey = "agent:main:direct:claude-transcript-cwd-present";
502+
const cliSessionId = "existing-claude-cwd-session";
503+
const homeDir = path.join(tmpDir, "home");
504+
const cwd = path.join(tmpDir, "task");
505+
const projectsDir = resolveClaudeCliProjectDirForWorkspace({
506+
workspaceDir: cwd,
507+
homeDir,
508+
});
509+
process.env.HOME = homeDir;
510+
await fs.mkdir(projectsDir, { recursive: true });
511+
await fs.writeFile(
512+
path.join(projectsDir, `${cliSessionId}.jsonl`),
513+
`${JSON.stringify({
514+
type: "assistant",
515+
message: {
516+
role: "assistant",
517+
content: [{ type: "text", text: "previous reply" }],
518+
},
519+
})}\n`,
520+
"utf-8",
521+
);
522+
const sessionEntry = makeClaudeCliSessionEntry("openclaw-session-cwd", cliSessionId);
523+
const sessionStore: Record<string, SessionEntry> = { [sessionKey]: sessionEntry };
524+
await fs.writeFile(storePath, JSON.stringify(sessionStore, null, 2), "utf-8");
525+
runCliAgentMock.mockResolvedValueOnce(makeCliResult("resumed cli response"));
526+
527+
await runClaudeCliAttempt({
528+
sessionKey,
529+
sessionEntry,
530+
sessionStore,
531+
body: "continue from task cwd",
532+
runId: "run-cli-transcript-cwd-present",
533+
cwd,
534+
});
535+
536+
expect(runCliAgentMock).toHaveBeenCalledTimes(1);
537+
expect(firstRunCliAgentArg().cliSessionId).toBe(cliSessionId);
538+
expect(firstRunCliAgentArg().cwd).toBe(cwd);
539+
expect(sessionStore[sessionKey]?.cliSessionIds?.["claude-cli"]).toBe(cliSessionId);
540+
});
541+
498542
it("passes session-bound OpenAI Codex auth profile to codex-cli aliases", async () => {
499543
const sessionKey = "agent:main:direct:codex-cli-auth-alias";
500544
const sessionEntry: SessionEntry = {

src/agents/command/attempt-execution.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
} from "../../sessions/user-turn-transcript.js";
2222
import { buildWorkspaceSkillSnapshot } from "../../skills/loading/workspace.js";
2323
import { sanitizeForLog } from "../../terminal/ansi.js";
24+
import { resolveUserPath } from "../../utils.js";
2425
import { resolveMessageChannel } from "../../utils/message-channel.js";
2526
import { resolveAuthProfileOrder } from "../auth-profiles/order.js";
2627
import { ensureAuthProfileStore } from "../auth-profiles/store.js";
@@ -504,13 +505,14 @@ export function runAgentAttempt(params: {
504505
: undefined);
505506
if (!isRawModelRun && isCliProvider(cliExecutionProvider, params.cfg)) {
506507
const cliSessionBinding = getCliSessionBinding(params.sessionEntry, cliExecutionProvider);
508+
const cliProcessCwd = params.cwd ? resolveUserPath(params.cwd) : params.workspaceDir;
507509
const resolveReusableCliSessionBinding = async () => {
508510
if (
509511
!isClaudeCliProvider(cliExecutionProvider) ||
510512
!cliSessionBinding?.sessionId ||
511513
(await claudeCliSessionTranscriptHasContent({
512514
sessionId: cliSessionBinding.sessionId,
513-
workspaceDir: params.workspaceDir,
515+
workspaceDir: cliProcessCwd,
514516
}))
515517
) {
516518
return cliSessionBinding;

0 commit comments

Comments
 (0)