fix(paths): respect OPENCLAW_HOME for all internal path resolution (#12091)

* fix(paths): respect OPENCLAW_HOME for all internal path resolution (#11995)

Add home-dir module (src/infra/home-dir.ts) that centralizes home
directory resolution with precedence: OPENCLAW_HOME > HOME > USERPROFILE > os.homedir().

Migrate all path-sensitive callsites: config IO, agent dirs, session
transcripts, pairing store, cron store, doctor, CLI profiles.

Add envHomedir() helper in config/paths.ts to reduce lambda noise.
Document OPENCLAW_HOME in docs/help/environment.md.

* fix(paths): handle OPENCLAW_HOME '~' fallback (#12091) (thanks @sebslight)

* docs: mention OPENCLAW_HOME in install and getting started (#12091) (thanks @sebslight)

* fix(status): show OPENCLAW_HOME in shortened paths (#12091) (thanks @sebslight)

* docs(changelog): clarify OPENCLAW_HOME and HOME precedence (#12091) (thanks @sebslight)
This commit is contained in:
Seb Slight
2026-02-08 16:20:13 -05:00
committed by GitHub
parent c95e6fe6dc
commit db137dd65d
32 changed files with 586 additions and 74 deletions

View File

@@ -3,6 +3,11 @@ import os from "node:os";
import path from "node:path";
import { resolveOAuthDir } from "./config/paths.js";
import { logVerbose, shouldLogVerbose } from "./globals.js";
import {
expandHomePrefix,
resolveEffectiveHomeDir,
resolveRequiredHomeDir,
} from "./infra/home-dir.js";
export async function ensureDir(dir: string) {
await fs.promises.mkdir(dir, { recursive: true });
@@ -239,7 +244,11 @@ export function resolveUserPath(input: string): string {
return trimmed;
}
if (trimmed.startsWith("~")) {
const expanded = trimmed.replace(/^~(?=$|[\\/])/, os.homedir());
const expanded = expandHomePrefix(trimmed, {
home: resolveRequiredHomeDir(process.env, os.homedir),
env: process.env,
homedir: os.homedir,
});
return path.resolve(expanded);
}
return path.resolve(trimmed);
@@ -253,7 +262,7 @@ export function resolveConfigDir(
if (override) {
return resolveUserPath(override);
}
const newDir = path.join(homedir(), ".openclaw");
const newDir = path.join(resolveRequiredHomeDir(env, homedir), ".openclaw");
try {
const hasNew = fs.existsSync(newDir);
if (hasNew) {
@@ -266,35 +275,35 @@ export function resolveConfigDir(
}
export function resolveHomeDir(): string | undefined {
const envHome = process.env.HOME?.trim();
if (envHome) {
return envHome;
}
const envProfile = process.env.USERPROFILE?.trim();
if (envProfile) {
return envProfile;
}
try {
const home = os.homedir();
return home?.trim() ? home : undefined;
} catch {
return resolveEffectiveHomeDir(process.env, os.homedir);
}
function resolveHomeDisplayPrefix(): { home: string; prefix: string } | undefined {
const home = resolveHomeDir();
if (!home) {
return undefined;
}
const explicitHome = process.env.OPENCLAW_HOME?.trim();
if (explicitHome) {
return { home, prefix: "$OPENCLAW_HOME" };
}
return { home, prefix: "~" };
}
export function shortenHomePath(input: string): string {
if (!input) {
return input;
}
const home = resolveHomeDir();
if (!home) {
const display = resolveHomeDisplayPrefix();
if (!display) {
return input;
}
const { home, prefix } = display;
if (input === home) {
return "~";
return prefix;
}
if (input.startsWith(`${home}/`)) {
return `~${input.slice(home.length)}`;
if (input.startsWith(`${home}/`) || input.startsWith(`${home}\\`)) {
return `${prefix}${input.slice(home.length)}`;
}
return input;
}
@@ -303,11 +312,11 @@ export function shortenHomeInString(input: string): string {
if (!input) {
return input;
}
const home = resolveHomeDir();
if (!home) {
const display = resolveHomeDisplayPrefix();
if (!display) {
return input;
}
return input.split(home).join("~");
return input.split(display.home).join(display.prefix);
}
export function displayPath(input: string): string {