mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:50:42 +00:00
fix(daemon): filter missing service path fallbacks
This commit is contained in:
@@ -17,6 +17,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- CLI/Ollama: run local `infer model run` through the lean provider completion path and skip global model discovery for one-shot local probes, so Ollama smoke tests no longer pay full chat-agent/tool startup cost or hang before the native `/api/chat` request. Fixes #72851. Thanks @TotalRes2020.
|
||||
- Daemon/service: only emit hard-coded version-manager paths such as `~/.volta/bin`, `~/.asdf/shims`, `~/.bun/bin`, and fnm/pnpm fallbacks into gateway and node service PATHs when the directories exist, so `openclaw doctor` no longer flags `gateway.path.non-minimal` against a PATH the daemon just wrote. Env-driven roots and stable user-bin dirs remain unconditional. Fixes #71944; carries forward #71964. Thanks @Sanjays2402.
|
||||
- Channels/commands: make generated `/dock-*` commands switch the active session reply route through `session.identityLinks` instead of falling through to normal chat. Fixes #69206; carries forward #73033. Thanks @clawbones and @michaelatamuk.
|
||||
- Providers/Cloudflare AI Gateway: strip assistant prefill turns from Anthropic Messages payloads when thinking is enabled, so Claude requests through Cloudflare AI Gateway no longer fail Anthropic conversation-ending validation. Fixes #72905; carries forward #73005. Thanks @AaronFaby and @sahilsatralkar.
|
||||
- Gateway/startup: keep primary-model startup prewarm on scoped metadata preparation, let native approval bootstraps retry outside channel startup, and skip the global hook runner when no `gateway_start` hook is registered, so clean post-ready sidecar work stays off the critical path. Refs #72846. Thanks @RayWoo, @livekm0309, and @mrz1836.
|
||||
|
||||
@@ -455,6 +455,9 @@ That stages grounded durable candidates into the short-term dreaming store while
|
||||
</Accordion>
|
||||
<Accordion title="17. Gateway runtime best practices">
|
||||
Doctor warns when the gateway service runs on Bun or a version-managed Node path (`nvm`, `fnm`, `volta`, `asdf`, etc.). WhatsApp + Telegram channels require Node, and version-manager paths can break after upgrades because the service does not load your shell init. Doctor offers to migrate to a system Node install when available (Homebrew/apt/choco).
|
||||
|
||||
Newly installed or repaired services keep explicit environment roots (`NVM_DIR`, `FNM_DIR`, `VOLTA_HOME`, `ASDF_DATA_DIR`, `BUN_INSTALL`, `PNPM_HOME`) and stable user-bin directories, but guessed version-manager fallback directories are only written to the service PATH when those directories exist on disk. This keeps the generated supervisor PATH aligned with the same minimal-PATH audit doctor runs later.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="18. Config write + wizard metadata">
|
||||
Doctor persists any config changes and stamps wizard metadata to record the doctor run.
|
||||
|
||||
@@ -13,10 +13,14 @@ import {
|
||||
} from "./service-env.js";
|
||||
|
||||
describe("getMinimalServicePathParts - Linux user directories", () => {
|
||||
const allExist = (): boolean => true;
|
||||
const noneExist = (): boolean => false;
|
||||
|
||||
it("includes user bin directories when HOME is set on Linux", () => {
|
||||
const result = getMinimalServicePathParts({
|
||||
platform: "linux",
|
||||
home: "/home/testuser",
|
||||
existsSync: allExist,
|
||||
});
|
||||
|
||||
// Should include all common user bin directories
|
||||
@@ -53,6 +57,7 @@ describe("getMinimalServicePathParts - Linux user directories", () => {
|
||||
const result = getMinimalServicePathParts({
|
||||
platform: "linux",
|
||||
home: "/home/testuser",
|
||||
existsSync: allExist,
|
||||
});
|
||||
|
||||
const userDirIndex = result.indexOf("/home/testuser/.local/bin");
|
||||
@@ -68,6 +73,7 @@ describe("getMinimalServicePathParts - Linux user directories", () => {
|
||||
platform: "linux",
|
||||
home: "/home/testuser",
|
||||
extraDirs: ["/custom/bin"],
|
||||
existsSync: allExist,
|
||||
});
|
||||
|
||||
const extraDirIndex = result.indexOf("/custom/bin");
|
||||
@@ -91,6 +97,7 @@ describe("getMinimalServicePathParts - Linux user directories", () => {
|
||||
NVM_DIR: "/opt/nvm",
|
||||
FNM_DIR: "/opt/fnm",
|
||||
},
|
||||
existsSync: allExist,
|
||||
});
|
||||
|
||||
expect(result).toContain("/opt/pnpm");
|
||||
@@ -107,6 +114,7 @@ describe("getMinimalServicePathParts - Linux user directories", () => {
|
||||
const result = getMinimalServicePathParts({
|
||||
platform: "darwin",
|
||||
home: "/Users/testuser",
|
||||
existsSync: allExist,
|
||||
});
|
||||
|
||||
// Should include common user bin directories
|
||||
@@ -138,6 +146,7 @@ describe("getMinimalServicePathParts - Linux user directories", () => {
|
||||
NVM_DIR: "/Users/testuser/.nvm",
|
||||
PNPM_HOME: "/Users/testuser/Library/pnpm",
|
||||
},
|
||||
existsSync: allExist,
|
||||
});
|
||||
|
||||
// fnm uses aliases/default/bin (not current)
|
||||
@@ -152,6 +161,7 @@ describe("getMinimalServicePathParts - Linux user directories", () => {
|
||||
const result = getMinimalServicePathParts({
|
||||
platform: "darwin",
|
||||
home: "/Users/testuser",
|
||||
existsSync: allExist,
|
||||
});
|
||||
|
||||
// fnm on macOS defaults to ~/Library/Application Support/fnm
|
||||
@@ -169,11 +179,97 @@ describe("getMinimalServicePathParts - Linux user directories", () => {
|
||||
const result = getMinimalServicePathParts({
|
||||
platform: "win32",
|
||||
home: "C:\\Users\\testuser",
|
||||
existsSync: allExist,
|
||||
});
|
||||
|
||||
// Windows returns empty array (uses existing PATH)
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("omits hard-coded version-manager fallbacks on Linux when missing", () => {
|
||||
const result = getMinimalServicePathParts({
|
||||
platform: "linux",
|
||||
home: "/home/testuser",
|
||||
existsSync: noneExist,
|
||||
});
|
||||
|
||||
expect(result).toContain("/home/testuser/.local/bin");
|
||||
expect(result).toContain("/home/testuser/.npm-global/bin");
|
||||
expect(result).toContain("/home/testuser/bin");
|
||||
expect(result).toContain("/home/testuser/.nix-profile/bin");
|
||||
expect(result).not.toContain("/home/testuser/.volta/bin");
|
||||
expect(result).not.toContain("/home/testuser/.asdf/shims");
|
||||
expect(result).not.toContain("/home/testuser/.bun/bin");
|
||||
expect(result).not.toContain("/home/testuser/.nvm/current/bin");
|
||||
expect(result).not.toContain("/home/testuser/.local/share/fnm/aliases/default/bin");
|
||||
expect(result).not.toContain("/home/testuser/.local/share/fnm/current/bin");
|
||||
expect(result).not.toContain("/home/testuser/.fnm/aliases/default/bin");
|
||||
expect(result).not.toContain("/home/testuser/.fnm/current/bin");
|
||||
expect(result).not.toContain("/home/testuser/.local/share/pnpm");
|
||||
});
|
||||
|
||||
it("omits hard-coded version-manager fallbacks on macOS when missing", () => {
|
||||
const result = getMinimalServicePathParts({
|
||||
platform: "darwin",
|
||||
home: "/Users/testuser",
|
||||
existsSync: noneExist,
|
||||
});
|
||||
|
||||
expect(result).toContain("/Users/testuser/.local/bin");
|
||||
expect(result).toContain("/Users/testuser/.npm-global/bin");
|
||||
expect(result).toContain("/Users/testuser/bin");
|
||||
expect(result).toContain("/Users/testuser/.nix-profile/bin");
|
||||
expect(result).not.toContain("/Users/testuser/.volta/bin");
|
||||
expect(result).not.toContain("/Users/testuser/.asdf/shims");
|
||||
expect(result).not.toContain("/Users/testuser/.bun/bin");
|
||||
expect(result).not.toContain(
|
||||
"/Users/testuser/Library/Application Support/fnm/aliases/default/bin",
|
||||
);
|
||||
expect(result).not.toContain("/Users/testuser/.fnm/aliases/default/bin");
|
||||
expect(result).not.toContain("/Users/testuser/Library/pnpm");
|
||||
expect(result).not.toContain("/Users/testuser/.local/share/pnpm");
|
||||
});
|
||||
|
||||
it("keeps env-configured roots when fallback directories are missing", () => {
|
||||
const result = getMinimalServicePathPartsFromEnv({
|
||||
platform: "linux",
|
||||
env: {
|
||||
HOME: "/home/testuser",
|
||||
PNPM_HOME: "/opt/pnpm",
|
||||
VOLTA_HOME: "/opt/volta",
|
||||
BUN_INSTALL: "/opt/bun",
|
||||
ASDF_DATA_DIR: "/opt/asdf",
|
||||
NVM_DIR: "/opt/nvm",
|
||||
FNM_DIR: "/opt/fnm",
|
||||
},
|
||||
existsSync: noneExist,
|
||||
});
|
||||
|
||||
expect(result).toContain("/opt/pnpm");
|
||||
expect(result).toContain("/opt/volta/bin");
|
||||
expect(result).toContain("/opt/bun/bin");
|
||||
expect(result).toContain("/opt/asdf/shims");
|
||||
expect(result).toContain("/opt/nvm/current/bin");
|
||||
expect(result).toContain("/opt/fnm/aliases/default/bin");
|
||||
expect(result).toContain("/opt/fnm/current/bin");
|
||||
});
|
||||
|
||||
it("emits only existing hard-coded version-manager fallbacks", () => {
|
||||
const exists = (candidate: string) =>
|
||||
candidate === "/home/testuser/.volta/bin" ||
|
||||
candidate === "/home/testuser/.local/share/fnm/aliases/default/bin";
|
||||
const result = getMinimalServicePathParts({
|
||||
platform: "linux",
|
||||
home: "/home/testuser",
|
||||
existsSync: exists,
|
||||
});
|
||||
|
||||
expect(result).toContain("/home/testuser/.volta/bin");
|
||||
expect(result).toContain("/home/testuser/.local/share/fnm/aliases/default/bin");
|
||||
expect(result).not.toContain("/home/testuser/.bun/bin");
|
||||
expect(result).not.toContain("/home/testuser/.asdf/shims");
|
||||
expect(result).not.toContain("/home/testuser/.fnm/aliases/default/bin");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getMinimalServicePathParts - Nix Home Manager", () => {
|
||||
@@ -181,6 +277,7 @@ describe("getMinimalServicePathParts - Nix Home Manager", () => {
|
||||
const result = getMinimalServicePathParts({
|
||||
platform: "linux",
|
||||
home: "/home/testuser",
|
||||
existsSync: () => true,
|
||||
});
|
||||
|
||||
expect(result).toContain("/home/testuser/.nix-profile/bin");
|
||||
@@ -190,6 +287,7 @@ describe("getMinimalServicePathParts - Nix Home Manager", () => {
|
||||
const result = getMinimalServicePathParts({
|
||||
platform: "darwin",
|
||||
home: "/Users/testuser",
|
||||
existsSync: () => true,
|
||||
});
|
||||
|
||||
expect(result).toContain("/Users/testuser/.nix-profile/bin");
|
||||
@@ -202,6 +300,7 @@ describe("getMinimalServicePathParts - Nix Home Manager", () => {
|
||||
HOME: "/home/testuser",
|
||||
NIX_PROFILES: "/nix/var/nix/profiles/default /home/testuser/.nix-profile",
|
||||
},
|
||||
existsSync: () => true,
|
||||
});
|
||||
|
||||
const userIdx = result.indexOf("/home/testuser/.nix-profile/bin");
|
||||
@@ -218,6 +317,7 @@ describe("getMinimalServicePathParts - Nix Home Manager", () => {
|
||||
HOME: "/Users/testuser",
|
||||
NIX_PROFILES: "/nix/var/nix/profiles/default /Users/testuser/.nix-profile",
|
||||
},
|
||||
existsSync: () => true,
|
||||
});
|
||||
|
||||
const userIdx = result.indexOf("/Users/testuser/.nix-profile/bin");
|
||||
@@ -234,6 +334,7 @@ describe("getMinimalServicePathParts - Nix Home Manager", () => {
|
||||
HOME: "/home/testuser",
|
||||
NIX_PROFILES: "/nix/var/nix/profiles/per-user/testuser/profile",
|
||||
},
|
||||
existsSync: () => true,
|
||||
});
|
||||
|
||||
expect(result).toContain("/nix/var/nix/profiles/per-user/testuser/profile/bin");
|
||||
@@ -246,6 +347,7 @@ describe("getMinimalServicePathParts - Nix Home Manager", () => {
|
||||
HOME: "/Users/testuser",
|
||||
NIX_PROFILES: "/nix/var/nix/profiles/per-user/testuser/profile",
|
||||
},
|
||||
existsSync: () => true,
|
||||
});
|
||||
|
||||
expect(result).toContain("/nix/var/nix/profiles/per-user/testuser/profile/bin");
|
||||
@@ -259,6 +361,7 @@ describe("getMinimalServicePathParts - Nix Home Manager", () => {
|
||||
NIX_PROFILES:
|
||||
"/nix/var/nix/profiles/default /nix/var/nix/profiles/per-user/testuser/custom /home/testuser/.nix-profile",
|
||||
},
|
||||
existsSync: () => true,
|
||||
});
|
||||
|
||||
const userIdx = result.indexOf("/home/testuser/.nix-profile/bin");
|
||||
@@ -299,6 +402,7 @@ describe("buildMinimalServicePath", () => {
|
||||
const result = buildMinimalServicePath({
|
||||
platform: "linux",
|
||||
env: { HOME: "/home/alice" },
|
||||
existsSync: () => true,
|
||||
});
|
||||
const parts = splitPath(result, "linux");
|
||||
|
||||
@@ -332,6 +436,7 @@ describe("buildMinimalServicePath", () => {
|
||||
const result = buildMinimalServicePath({
|
||||
platform: "linux",
|
||||
env: { HOME: "/home/bob" },
|
||||
existsSync: () => true,
|
||||
});
|
||||
const parts = splitPath(result, "linux");
|
||||
|
||||
@@ -366,6 +471,7 @@ describe("buildMinimalServicePath", () => {
|
||||
platform: "linux",
|
||||
extraDirs: ["/home/alice/.nvm/versions/node/v22.22.0/bin"],
|
||||
env: { HOME: "/home/alice" },
|
||||
existsSync: () => true,
|
||||
});
|
||||
const parts = splitPath(result, "linux");
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import {
|
||||
@@ -29,6 +30,7 @@ export type MinimalServicePathOptions = {
|
||||
extraDirs?: string[];
|
||||
home?: string;
|
||||
env?: Record<string, string | undefined>;
|
||||
existsSync?: (candidate: string) => boolean;
|
||||
};
|
||||
|
||||
type BuildServicePathOptions = MinimalServicePathOptions & {
|
||||
@@ -68,13 +70,27 @@ function appendSubdir(base: string | undefined, subdir: string): string | undefi
|
||||
return base.endsWith(`/${subdir}`) ? base : path.posix.join(base, subdir);
|
||||
}
|
||||
|
||||
function addCommonUserBinDirs(dirs: string[], home: string): void {
|
||||
function addExistingDir(
|
||||
dirs: string[],
|
||||
candidate: string,
|
||||
existsSync: (candidate: string) => boolean,
|
||||
): void {
|
||||
if (existsSync(candidate)) {
|
||||
dirs.push(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
function addCommonUserBinDirs(
|
||||
dirs: string[],
|
||||
home: string,
|
||||
existsSync: (candidate: string) => boolean,
|
||||
): void {
|
||||
dirs.push(`${home}/.local/bin`);
|
||||
dirs.push(`${home}/.npm-global/bin`);
|
||||
dirs.push(`${home}/bin`);
|
||||
dirs.push(`${home}/.volta/bin`);
|
||||
dirs.push(`${home}/.asdf/shims`);
|
||||
dirs.push(`${home}/.bun/bin`);
|
||||
addExistingDir(dirs, `${home}/.volta/bin`, existsSync);
|
||||
addExistingDir(dirs, `${home}/.asdf/shims`, existsSync);
|
||||
addExistingDir(dirs, `${home}/.bun/bin`, existsSync);
|
||||
}
|
||||
|
||||
function addCommonEnvConfiguredBinDirs(
|
||||
@@ -126,6 +142,7 @@ function resolveSystemPathDirs(platform: NodeJS.Platform): string[] {
|
||||
export function resolveDarwinUserBinDirs(
|
||||
home: string | undefined,
|
||||
env?: Record<string, string | undefined>,
|
||||
existsSync: (candidate: string) => boolean = fs.existsSync,
|
||||
): string[] {
|
||||
if (!home) {
|
||||
return [];
|
||||
@@ -145,7 +162,7 @@ export function resolveDarwinUserBinDirs(
|
||||
// pnpm: binary is directly in PNPM_HOME (not in bin subdirectory)
|
||||
|
||||
// Common user bin directories
|
||||
addCommonUserBinDirs(dirs, home);
|
||||
addCommonUserBinDirs(dirs, home, existsSync);
|
||||
|
||||
// Nix Home Manager (cross-platform)
|
||||
addNixProfileBinDirs(dirs, home, env);
|
||||
@@ -153,11 +170,11 @@ export function resolveDarwinUserBinDirs(
|
||||
// Node version managers - macOS specific paths
|
||||
// nvm: no stable default path, depends on user's shell configuration
|
||||
// fnm: macOS default is ~/Library/Application Support/fnm, not ~/.fnm
|
||||
dirs.push(`${home}/Library/Application Support/fnm/aliases/default/bin`); // fnm default
|
||||
dirs.push(`${home}/.fnm/aliases/default/bin`); // fnm if customized to ~/.fnm
|
||||
addExistingDir(dirs, `${home}/Library/Application Support/fnm/aliases/default/bin`, existsSync); // fnm default
|
||||
addExistingDir(dirs, `${home}/.fnm/aliases/default/bin`, existsSync); // fnm if customized to ~/.fnm
|
||||
// pnpm: macOS default is ~/Library/pnpm, not ~/.local/share/pnpm
|
||||
dirs.push(`${home}/Library/pnpm`); // pnpm default
|
||||
dirs.push(`${home}/.local/share/pnpm`); // pnpm XDG fallback
|
||||
addExistingDir(dirs, `${home}/Library/pnpm`, existsSync); // pnpm default
|
||||
addExistingDir(dirs, `${home}/.local/share/pnpm`, existsSync); // pnpm XDG fallback
|
||||
|
||||
return dirs;
|
||||
}
|
||||
@@ -169,6 +186,7 @@ export function resolveDarwinUserBinDirs(
|
||||
export function resolveLinuxUserBinDirs(
|
||||
home: string | undefined,
|
||||
env?: Record<string, string | undefined>,
|
||||
existsSync: (candidate: string) => boolean = fs.existsSync,
|
||||
): string[] {
|
||||
if (!home) {
|
||||
return [];
|
||||
@@ -183,18 +201,18 @@ export function resolveLinuxUserBinDirs(
|
||||
addNonEmptyDir(dirs, appendSubdir(env?.FNM_DIR, "current/bin"));
|
||||
|
||||
// Common user bin directories
|
||||
addCommonUserBinDirs(dirs, home);
|
||||
addCommonUserBinDirs(dirs, home, existsSync);
|
||||
|
||||
// Nix Home Manager (cross-platform)
|
||||
addNixProfileBinDirs(dirs, home, env);
|
||||
|
||||
// Node version managers
|
||||
dirs.push(`${home}/.nvm/current/bin`); // nvm with current symlink
|
||||
dirs.push(`${home}/.local/share/fnm/aliases/default/bin`); // fnm default
|
||||
dirs.push(`${home}/.local/share/fnm/current/bin`); // fnm legacy current symlink
|
||||
dirs.push(`${home}/.fnm/aliases/default/bin`); // fnm if customized to ~/.fnm
|
||||
dirs.push(`${home}/.fnm/current/bin`); // fnm legacy current symlink
|
||||
dirs.push(`${home}/.local/share/pnpm`); // pnpm global bin
|
||||
addExistingDir(dirs, `${home}/.nvm/current/bin`, existsSync); // nvm with current symlink
|
||||
addExistingDir(dirs, `${home}/.local/share/fnm/aliases/default/bin`, existsSync); // fnm default
|
||||
addExistingDir(dirs, `${home}/.local/share/fnm/current/bin`, existsSync); // fnm legacy current symlink
|
||||
addExistingDir(dirs, `${home}/.fnm/aliases/default/bin`, existsSync); // fnm if customized to ~/.fnm
|
||||
addExistingDir(dirs, `${home}/.fnm/current/bin`, existsSync); // fnm legacy current symlink
|
||||
addExistingDir(dirs, `${home}/.local/share/pnpm`, existsSync); // pnpm global bin
|
||||
|
||||
return dirs;
|
||||
}
|
||||
@@ -210,11 +228,12 @@ export function getMinimalServicePathParts(options: MinimalServicePathOptions =
|
||||
const systemDirs = resolveSystemPathDirs(platform);
|
||||
|
||||
// Add user bin directories for version managers (npm global, nvm, fnm, volta, etc.)
|
||||
const existsSync = options.existsSync ?? fs.existsSync;
|
||||
const userDirs =
|
||||
platform === "linux"
|
||||
? resolveLinuxUserBinDirs(options.home, options.env)
|
||||
? resolveLinuxUserBinDirs(options.home, options.env, existsSync)
|
||||
: platform === "darwin"
|
||||
? resolveDarwinUserBinDirs(options.home, options.env)
|
||||
? resolveDarwinUserBinDirs(options.home, options.env, existsSync)
|
||||
: [];
|
||||
|
||||
const add = (dir: string) => {
|
||||
|
||||
Reference in New Issue
Block a user