mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-05 10:50:22 +00:00
fix(daemon): avoid freezing Windows PATH in task scripts (#39139, thanks @Narcooo)
Co-authored-by: majx_mac <mjxnarco@pku.edu.cn>
This commit is contained in:
@@ -281,6 +281,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Models/default alias refresh: bump `gpt` to `openai/gpt-5.4` and Gemini defaults to `gemini-3.1` preview aliases (including normalization/default wiring) to track current model IDs. (#38638) Thanks @ademczuk.
|
- Models/default alias refresh: bump `gpt` to `openai/gpt-5.4` and Gemini defaults to `gemini-3.1` preview aliases (including normalization/default wiring) to track current model IDs. (#38638) Thanks @ademczuk.
|
||||||
- Config/env substitution degraded mode: convert missing `${VAR}` resolution in config reads from hard-fail to warning-backed degraded behavior, while preventing unresolved placeholders from being accepted as gateway credentials. (#39050) Thanks @akz142857.
|
- Config/env substitution degraded mode: convert missing `${VAR}` resolution in config reads from hard-fail to warning-backed degraded behavior, while preventing unresolved placeholders from being accepted as gateway credentials. (#39050) Thanks @akz142857.
|
||||||
- Discord inbound listener non-blocking dispatch: make `MESSAGE_CREATE` listener handoff asynchronous (no per-listener queue blocking), so long runs no longer stall unrelated incoming events. (#39154) Thanks @yaseenkadlemakki.
|
- Discord inbound listener non-blocking dispatch: make `MESSAGE_CREATE` listener handoff asynchronous (no per-listener queue blocking), so long runs no longer stall unrelated incoming events. (#39154) Thanks @yaseenkadlemakki.
|
||||||
|
- Daemon/Windows PATH freeze fix: stop persisting install-time `PATH` snapshots into Scheduled Task scripts so runtime tool lookup follows current host PATH updates; also refresh local TUI history on silent local finals. (#39139) Thanks @Narcooo.
|
||||||
|
|
||||||
## 2026.3.2
|
## 2026.3.2
|
||||||
|
|
||||||
|
|||||||
@@ -133,4 +133,22 @@ describe("installScheduledTask", () => {
|
|||||||
).rejects.toThrow(/Task description cannot contain CR or LF/);
|
).rejects.toThrow(/Task description cannot contain CR or LF/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not persist a frozen PATH snapshot into the generated task script", async () => {
|
||||||
|
await withUserProfileDir(async (_tmpDir, env) => {
|
||||||
|
const { scriptPath } = await installScheduledTask({
|
||||||
|
env,
|
||||||
|
stdout: new PassThrough(),
|
||||||
|
programArguments: ["node", "gateway.js"],
|
||||||
|
environment: {
|
||||||
|
PATH: "C:\\Windows\\System32;C:\\Program Files\\Docker\\Docker\\resources\\bin",
|
||||||
|
OPENCLAW_GATEWAY_PORT: "18789",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const script = await fs.readFile(scriptPath, "utf8");
|
||||||
|
expect(script).not.toContain('set "PATH=');
|
||||||
|
expect(script).toContain('set "OPENCLAW_GATEWAY_PORT=18789"');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -209,6 +209,9 @@ function buildTaskScript({
|
|||||||
if (!value) {
|
if (!value) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (key.toUpperCase() === "PATH") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
lines.push(renderCmdSetAssignment(key, value));
|
lines.push(renderCmdSetAssignment(key, value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ describe("buildServiceEnvironment", () => {
|
|||||||
});
|
});
|
||||||
expect(env.HOME).toBe("/home/user");
|
expect(env.HOME).toBe("/home/user");
|
||||||
if (process.platform === "win32") {
|
if (process.platform === "win32") {
|
||||||
expect(env.PATH).toBe("");
|
expect(env).not.toHaveProperty("PATH");
|
||||||
} else {
|
} else {
|
||||||
expect(env.PATH).toContain("/usr/bin");
|
expect(env.PATH).toContain("/usr/bin");
|
||||||
}
|
}
|
||||||
@@ -331,6 +331,20 @@ describe("buildServiceEnvironment", () => {
|
|||||||
expect(env.http_proxy).toBe("http://proxy.local:7890");
|
expect(env.http_proxy).toBe("http://proxy.local:7890");
|
||||||
expect(env.all_proxy).toBe("socks5://proxy.local:1080");
|
expect(env.all_proxy).toBe("socks5://proxy.local:1080");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("omits PATH on Windows so Scheduled Tasks can inherit the current shell path", () => {
|
||||||
|
const env = buildServiceEnvironment({
|
||||||
|
env: {
|
||||||
|
HOME: "C:\\Users\\alice",
|
||||||
|
PATH: "C:\\Windows\\System32;C:\\Tools\\rg",
|
||||||
|
},
|
||||||
|
port: 18789,
|
||||||
|
platform: "win32",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(env).not.toHaveProperty("PATH");
|
||||||
|
expect(env.OPENCLAW_WINDOWS_TASK_NAME).toBe("OpenClaw Gateway");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("buildNodeServiceEnvironment", () => {
|
describe("buildNodeServiceEnvironment", () => {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ type SharedServiceEnvironmentFields = {
|
|||||||
stateDir: string | undefined;
|
stateDir: string | undefined;
|
||||||
configPath: string | undefined;
|
configPath: string | undefined;
|
||||||
tmpDir: string;
|
tmpDir: string;
|
||||||
minimalPath: string;
|
minimalPath: string | undefined;
|
||||||
proxyEnv: Record<string, string | undefined>;
|
proxyEnv: Record<string, string | undefined>;
|
||||||
nodeCaCerts: string | undefined;
|
nodeCaCerts: string | undefined;
|
||||||
nodeUseSystemCa: string | undefined;
|
nodeUseSystemCa: string | undefined;
|
||||||
@@ -297,16 +297,19 @@ function buildCommonServiceEnvironment(
|
|||||||
env: Record<string, string | undefined>,
|
env: Record<string, string | undefined>,
|
||||||
sharedEnv: SharedServiceEnvironmentFields,
|
sharedEnv: SharedServiceEnvironmentFields,
|
||||||
): Record<string, string | undefined> {
|
): Record<string, string | undefined> {
|
||||||
return {
|
const serviceEnv: Record<string, string | undefined> = {
|
||||||
HOME: env.HOME,
|
HOME: env.HOME,
|
||||||
TMPDIR: sharedEnv.tmpDir,
|
TMPDIR: sharedEnv.tmpDir,
|
||||||
PATH: sharedEnv.minimalPath,
|
|
||||||
...sharedEnv.proxyEnv,
|
...sharedEnv.proxyEnv,
|
||||||
NODE_EXTRA_CA_CERTS: sharedEnv.nodeCaCerts,
|
NODE_EXTRA_CA_CERTS: sharedEnv.nodeCaCerts,
|
||||||
NODE_USE_SYSTEM_CA: sharedEnv.nodeUseSystemCa,
|
NODE_USE_SYSTEM_CA: sharedEnv.nodeUseSystemCa,
|
||||||
OPENCLAW_STATE_DIR: sharedEnv.stateDir,
|
OPENCLAW_STATE_DIR: sharedEnv.stateDir,
|
||||||
OPENCLAW_CONFIG_PATH: sharedEnv.configPath,
|
OPENCLAW_CONFIG_PATH: sharedEnv.configPath,
|
||||||
};
|
};
|
||||||
|
if (sharedEnv.minimalPath) {
|
||||||
|
serviceEnv.PATH = sharedEnv.minimalPath;
|
||||||
|
}
|
||||||
|
return serviceEnv;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveSharedServiceEnvironmentFields(
|
function resolveSharedServiceEnvironmentFields(
|
||||||
@@ -328,7 +331,9 @@ function resolveSharedServiceEnvironmentFields(
|
|||||||
stateDir,
|
stateDir,
|
||||||
configPath,
|
configPath,
|
||||||
tmpDir,
|
tmpDir,
|
||||||
minimalPath: buildMinimalServicePath({ env }),
|
// On Windows, Scheduled Tasks should inherit the current task PATH instead of
|
||||||
|
// freezing the install-time snapshot into gateway.cmd/node-host.cmd.
|
||||||
|
minimalPath: platform === "win32" ? undefined : buildMinimalServicePath({ env, platform }),
|
||||||
proxyEnv,
|
proxyEnv,
|
||||||
nodeCaCerts,
|
nodeCaCerts,
|
||||||
nodeUseSystemCa,
|
nodeUseSystemCa,
|
||||||
|
|||||||
@@ -484,4 +484,20 @@ describe("tui-event-handlers: handleAgentEvent", () => {
|
|||||||
expect(chatLog.dropAssistant).toHaveBeenCalledWith("run-silent");
|
expect(chatLog.dropAssistant).toHaveBeenCalledWith("run-silent");
|
||||||
expect(chatLog.finalizeAssistant).not.toHaveBeenCalled();
|
expect(chatLog.finalizeAssistant).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("reloads history when a local run ends without a displayable final message", () => {
|
||||||
|
const { state, loadHistory, noteLocalRunId, handleChatEvent } = createHandlersHarness({
|
||||||
|
state: { activeChatRunId: "run-local-silent" },
|
||||||
|
});
|
||||||
|
|
||||||
|
noteLocalRunId("run-local-silent");
|
||||||
|
|
||||||
|
handleChatEvent({
|
||||||
|
runId: "run-local-silent",
|
||||||
|
sessionKey: state.currentSessionKey,
|
||||||
|
state: "final",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(loadHistory).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -136,10 +136,16 @@ export function createEventHandlers(context: EventHandlerContext) {
|
|||||||
return sessionRuns.has(activeRunId);
|
return sessionRuns.has(activeRunId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const maybeRefreshHistoryForRun = (runId: string) => {
|
const maybeRefreshHistoryForRun = (
|
||||||
if (isLocalRunId?.(runId)) {
|
runId: string,
|
||||||
|
opts?: { allowLocalWithoutDisplayableFinal?: boolean },
|
||||||
|
) => {
|
||||||
|
const isLocalRun = isLocalRunId?.(runId) ?? false;
|
||||||
|
if (isLocalRun) {
|
||||||
forgetLocalRunId?.(runId);
|
forgetLocalRunId?.(runId);
|
||||||
return;
|
if (!opts?.allowLocalWithoutDisplayableFinal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (hasConcurrentActiveRun(runId)) {
|
if (hasConcurrentActiveRun(runId)) {
|
||||||
return;
|
return;
|
||||||
@@ -202,7 +208,9 @@ export function createEventHandlers(context: EventHandlerContext) {
|
|||||||
if (evt.state === "final") {
|
if (evt.state === "final") {
|
||||||
const wasActiveRun = state.activeChatRunId === evt.runId;
|
const wasActiveRun = state.activeChatRunId === evt.runId;
|
||||||
if (!evt.message) {
|
if (!evt.message) {
|
||||||
maybeRefreshHistoryForRun(evt.runId);
|
maybeRefreshHistoryForRun(evt.runId, {
|
||||||
|
allowLocalWithoutDisplayableFinal: true,
|
||||||
|
});
|
||||||
chatLog.dropAssistant(evt.runId);
|
chatLog.dropAssistant(evt.runId);
|
||||||
finalizeRun({ runId: evt.runId, wasActiveRun, status: "idle" });
|
finalizeRun({ runId: evt.runId, wasActiveRun, status: "idle" });
|
||||||
tui.requestRender();
|
tui.requestRender();
|
||||||
|
|||||||
Reference in New Issue
Block a user