mirror of
https://github.com/openclaw/clawhub.git
synced 2026-07-22 03:35:29 -04:00
fix: prefer $HOME over os.homedir() for path resolution
os.homedir() reads from /etc/passwd which can return a stale path after a Linux user rename (usermod -l). Prefer the $HOME environment variable which reflects the current session. Closes #82 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ import { mkdir, mkdtemp, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join, resolve } from 'node:path'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { resolveHome } from '../homedir.js'
|
||||
import { resolveClawdbotDefaultWorkspace, resolveClawdbotSkillRoots } from './clawdbotConfig.js'
|
||||
|
||||
const originalEnv = { ...process.env }
|
||||
@@ -177,6 +178,31 @@ describe('resolveClawdbotSkillRoots', () => {
|
||||
expect(labels[resolve(openclawStateDir, 'skills')]).toBe('OpenClaw: Shared skills')
|
||||
})
|
||||
|
||||
it('uses $HOME over os.homedir() for tilde expansion', async () => {
|
||||
const base = await mkdtemp(join(tmpdir(), 'clawhub-home-override-'))
|
||||
const customHome = join(base, 'custom-home')
|
||||
const stateDir = join(base, 'state')
|
||||
const configPath = join(base, 'clawdbot.json')
|
||||
const openclawStateDir = join(base, 'openclaw-state')
|
||||
|
||||
process.env.HOME = customHome
|
||||
process.env.CLAWDBOT_STATE_DIR = stateDir
|
||||
process.env.CLAWDBOT_CONFIG_PATH = configPath
|
||||
process.env.OPENCLAW_STATE_DIR = openclawStateDir
|
||||
process.env.OPENCLAW_CONFIG_PATH = join(openclawStateDir, 'openclaw.json')
|
||||
|
||||
const config = `{
|
||||
agents: {
|
||||
defaults: { workspace: "~/my-workspace" },
|
||||
},
|
||||
}`
|
||||
await writeFile(configPath, config, 'utf8')
|
||||
|
||||
const workspace = await resolveClawdbotDefaultWorkspace()
|
||||
expect(workspace).toBe(resolve(customHome, 'my-workspace'))
|
||||
expect(resolveHome()).toBe(customHome)
|
||||
})
|
||||
|
||||
it('supports OpenClaw configuration files', async () => {
|
||||
const base = await mkdtemp(join(tmpdir(), 'clawhub-openclaw-'))
|
||||
const stateDir = join(base, 'openclaw-state')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { homedir } from 'node:os'
|
||||
import { basename, join, resolve } from 'node:path'
|
||||
import JSON5 from 'json5'
|
||||
import { resolveHome } from '../homedir.js'
|
||||
|
||||
type ClawdbotConfig = {
|
||||
agent?: { workspace?: string }
|
||||
@@ -95,7 +95,7 @@ export async function resolveClawdbotDefaultWorkspace(): Promise<string | null>
|
||||
function resolveClawdbotStateDir() {
|
||||
const override = process.env.CLAWDBOT_STATE_DIR?.trim()
|
||||
if (override) return resolveUserPath(override)
|
||||
return join(homedir(), '.clawdbot')
|
||||
return join(resolveHome(), '.clawdbot')
|
||||
}
|
||||
|
||||
function resolveClawdbotConfigPath() {
|
||||
@@ -107,7 +107,7 @@ function resolveClawdbotConfigPath() {
|
||||
function resolveOpenclawStateDir() {
|
||||
const override = process.env.OPENCLAW_STATE_DIR?.trim()
|
||||
if (override) return resolveUserPath(override)
|
||||
return join(homedir(), '.openclaw')
|
||||
return join(resolveHome(), '.openclaw')
|
||||
}
|
||||
|
||||
function resolveOpenclawConfigPath() {
|
||||
@@ -120,7 +120,7 @@ function resolveUserPath(input: string) {
|
||||
const trimmed = input.trim()
|
||||
if (!trimmed) return ''
|
||||
if (trimmed.startsWith('~')) {
|
||||
return resolve(trimmed.replace(/^~(?=$|[\\/])/, homedir()))
|
||||
return resolve(trimmed.replace(/^~(?=$|[\\/])/, resolveHome()))
|
||||
}
|
||||
return resolve(trimmed)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { realpath } from 'node:fs/promises'
|
||||
import { homedir } from 'node:os'
|
||||
import { resolve } from 'node:path'
|
||||
import { resolveHome } from '../../homedir.js'
|
||||
import { isCancel, multiselect } from '@clack/prompts'
|
||||
import semver from 'semver'
|
||||
import { apiRequest, downloadZip } from '../../http.js'
|
||||
@@ -338,7 +338,7 @@ export function printSection(title: string, body?: string) {
|
||||
}
|
||||
|
||||
function abbreviatePath(value: string) {
|
||||
const home = homedir()
|
||||
const home = resolveHome()
|
||||
if (value.startsWith(home)) return `~${value.slice(home.length)}`
|
||||
return value
|
||||
}
|
||||
@@ -348,7 +348,7 @@ function rootTelemetryId(value: string) {
|
||||
}
|
||||
|
||||
function formatRootLabel(value: string) {
|
||||
const home = homedir()
|
||||
const home = resolveHome()
|
||||
if (value === home) return '~'
|
||||
|
||||
const normalized = value.replaceAll('\\', '/')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { readdir, stat } from 'node:fs/promises'
|
||||
import { homedir } from 'node:os'
|
||||
import { basename, join, resolve } from 'node:path'
|
||||
import { resolveHome } from '../homedir.js'
|
||||
import { sanitizeSlug, titleCase } from './slug.js'
|
||||
|
||||
export type SkillFolder = {
|
||||
@@ -30,7 +30,7 @@ export async function findSkillFolders(root: string): Promise<SkillFolder[]> {
|
||||
}
|
||||
|
||||
export function getFallbackSkillRoots(workdir: string) {
|
||||
const home = homedir()
|
||||
const home = resolveHome()
|
||||
const roots = [
|
||||
// adjacent repo installs
|
||||
resolve(workdir, '..', 'clawdis', 'skills'),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { existsSync } from 'node:fs'
|
||||
import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises'
|
||||
import { homedir } from 'node:os'
|
||||
import { dirname, join, resolve } from 'node:path'
|
||||
import { resolveHome } from './homedir.js'
|
||||
import { type GlobalConfig, GlobalConfigSchema, parseArk } from './schema/index.js'
|
||||
|
||||
/**
|
||||
@@ -27,7 +27,7 @@ export function getGlobalConfigPath() {
|
||||
process.env.CLAWHUB_CONFIG_PATH?.trim() ?? process.env.CLAWDHUB_CONFIG_PATH?.trim()
|
||||
if (override) return resolve(override)
|
||||
|
||||
const home = homedir()
|
||||
const home = resolveHome()
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
return resolveConfigPath(join(home, 'Library', 'Application Support'))
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { homedir } from 'node:os'
|
||||
|
||||
/**
|
||||
* Resolve the user's home directory, preferring environment variables over
|
||||
* os.homedir(). On Linux, os.homedir() reads from /etc/passwd which can
|
||||
* return a stale path after a user rename (usermod -l). The $HOME env var
|
||||
* is set by the login process and reflects the current session.
|
||||
*/
|
||||
export function resolveHome(): string {
|
||||
if (process.platform === 'win32') {
|
||||
return process.env.USERPROFILE?.trim() || process.env.HOME?.trim() || homedir()
|
||||
}
|
||||
return process.env.HOME?.trim() || homedir()
|
||||
}
|
||||
Reference in New Issue
Block a user