From c5c9cb056a72155386309eaa431bf4a0bd342238 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Wed, 22 Jul 2026 13:03:46 +0900 Subject: [PATCH] fix: use OS home for cached snapshot tilde paths (#112236) * fix: avoid OCM snapshot path false positives * refactor: enforce OS home for snapshot paths --- .../doctor-session-snapshots.test-support.ts | 1 - src/commands/doctor-session-snapshots.test.ts | 26 +++++++++++-------- src/commands/doctor-session-snapshots.ts | 15 +++++------ 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/commands/doctor-session-snapshots.test-support.ts b/src/commands/doctor-session-snapshots.test-support.ts index 536953fc04ea..bbbcbb2ad469 100644 --- a/src/commands/doctor-session-snapshots.test-support.ts +++ b/src/commands/doctor-session-snapshots.test-support.ts @@ -18,7 +18,6 @@ type TestApi = { store: Record; bundledSkillsDir: string | undefined; pathExists?: (filePath: string) => boolean; - homeDir?: string; env?: NodeJS.ProcessEnv; }): Array<{ sessionKey: string; diff --git a/src/commands/doctor-session-snapshots.test.ts b/src/commands/doctor-session-snapshots.test.ts index 529b323328a7..52092a1bd701 100644 --- a/src/commands/doctor-session-snapshots.test.ts +++ b/src/commands/doctor-session-snapshots.test.ts @@ -234,29 +234,33 @@ describe("doctor session snapshot stale runtime metadata", () => { }); }); - it("expands home-relative cached bundled skill locations before classifying them", () => { + it("uses the OS home for cached OCM paths when OPENCLAW_HOME differs", () => { const homeDir = path.join(root, "home"); - const stalePath = "~/old-runtime/node_modules/openclaw/skills/doctor/SKILL.md"; + const currentBundledSkillsDir = path.join(homeDir, ".ocm/current/node_modules/openclaw/skills"); + const expectedPath = path.join(currentBundledSkillsDir, "doctor", "SKILL.md"); + const currentPath = "~/.ocm/current/node_modules/openclaw/skills/doctor/SKILL.md"; + const stalePath = "~/.ocm/old/node_modules/openclaw/skills/doctor/SKILL.md"; const findings = scanSessionStoreForStaleRuntimeSnapshotPaths({ - bundledSkillsDir, - env: { HOME: homeDir }, + bundledSkillsDir: currentBundledSkillsDir, + env: { HOME: homeDir, OPENCLAW_HOME: path.join(root, "ocm-profile") }, store: { - "agent:home": sessionEntry({ - skillsSnapshot: { - prompt: skillPrompt(stalePath), - skills: [{ name: "doctor" }], - }, + "agent:current": sessionEntry({ + skillsSnapshot: { prompt: skillPrompt(currentPath), skills: [{ name: "doctor" }] }, + }), + "agent:stale": sessionEntry({ + skillsSnapshot: { prompt: skillPrompt(stalePath), skills: [{ name: "doctor" }] }, }), }, + pathExists: (filePath) => filePath === expectedPath, }); expect(findings).toEqual([ { - sessionKey: "agent:home", + sessionKey: "agent:stale", field: "skillsSnapshot.prompt", cachedPath: stalePath, - expectedPath: path.join(bundledSkillsDir, "doctor", "SKILL.md"), + expectedPath, }, ]); }); diff --git a/src/commands/doctor-session-snapshots.ts b/src/commands/doctor-session-snapshots.ts index 29326fd3df5f..627fdc33896e 100644 --- a/src/commands/doctor-session-snapshots.ts +++ b/src/commands/doctor-session-snapshots.ts @@ -10,7 +10,7 @@ import { resolveAllAgentSessionStoreTargetsSync } from "../config/sessions/targe import type { SessionEntry } from "../config/sessions/types.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import type { HealthFinding, HealthRepairEffect } from "../flows/health-checks.js"; -import { expandHomePrefix } from "../infra/home-dir.js"; +import { expandHomePrefix, resolveOsHomeDir } from "../infra/home-dir.js"; import { writeTextAtomic } from "../infra/json-files.js"; import { resolveOpenClawPackageRootSync } from "../infra/openclaw-root.js"; import { resolveBundledSkillsDir } from "../skills/loading/bundled-dir.js"; @@ -214,13 +214,14 @@ function resolveExpectedBundledSkillPath(params: { cachedPath: string; bundledSkillsDir: string; pathExists: (filePath: string) => boolean; - homeDir?: string; env?: NodeJS.ProcessEnv; }): string | undefined { - const expandedCachedPath = expandHomePrefix(params.cachedPath, { - home: params.homeDir, - env: params.env, - }); + // Snapshot paths use shell `~` semantics. OPENCLAW_HOME may point at an isolated + // runtime profile, so expanding against it would make the active runtime look stale. + const osHomeDir = resolveOsHomeDir(params.env); + const expandedCachedPath = osHomeDir + ? expandHomePrefix(params.cachedPath, { home: osHomeDir }) + : params.cachedPath; if (!isAbsolutePathLike(expandedCachedPath)) { return undefined; } @@ -263,7 +264,6 @@ function scanSessionStoreForStaleRuntimeSnapshotPaths(params: { store: Record; bundledSkillsDir: string | undefined; pathExists?: (filePath: string) => boolean; - homeDir?: string; env?: NodeJS.ProcessEnv; }): StaleSessionSnapshotPathFinding[] { const bundledSkillsDir = params.bundledSkillsDir?.trim(); @@ -282,7 +282,6 @@ function scanSessionStoreForStaleRuntimeSnapshotPaths(params: { cachedPath: cached.path, bundledSkillsDir, pathExists, - homeDir: params.homeDir, env: params.env, }); if (!expectedPath) {