@@ -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");
2526
2627const params = JSON.parse(fs.readFileSync(process.argv[2], "utf-8"));
@@ -62,6 +63,23 @@ function cleanupSensitiveFiles() {
6263 }
6364}
6465
66+ 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
273301 return next ;
274302}
275303
304+ 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 : {
277323 root : string ;
278324 timeoutMs ?: number ;
@@ -295,14 +341,15 @@ export async function startManagedServiceUpdateHandoff(params: {
295341 argv1 : params . argv1 ?? process . argv [ 1 ] ,
296342 } ) ;
297343 const commandLabel = formatManagedServiceUpdateCommand ( params . timeoutMs ) ;
344+ const handoffCwd = await resolveManagedServiceHandoffCwd ( params . root ) ;
298345 const metaFile : ControlPlaneUpdateSentinelMetaFile = {
299346 version : 1 ,
300347 meta : params . meta ,
301348 } ;
302349 const helperParams = {
303350 parentPid : params . parentPid ?? process . pid ,
304351 parentExitTimeoutMs : Math . max ( 0 , params . restartDelayMs ?? 0 ) + PARENT_EXIT_GRACE_MS ,
305- cwd : params . root ,
352+ cwd : handoffCwd ,
306353 commandArgv,
307354 commandLabel,
308355 handoffId : params . handoffId ,
@@ -322,7 +369,7 @@ export async function startManagedServiceUpdateHandoff(params: {
322369 OPENCLAW_UPDATE_RUN_HANDOFF : "1" ,
323370 } ;
324371 const child = spawn ( params . execPath ?? process . execPath , [ scriptPath , paramsPath ] , {
325- cwd : params . root ,
372+ cwd : handoffCwd ,
326373 env,
327374 detached : true ,
328375 stdio : "ignore" ,
0 commit comments