mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 09:39:25 +00:00
fix(anthropic): preserve resumed CLI caches across Git changes (#140566)
* fix(anthropic): preserve resumed CLI caches across Git changes * test(anthropic): expect cache policy with context windows --------- Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
This commit is contained in:
parent
3c6c2a4697
commit
940c359a36
7 changed files with 57 additions and 30 deletions
|
|
@ -143,6 +143,13 @@ claude update
|
|||
|
||||
The bundled `claude-cli` backend prefers Claude Code's native skill resolver. When the current skills snapshot has at least one selected skill with a materialized path, OpenClaw passes a temporary Claude Code plugin via `--plugin-dir` and omits the duplicate OpenClaw skills catalog from the appended system prompt. Without a materialized plugin skill, OpenClaw keeps the prompt catalog as a fallback. Skill env/API key overrides still apply to the child process environment for the run.
|
||||
|
||||
OpenClaw disables Claude Code's built-in Git workflow instructions and startup
|
||||
Git-status snapshot with `CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS=1`. Claude Code
|
||||
rebuilds that snapshot when a process resumes, so workspace edits or commits
|
||||
would otherwise invalidate cached conversation history. Git tools and workspace
|
||||
instructions remain available. This does not prevent cache misses after prompt
|
||||
changes, compaction, model or thinking changes, or cache expiry.
|
||||
|
||||
OpenClaw always launches Claude Code with its default permission mode.
|
||||
OpenClaw's permission responses and `PreToolUse` hook keep native tools under
|
||||
host control, including when user or enterprise settings would otherwise
|
||||
|
|
|
|||
|
|
@ -272,23 +272,25 @@ export function buildAnthropicCliBackend(
|
|||
}
|
||||
: undefined;
|
||||
const env = {
|
||||
// Claude rebuilds the startup Git snapshot on process resume, rewriting
|
||||
// the conversation prefix after workspace edits or commits. OpenClaw
|
||||
// supplies workspace instructions; Git state can be read with tools.
|
||||
CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS: "1",
|
||||
...resolveClaudeCliAutoCompactEnv(context.contextTokenBudget),
|
||||
...(context.contextWindow === "200k" ? { CLAUDE_CODE_DISABLE_1M_CONTEXT: "1" } : {}),
|
||||
...resolveClaudeCliThinkingEnv(context.thinkingLevel, context.modelId),
|
||||
...authInput?.env,
|
||||
};
|
||||
return Object.keys(env).length > 0 || isolatedCompletion || cliExecution
|
||||
? {
|
||||
env,
|
||||
// The paired side-question argv projection disables settings, memory,
|
||||
// hooks, session persistence, and tools before process launch.
|
||||
...(isolatedCompletion ? { isolatedCompletionEnforced: true as const } : {}),
|
||||
...(authInput?.clearEnv ? { clearEnv: authInput.clearEnv } : {}),
|
||||
...(authInput?.secretInput ? { secretInput: authInput.secretInput } : {}),
|
||||
...(authInput?.cleanup ? { cleanup: authInput.cleanup } : {}),
|
||||
...cliExecution,
|
||||
}
|
||||
: undefined;
|
||||
return {
|
||||
env,
|
||||
// The paired side-question argv projection disables settings, memory,
|
||||
// hooks, session persistence, and tools before process launch.
|
||||
...(isolatedCompletion ? { isolatedCompletionEnforced: true as const } : {}),
|
||||
...(authInput?.clearEnv ? { clearEnv: authInput.clearEnv } : {}),
|
||||
...(authInput?.secretInput ? { secretInput: authInput.secretInput } : {}),
|
||||
...(authInput?.cleanup ? { cleanup: authInput.cleanup } : {}),
|
||||
...cliExecution,
|
||||
};
|
||||
};
|
||||
const supportProbe = options.ensureDynamicSystemPromptSectionsSupport?.();
|
||||
return supportProbe ? supportProbe.then(prepare) : prepare();
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ describe("Claude CLI context-window selection", () => {
|
|||
env: {
|
||||
CLAUDE_CODE_AUTO_COMPACT_WINDOW: "200000",
|
||||
CLAUDE_CODE_DISABLE_1M_CONTEXT: "1",
|
||||
CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS: "1",
|
||||
},
|
||||
});
|
||||
expect(resolveModelId?.({ modelId, contextWindow: "1m" })).toBe(`${modelId}[1m]`);
|
||||
|
|
@ -36,7 +37,12 @@ describe("Claude CLI context-window selection", () => {
|
|||
contextWindow: "1m",
|
||||
contextTokenBudget: 1_000_000,
|
||||
}),
|
||||
).toEqual({ env: { CLAUDE_CODE_AUTO_COMPACT_WINDOW: "1000000" } });
|
||||
).toEqual({
|
||||
env: {
|
||||
CLAUDE_CODE_AUTO_COMPACT_WINDOW: "1000000",
|
||||
CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS: "1",
|
||||
},
|
||||
});
|
||||
expect(backend.config.clearEnv).toContain("CLAUDE_CODE_DISABLE_1M_CONTEXT");
|
||||
expect(resolveModelId?.({ modelId: "claude-opus-4-8" })).toBe("claude-opus-4-8");
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { resolveClaudeCliThinkingEnv } from "./cli-shared.js";
|
||||
import { resolveClaudeCliAutoCompactEnv, resolveClaudeCliThinkingEnv } from "./cli-shared.js";
|
||||
|
||||
describe("Claude CLI execution environment", () => {
|
||||
it.each([
|
||||
|
|
@ -17,3 +17,15 @@ describe("Claude CLI execution environment", () => {
|
|||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("resolveClaudeCliAutoCompactEnv", () => {
|
||||
it("maps the effective OpenClaw context budget into Claude Code compaction", () => {
|
||||
expect(resolveClaudeCliAutoCompactEnv(100_000.9)).toEqual({
|
||||
CLAUDE_CODE_AUTO_COMPACT_WINDOW: "100000",
|
||||
});
|
||||
});
|
||||
|
||||
it.each([undefined, 0, 0.5, Number.NaN])("rejects an invalid context budget: %s", (budget) => {
|
||||
expect(resolveClaudeCliAutoCompactEnv(budget)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { buildAnthropicCliBackend } from "./cli-backend.js";
|
|||
import {
|
||||
CLAUDE_CLI_CLEAR_ENV,
|
||||
normalizeClaudeBackendConfig,
|
||||
resolveClaudeCliAutoCompactEnv,
|
||||
resolveClaudeCliExecutionArgs,
|
||||
supportsClaudeDynamicSystemPromptSections,
|
||||
} from "./cli-shared.js";
|
||||
|
|
@ -70,7 +69,10 @@ describe("Claude CLI adapter equivalence", () => {
|
|||
isolatedCompletionSystemPrompt: string;
|
||||
}) as { env?: Record<string, string>; isolatedCompletionEnforced?: true };
|
||||
|
||||
expect(prepared).toEqual({ env: {}, isolatedCompletionEnforced: true });
|
||||
expect(prepared).toEqual({
|
||||
env: { CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS: "1" },
|
||||
isolatedCompletionEnforced: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("builds Claude Code's native manual compaction command", () => {
|
||||
|
|
@ -110,18 +112,6 @@ describe("Claude CLI adapter equivalence", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("resolveClaudeCliAutoCompactEnv", () => {
|
||||
it("maps the effective OpenClaw context budget into Claude Code compaction", () => {
|
||||
expect(resolveClaudeCliAutoCompactEnv(100_000.9)).toEqual({
|
||||
CLAUDE_CODE_AUTO_COMPACT_WINDOW: "100000",
|
||||
});
|
||||
});
|
||||
|
||||
it.each([undefined, 0, 0.5, Number.NaN])("rejects an invalid context budget: %s", (budget) => {
|
||||
expect(resolveClaudeCliAutoCompactEnv(budget)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
function expectDefaultDisallowedTools(args: readonly string[] | undefined) {
|
||||
const disallowedIndex = args?.indexOf("--disallowedTools") ?? -1;
|
||||
expect(disallowedIndex).toBeGreaterThanOrEqual(0);
|
||||
|
|
@ -918,7 +908,10 @@ describe("normalizeClaudeBackendConfig", () => {
|
|||
contextTokenBudget: 100_000,
|
||||
}),
|
||||
).toEqual({
|
||||
env: { CLAUDE_CODE_AUTO_COMPACT_WINDOW: "100000" },
|
||||
env: {
|
||||
CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS: "1",
|
||||
CLAUDE_CODE_AUTO_COMPACT_WINDOW: "100000",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -948,6 +941,7 @@ describe("normalizeClaudeBackendConfig", () => {
|
|||
}) as ClaudePreparedExecutionWithSecret;
|
||||
|
||||
expect(prepared.env).toEqual({
|
||||
CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS: "1",
|
||||
CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR: "3",
|
||||
});
|
||||
expect(prepared.env).not.toHaveProperty("CLAUDE_CODE_OAUTH_TOKEN");
|
||||
|
|
@ -1058,6 +1052,7 @@ describe("normalizeClaudeBackendConfig", () => {
|
|||
}) as ClaudePreparedExecutionWithSecret;
|
||||
|
||||
expect(prepared.env).toEqual({
|
||||
CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS: "1",
|
||||
CLAUDE_CODE_API_KEY_FILE_DESCRIPTOR: "3",
|
||||
});
|
||||
expect(prepared.env).not.toHaveProperty("ANTHROPIC_API_KEY");
|
||||
|
|
|
|||
|
|
@ -299,6 +299,7 @@ process.stdout.write(JSON.stringify({
|
|||
descriptor: descriptor ?? null,
|
||||
rawPresent: Object.hasOwn(process.env, "CLAUDE_CODE_OAUTH_TOKEN") || Object.hasOwn(process.env, "ANTHROPIC_API_KEY"),
|
||||
scrubPresent: Object.hasOwn(process.env, "CLAUDE_CODE_SUBPROCESS_ENV_SCRUB"),
|
||||
gitInstructionsDisabled: process.env.CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS,
|
||||
}) + "\\n");`);
|
||||
const calls: Array<{ method: string; params: unknown }> = [];
|
||||
const handleSystemRun = vi.fn(
|
||||
|
|
@ -356,6 +357,7 @@ process.stdout.write(JSON.stringify({
|
|||
});
|
||||
expect(progress).toContain('"rawPresent":false');
|
||||
expect(progress).toContain('"scrubPresent":false');
|
||||
expect(progress).toContain('"gitInstructionsDisabled":"1"');
|
||||
expect(calls).toContainEqual({
|
||||
method: "node.invoke.result",
|
||||
params: expect.objectContaining({
|
||||
|
|
@ -520,6 +522,7 @@ process.stdout.write(JSON.stringify({
|
|||
descriptor: process.env[${JSON.stringify(descriptorEnv)}],
|
||||
rawPresent: Object.hasOwn(process.env, ${JSON.stringify(rawEnv)}),
|
||||
scrubPresent: Object.hasOwn(process.env, "CLAUDE_CODE_SUBPROCESS_ENV_SCRUB"),
|
||||
gitInstructionsDisabled: process.env.CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS,
|
||||
}) + "\\n");`);
|
||||
const request = { argv: ["-p"], idleTimeoutMs: 1_000, timeoutMs: 5_000 };
|
||||
const calls: Array<{ method: string; params: unknown }> = [];
|
||||
|
|
@ -543,6 +546,7 @@ process.stdout.write(JSON.stringify({
|
|||
expect(progress).toContain('"descriptor":"3"');
|
||||
expect(progress).toContain('"rawPresent":false');
|
||||
expect(progress).toContain('"scrubPresent":false');
|
||||
expect(progress).toContain('"gitInstructionsDisabled":"1"');
|
||||
expect(result).toMatchObject({ exitCode: 0, success: true });
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -151,7 +151,8 @@ export async function runClaudeCliNodeCommand(params: {
|
|||
mode: "child",
|
||||
argv,
|
||||
cwd: params.cwd,
|
||||
env: params.env,
|
||||
// Apply the cache-stable Git policy locally without extending the node wire contract.
|
||||
env: { ...(params.env ?? process.env), CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS: "1" },
|
||||
exactEnv: true,
|
||||
input:
|
||||
skillSession?.rewriteReferences(params.request.stdin ?? "") ?? params.request.stdin ?? "",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue