



















@@ -21,6 +21,7 @@ const SERVICE_IDENTITY_ENV_VARS = new Set<string>([
2121const HANDOFF_SCRIPT = String.raw`
2222const { spawn } = require("node:child_process");
2323const fs = require("node:fs");
24+const os = require("node:os");
2425const path = require("node:path");
25262627const params = JSON.parse(fs.readFileSync(process.argv[2], "utf-8"));
@@ -62,6 +63,23 @@ function cleanupSensitiveFiles() {
6263 }
6364}
646566+function resolveExistingDirectory(candidates) {
67+ for (const candidate of candidates) {
68+ if (!candidate || typeof candidate !== "string") {
69+ continue;
70+ }
71+ try {
72+ const stat = fs.statSync(candidate);
73+ if (stat.isDirectory()) {
74+ return candidate;
75+ }
76+ } catch {
77+ // Try the next candidate.
78+ }
79+ }
80+ return undefined;
81+}
82+6583function readJsonFile(filePath) {
6684 try {
6785 return JSON.parse(fs.readFileSync(filePath, "utf-8"));
@@ -170,8 +188,18 @@ function markUpdateSentinelFailureIfPending(reason) {
170188 let outputFd;
171189 try {
172190 outputFd = fs.openSync(params.logPath, "a", 0o600);
191+ const commandCwd =
192+ resolveExistingDirectory([
193+ params.cwd,
194+ os.homedir(),
195+ os.tmpdir(),
196+ path.parse(process.execPath).root,
197+ ]) || params.cwd;
198+ if (commandCwd !== params.cwd) {
199+ appendLog("managed update command cwd fallback: " + params.cwd + " -> " + commandCwd);
200+ }
173201 const child = spawn(params.commandArgv[0], params.commandArgv.slice(1), {
174- cwd: params.cwd,
202+ cwd: commandCwd,
175203 env: process.env,
176204 detached: true,
177205 stdio: ["ignore", outputFd, outputFd],
@@ -273,6 +301,24 @@ export function stripSupervisorHintEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEn
273301return next;
274302}
275303304+async function resolveManagedServiceHandoffCwd(root: string): Promise<string> {
305+const candidates = [os.homedir(), os.tmpdir(), path.dirname(process.execPath), root];
306+for (const candidate of candidates) {
307+if (!candidate.trim()) {
308+continue;
309+}
310+try {
311+const stat = await fs.stat(candidate);
312+if (stat.isDirectory()) {
313+return candidate;
314+}
315+} catch {
316+// Try the next candidate.
317+}
318+}
319+return root;
320+}
321+276322export async function startManagedServiceUpdateHandoff(params: {
277323root: string;
278324timeoutMs?: number;
@@ -295,14 +341,15 @@ export async function startManagedServiceUpdateHandoff(params: {
295341argv1: params.argv1 ?? process.argv[1],
296342});
297343const commandLabel = formatManagedServiceUpdateCommand(params.timeoutMs);
344+const handoffCwd = await resolveManagedServiceHandoffCwd(params.root);
298345const metaFile: ControlPlaneUpdateSentinelMetaFile = {
299346version: 1,
300347meta: params.meta,
301348};
302349const helperParams = {
303350parentPid: params.parentPid ?? process.pid,
304351parentExitTimeoutMs: Math.max(0, params.restartDelayMs ?? 0) + PARENT_EXIT_GRACE_MS,
305-cwd: params.root,
352+cwd: handoffCwd,
306353 commandArgv,
307354 commandLabel,
308355handoffId: params.handoffId,
@@ -322,7 +369,7 @@ export async function startManagedServiceUpdateHandoff(params: {
322369OPENCLAW_UPDATE_RUN_HANDOFF: "1",
323370};
324371const child = spawn(params.execPath ?? process.execPath, [scriptPath, paramsPath], {
325-cwd: params.root,
372+cwd: handoffCwd,
326373 env,
327374detached: true,
328375stdio: "ignore",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。