From c686c1c80349d0af6539b63207d1c91a882ee36f Mon Sep 17 00:00:00 2001 From: roboomp Date: Tue, 23 Jun 2026 01:27:52 +0000 Subject: [PATCH 1/4] fix(ai): kept anthropic thinking context Sent context_management.keep=all for all enabled Anthropic thinking requests so API-key and Anthropic-compatible providers preserve replayed reasoning blocks across turns. Added regression coverage for budget and adaptive thinking payloads. Fixes #3288 --- packages/ai/CHANGELOG.md | 4 ++++ packages/ai/src/providers/anthropic.ts | 16 +++++++++---- packages/ai/test/anthropic-alignment.test.ts | 8 ++++--- ...anthropic-unsigned-thinking-replay.test.ts | 23 +++++++++++++++++++ 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 5a45d955e..6aad3230a 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed Anthropic-compatible thinking requests sending replayed thinking blocks without `context_management.keep: "all"`, preserving multi-turn reasoning context for API-key providers. ([#3288](https://github.com/can1357/oh-my-pi/issues/3288)) + ## [16.1.15] - 2026-06-22 ### Fixed diff --git a/packages/ai/src/providers/anthropic.ts b/packages/ai/src/providers/anthropic.ts index bfa6f6773..f681f7f54 100644 --- a/packages/ai/src/providers/anthropic.ts +++ b/packages/ai/src/providers/anthropic.ts @@ -2944,11 +2944,17 @@ function buildParams( } } - // Pre-compute context_management (depends on thinking). - const contextManagement = - isOAuthToken && thinking?.type === "adaptive" - ? { edits: [{ type: "clear_thinking_20251015" as const, keep: "all" as const }] } - : undefined; + // Pre-compute context_management. Send keep: "all" for every enabled or + // adaptive thinking request (OAuth + API-key) — not just OAuth. Without + // this directive Anthropic-compatible backends (Z.AI, Kimi, DeepSeek, …) + // strip the replayed thinking blocks `replayUnsignedThinking` puts back + // on the wire, so the model loses the prior reasoning chain across turns + // and the KV cache misses every turn (#3288). Narrowing this guard back + // to `isOAuthToken` regresses every API-key thinking provider. + const shouldKeepThinkingContext = thinking?.type === "adaptive" || thinking?.type === "enabled"; + const contextManagement = shouldKeepThinkingContext + ? { edits: [{ type: "clear_thinking_20251015" as const, keep: "all" as const }] } + : undefined; // Pre-compute output_config. const outputConfigEntries: AnthropicOutputConfig = {}; diff --git a/packages/ai/test/anthropic-alignment.test.ts b/packages/ai/test/anthropic-alignment.test.ts index 47a966f68..47a2714fc 100644 --- a/packages/ai/test/anthropic-alignment.test.ts +++ b/packages/ai/test/anthropic-alignment.test.ts @@ -1870,7 +1870,7 @@ describe("Anthropic request fingerprint alignment", () => { expect(maxPayload.output_config).toEqual({ effort: "max" }); }); - it("keeps summarized adaptive thinking by default for API-key Opus 4.7+ requests", async () => { + it("keeps summarized adaptive thinking and context management for API-key Opus 4.7+ requests", async () => { const payload = (await captureAnthropicPayload( buildModel({ ...ANTHROPIC_MODEL_SPEC, @@ -1892,12 +1892,14 @@ describe("Anthropic request fingerprint alignment", () => { }, )) as { thinking?: { type?: string; display?: string }; - context_management?: unknown; + context_management?: { edits?: Array<{ type?: string; keep?: string | number }> }; output_config?: { effort?: string }; }; expect(payload.thinking).toEqual({ type: "adaptive", display: "summarized" }); - expect(payload.context_management).toBeUndefined(); + expect(payload.context_management).toEqual({ + edits: [{ type: "clear_thinking_20251015", keep: "all" }], + }); expect(payload.output_config).toEqual({ effort: "xhigh" }); }); diff --git a/packages/ai/test/anthropic-unsigned-thinking-replay.test.ts b/packages/ai/test/anthropic-unsigned-thinking-replay.test.ts index 1aa908bd8..6cd7369b2 100644 --- a/packages/ai/test/anthropic-unsigned-thinking-replay.test.ts +++ b/packages/ai/test/anthropic-unsigned-thinking-replay.test.ts @@ -101,6 +101,29 @@ describe("Anthropic-compatible unsigned thinking replay (#2005)", () => { expect(blocks[1]).toEqual({ type: "text", text: "Sure." }); }); + it("sends context_management for API-key Anthropic-compatible thinking requests", async () => { + const { promise, resolve } = Promise.withResolvers(); + streamAnthropic( + makeModel(), + { systemPrompt: [], messages: [makeUser("continue")] }, + { + apiKey: "sk-ant-api-test", + signal: AbortSignal.abort(), + thinkingEnabled: true, + onPayload: payload => resolve(payload), + }, + ); + + const payload = (await promise) as { + thinking?: { type?: string }; + context_management?: { edits?: Array<{ type?: string; keep?: string }> }; + }; + expect(payload.thinking?.type).toBe("enabled"); + expect(payload.context_management).toEqual({ + edits: [{ type: "clear_thinking_20251015", keep: "all" }], + }); + }); + it("sanitizes lone surrogates in tool arguments regardless of origin API", () => { const loneSurrogate = "broken \ud83d end"; const makeToolCallAssistant = (api: AssistantMessage["api"]): AssistantMessage => ({ From 6b3c7ad3766cbe092a5786b3ebd1f080a32069c5 Mon Sep 17 00:00:00 2001 From: roboomp Date: Tue, 23 Jun 2026 01:35:42 +0000 Subject: [PATCH 2/4] fix(ai): advertised context-management beta for api-key thinking Anthropic rejects context_management.clear_thinking_20251015 without the context-management-2025-06-27 beta header. OAuth requests carried it via claudeCodeAgentBetaDefaults; API-key requests pushed the new context_management field without the beta, so the server rejected them. Push the beta into extraBetas alongside the field for every API-key thinking request, and exclude the GitHub Copilot proxy (which strips Anthropic betas and demotes thinking blocks upstream) from emitting the field. Fixes #3288 --- packages/ai/CHANGELOG.md | 2 +- packages/ai/src/providers/anthropic.ts | 28 ++++++++++++--- packages/ai/test/anthropic-alignment.test.ts | 35 +++++++++++++++++++ .../ai/test/github-copilot-reasoning.test.ts | 6 ++++ 4 files changed, 66 insertions(+), 5 deletions(-) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 6aad3230a..f167fe2d3 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -4,7 +4,7 @@ ### Fixed -- Fixed Anthropic-compatible thinking requests sending replayed thinking blocks without `context_management.keep: "all"`, preserving multi-turn reasoning context for API-key providers. ([#3288](https://github.com/can1357/oh-my-pi/issues/3288)) +- Fixed Anthropic-compatible thinking requests sending replayed thinking blocks without `context_management.keep: "all"`, preserving multi-turn reasoning context for API-key providers. API-key requests now also advertise the required `context-management-2025-06-27` beta header so the field is honored instead of rejected. GitHub Copilot's Anthropic proxy is excluded because it strips Anthropic betas and demotes thinking blocks to text upstream. ([#3288](https://github.com/can1357/oh-my-pi/issues/3288)) ## [16.1.15] - 2026-06-22 diff --git a/packages/ai/src/providers/anthropic.ts b/packages/ai/src/providers/anthropic.ts index f681f7f54..7543d0594 100644 --- a/packages/ai/src/providers/anthropic.ts +++ b/packages/ai/src/providers/anthropic.ts @@ -120,10 +120,11 @@ export function buildBetaHeader(baseBetas: readonly string[], extraBetas: readon } const midConversationSystemBeta = "mid-conversation-system-2026-04-07"; +const contextManagementBeta = "context-management-2025-06-27"; const claudeCodeUtilityBetaDefaults = [ "oauth-2025-04-20", "interleaved-thinking-2025-05-14", - "context-management-2025-06-27", + contextManagementBeta, "prompt-caching-scope-2026-01-05", "structured-outputs-2025-12-15", ] as const; @@ -131,7 +132,7 @@ const claudeCodeAgentBetaDefaults = [ "claude-code-20250219", "oauth-2025-04-20", "interleaved-thinking-2025-05-14", - "context-management-2025-06-27", + contextManagementBeta, "prompt-caching-scope-2026-01-05", midConversationSystemBeta, "advanced-tool-use-2025-11-20", @@ -1680,6 +1681,20 @@ const streamAnthropicOnce = ( // carry it in the Claude Code list). extraBetas.push(midConversationSystemBeta); } + // `context_management.clear_thinking_20251015` requires this beta. OAuth + // requests carry it in `claudeCodeAgentBetaDefaults`; API-key requests + // need it added explicitly so the field is honored instead of rejected + // (#3288). Skip Copilot — its proxy strips Anthropic betas and the + // upstream compat flag demotes thinking blocks to text, so there is + // nothing for `keep: "all"` to preserve. + if ( + model.reasoning && + options?.thinkingEnabled && + model.provider !== "github-copilot" && + !extraBetas.includes(contextManagementBeta) + ) { + extraBetas.push(contextManagementBeta); + } const created = createClient(model, { model, @@ -2950,8 +2965,13 @@ function buildParams( // strip the replayed thinking blocks `replayUnsignedThinking` puts back // on the wire, so the model loses the prior reasoning chain across turns // and the KV cache misses every turn (#3288). Narrowing this guard back - // to `isOAuthToken` regresses every API-key thinking provider. - const shouldKeepThinkingContext = thinking?.type === "adaptive" || thinking?.type === "enabled"; + // to `isOAuthToken` regresses every API-key thinking provider. Skip + // Copilot — its proxy strips Anthropic betas (so the required + // `context-management-2025-06-27` header never lands) and the compat + // flag demotes thinking blocks to text, so `keep: "all"` is a no-op + // that risks the proxy rejecting an unrecognized field. + const shouldKeepThinkingContext = + model.provider !== "github-copilot" && (thinking?.type === "adaptive" || thinking?.type === "enabled"); const contextManagement = shouldKeepThinkingContext ? { edits: [{ type: "clear_thinking_20251015" as const, keep: "all" as const }] } : undefined; diff --git a/packages/ai/test/anthropic-alignment.test.ts b/packages/ai/test/anthropic-alignment.test.ts index 47a2714fc..f4efa80f7 100644 --- a/packages/ai/test/anthropic-alignment.test.ts +++ b/packages/ai/test/anthropic-alignment.test.ts @@ -406,6 +406,41 @@ describe("Anthropic request fingerprint alignment", () => { expect(capturedBeta).toContain("mid-conversation-system-2026-04-07"); }); + it("adds the context-management beta to API-key thinking requests", async () => { + let capturedBeta: string | undefined; + const fetchMock = (async (_input: string | URL | Request, init?: RequestInit) => { + capturedBeta = (init?.headers as Record | undefined)?.["anthropic-beta"]; + return new Response( + JSON.stringify({ type: "error", error: { type: "invalid_request_error", message: "captured" } }), + { status: 400, headers: { "Content-Type": "application/json" } }, + ); + }) as typeof fetch; + + // `context_management.clear_thinking_20251015` is rejected without + // the `context-management-2025-06-27` beta. OAuth requests carry it + // via `claudeCodeAgentBetaDefaults`; API-key requests must add it + // explicitly whenever thinking is enabled so the field is honored + // instead of dropped on the floor (#3288). + await streamAnthropic( + ANTHROPIC_MODEL, + { systemPrompt: ["Stay concise."], messages: [{ role: "user", content: "Hi", timestamp: Date.now() }] }, + { apiKey: "sk-ant-api-test", thinkingEnabled: true, fetch: fetchMock }, + ).result(); + + expect(capturedBeta).toContain("context-management-2025-06-27"); + + capturedBeta = undefined; + await streamAnthropic( + ANTHROPIC_MODEL, + { systemPrompt: ["Stay concise."], messages: [{ role: "user", content: "Hi", timestamp: Date.now() }] }, + { apiKey: "sk-ant-api-test", thinkingEnabled: false, fetch: fetchMock }, + ).result(); + + // No context_management field is sent when thinking is disabled, so the + // beta MUST NOT be advertised either. + expect(capturedBeta ?? "").not.toContain("context-management-2025-06-27"); + }); + it("billing-header fingerprint uses first user message, not leading developer message", async () => { const userText = "Hello from user with enough chars padding here"; diff --git a/packages/ai/test/github-copilot-reasoning.test.ts b/packages/ai/test/github-copilot-reasoning.test.ts index 6360c40dd..e96a2c710 100644 --- a/packages/ai/test/github-copilot-reasoning.test.ts +++ b/packages/ai/test/github-copilot-reasoning.test.ts @@ -56,9 +56,15 @@ describe("GitHub Copilot reasoning request construction", () => { const payload = (await captureAnthropicPayload(model)) as { thinking?: { type?: string }; output_config?: { effort?: string }; + context_management?: unknown; }; expect(payload.thinking).toEqual({ type: "adaptive" }); expect(payload.output_config).toEqual({ effort: "high" }); + // The Copilot Anthropic proxy strips Anthropic betas and demotes + // thinking blocks to text upstream — the `context_management` field + // would have no replayed thinking to keep and risks proxy rejection + // of an unrecognized field. The field MUST NOT be sent (#3288). + expect(payload.context_management).toBeUndefined(); }); }); From 5291b2f5a07f81e64a8312f00c7c7e2f04a1e02a Mon Sep 17 00:00:00 2001 From: roboomp Date: Tue, 23 Jun 2026 01:42:53 +0000 Subject: [PATCH 3/4] fix(ai): skipped context management for injected clients Injected Anthropic clients bypass buildAnthropicClientOptions, so this package cannot add the context-management beta header their SDK instance would need before accepting context_management.clear_thinking_20251015. Omit context_management for options.client requests while preserving thinking itself, and add regression coverage for injected-client payload shaping. Fixes #3288 --- packages/ai/CHANGELOG.md | 2 +- packages/ai/src/providers/anthropic.ts | 13 ++++---- .../ai/test/anthropic-stream-envelope.test.ts | 30 +++++++++++++++++++ 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index f167fe2d3..bd038ba1a 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -4,7 +4,7 @@ ### Fixed -- Fixed Anthropic-compatible thinking requests sending replayed thinking blocks without `context_management.keep: "all"`, preserving multi-turn reasoning context for API-key providers. API-key requests now also advertise the required `context-management-2025-06-27` beta header so the field is honored instead of rejected. GitHub Copilot's Anthropic proxy is excluded because it strips Anthropic betas and demotes thinking blocks to text upstream. ([#3288](https://github.com/can1357/oh-my-pi/issues/3288)) +- Fixed Anthropic-compatible thinking requests sending replayed thinking blocks without `context_management.keep: "all"`, preserving multi-turn reasoning context for API-key providers. API-key requests now also advertise the required `context-management-2025-06-27` beta header so the field is honored instead of rejected. Injected SDK clients and GitHub Copilot's Anthropic proxy are excluded because this code path cannot add the beta to caller-owned clients, while Copilot strips Anthropic betas and demotes thinking blocks to text upstream. ([#3288](https://github.com/can1357/oh-my-pi/issues/3288)) ## [16.1.15] - 2026-06-22 diff --git a/packages/ai/src/providers/anthropic.ts b/packages/ai/src/providers/anthropic.ts index 7543d0594..8300316a2 100644 --- a/packages/ai/src/providers/anthropic.ts +++ b/packages/ai/src/providers/anthropic.ts @@ -2966,12 +2966,15 @@ function buildParams( // on the wire, so the model loses the prior reasoning chain across turns // and the KV cache misses every turn (#3288). Narrowing this guard back // to `isOAuthToken` regresses every API-key thinking provider. Skip - // Copilot — its proxy strips Anthropic betas (so the required - // `context-management-2025-06-27` header never lands) and the compat - // flag demotes thinking blocks to text, so `keep: "all"` is a no-op - // that risks the proxy rejecting an unrecognized field. + // injected clients because this code cannot add the required + // `context-management-2025-06-27` beta to caller-owned SDK clients. Skip + // Copilot because its proxy strips Anthropic betas and demotes thinking + // blocks to text upstream, so `keep: "all"` is a no-op that risks proxy + // rejection of an unrecognized field. const shouldKeepThinkingContext = - model.provider !== "github-copilot" && (thinking?.type === "adaptive" || thinking?.type === "enabled"); + !options?.client && + model.provider !== "github-copilot" && + (thinking?.type === "adaptive" || thinking?.type === "enabled"); const contextManagement = shouldKeepThinkingContext ? { edits: [{ type: "clear_thinking_20251015" as const, keep: "all" as const }] } : undefined; diff --git a/packages/ai/test/anthropic-stream-envelope.test.ts b/packages/ai/test/anthropic-stream-envelope.test.ts index f73748009..27cee8e5c 100644 --- a/packages/ai/test/anthropic-stream-envelope.test.ts +++ b/packages/ai/test/anthropic-stream-envelope.test.ts @@ -576,6 +576,36 @@ describe("anthropic stream envelope handling", () => { expect(capturedParams?.tools?.map(tool => tool.name)).toEqual(["web_search"]); expect(capturedOptions?.headers).toEqual({ "X-Umans-Websearch-Provider": "exa" }); }); + + it("does not send context_management through injected clients", async () => { + type CapturedPayload = { + thinking?: { type?: string }; + context_management?: unknown; + }; + let capturedParams: CapturedPayload | undefined; + const client: AnthropicMessagesClientLike = { + messages: { + create(params) { + capturedParams = params as CapturedPayload; + return createMockRequest(createTextSuccessEvents("done")); + }, + }, + }; + + const stream = streamAnthropic(model, context, { + client, + thinkingEnabled: true, + }); + const events: AssistantMessageEvent[] = []; + for await (const event of stream) { + events.push(event); + } + const result = await stream.result(); + + expect(result.content).toEqual([{ type: "text", text: "done" }]); + expect(capturedParams?.thinking?.type).toBe("enabled"); + expect(capturedParams?.context_management).toBeUndefined(); + }); it("unwraps thinking blocks that Anthropic streams with literal thinking tags", async () => { const wrappedThinking = "\n\nCheck logs before accepting container health.\n"; From c04fa895694203a961c323e364365c2615a03633 Mon Sep 17 00:00:00 2001 From: roboomp Date: Tue, 23 Jun 2026 01:54:33 +0000 Subject: [PATCH 4/4] fix(ai): skipped context management for vertex rawpredict Vertex Claude rawPredict expects Anthropic beta flags in the JSON body as anthropic_beta. The Anthropic stream path can only add context-management-2025-06-27 as an HTTP header there, so sending context_management would make reasoning requests fail. Omit context_management and its beta header for google-vertex Anthropic models while preserving thinking itself, and add regression coverage to the rawPredict routing test. Fixes #3288 --- packages/ai/CHANGELOG.md | 2 +- packages/ai/src/providers/anthropic.ts | 13 +++++++++---- packages/ai/test/stream.test.ts | 12 +++++++++++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index bd038ba1a..329d36472 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -4,7 +4,7 @@ ### Fixed -- Fixed Anthropic-compatible thinking requests sending replayed thinking blocks without `context_management.keep: "all"`, preserving multi-turn reasoning context for API-key providers. API-key requests now also advertise the required `context-management-2025-06-27` beta header so the field is honored instead of rejected. Injected SDK clients and GitHub Copilot's Anthropic proxy are excluded because this code path cannot add the beta to caller-owned clients, while Copilot strips Anthropic betas and demotes thinking blocks to text upstream. ([#3288](https://github.com/can1357/oh-my-pi/issues/3288)) +- Fixed Anthropic-compatible thinking requests sending replayed thinking blocks without `context_management.keep: "all"`, preserving multi-turn reasoning context for API-key providers. API-key requests now also advertise the required `context-management-2025-06-27` beta header so the field is honored instead of rejected. Injected SDK clients, GitHub Copilot's Anthropic proxy, and Vertex rawPredict are excluded because this code path cannot add the beta to caller-owned clients, Copilot strips Anthropic betas and demotes thinking blocks to text upstream, and Vertex expects betas in the JSON body rather than the Anthropic HTTP beta header. ([#3288](https://github.com/can1357/oh-my-pi/issues/3288)) ## [16.1.15] - 2026-06-22 diff --git a/packages/ai/src/providers/anthropic.ts b/packages/ai/src/providers/anthropic.ts index 8300316a2..ab5974f3a 100644 --- a/packages/ai/src/providers/anthropic.ts +++ b/packages/ai/src/providers/anthropic.ts @@ -1684,13 +1684,15 @@ const streamAnthropicOnce = ( // `context_management.clear_thinking_20251015` requires this beta. OAuth // requests carry it in `claudeCodeAgentBetaDefaults`; API-key requests // need it added explicitly so the field is honored instead of rejected - // (#3288). Skip Copilot — its proxy strips Anthropic betas and the - // upstream compat flag demotes thinking blocks to text, so there is - // nothing for `keep: "all"` to preserve. + // (#3288). Skip transports where this package cannot deliver the beta + // in the form their adapter accepts: Copilot strips Anthropic betas, + // and Vertex rawPredict needs betas in the body (`anthropic_beta`), + // not as an `anthropic-beta` HTTP header. if ( model.reasoning && options?.thinkingEnabled && model.provider !== "github-copilot" && + model.provider !== "google-vertex" && !extraBetas.includes(contextManagementBeta) ) { extraBetas.push(contextManagementBeta); @@ -2970,10 +2972,13 @@ function buildParams( // `context-management-2025-06-27` beta to caller-owned SDK clients. Skip // Copilot because its proxy strips Anthropic betas and demotes thinking // blocks to text upstream, so `keep: "all"` is a no-op that risks proxy - // rejection of an unrecognized field. + // rejection of an unrecognized field. Skip Vertex rawPredict because that + // adapter requires betas in the JSON body (`anthropic_beta`) instead of the + // Anthropic HTTP beta header this code can add. const shouldKeepThinkingContext = !options?.client && model.provider !== "github-copilot" && + model.provider !== "google-vertex" && (thinking?.type === "adaptive" || thinking?.type === "enabled"); const contextManagement = shouldKeepThinkingContext ? { edits: [{ type: "clear_thinking_20251015" as const, keep: "all" as const }] } diff --git a/packages/ai/test/stream.test.ts b/packages/ai/test/stream.test.ts index bdf1a7218..c3891d1a5 100644 --- a/packages/ai/test/stream.test.ts +++ b/packages/ai/test/stream.test.ts @@ -580,7 +580,12 @@ describe("Generate E2E Tests", () => { contextWindow: 200_000, maxTokens: 64_000, }); - const captured = Promise.withResolvers<{ url: string; authorization: string | null; body: unknown }>(); + const captured = Promise.withResolvers<{ + url: string; + authorization: string | null; + betaHeader: string | null; + body: unknown; + }>(); try { __resetVertexTokenCache(); @@ -598,6 +603,7 @@ describe("Generate E2E Tests", () => { { messages: [{ role: "user", content: "Hello", timestamp: Date.now() }] }, { apiKey: "", + thinkingEnabled: true, fetch: async (input, init) => { const url = input instanceof Request ? input.url : input.toString(); if ( @@ -611,6 +617,7 @@ describe("Generate E2E Tests", () => { captured.resolve({ url, authorization: headers.get("authorization"), + betaHeader: headers.get("anthropic-beta"), body: JSON.parse(bodyText), }); return new Response(JSON.stringify({ error: { message: "stop after capture" } }), { status: 400 }); @@ -632,6 +639,9 @@ describe("Generate E2E Tests", () => { stream: true, }); expect((request.body as Record).model).toBeUndefined(); + expect((request.body as Record).thinking?.type).toBe("enabled"); + expect((request.body as Record).context_management).toBeUndefined(); + expect(request.betaHeader ?? "").not.toContain("context-management-2025-06-27"); } finally { __resetVertexTokenCache(); homedirSpy.mockRestore();