From 968f0c388f5dd154a0c54d7b1fbd4bbbfcd44aa9 Mon Sep 17 00:00:00 2001 From: i-trytoohard <193449657+i-trytoohard@users.noreply.github.com> Date: Thu, 21 May 2026 07:49:41 +0530 Subject: [PATCH] fix(cli): preserve update lifecycle config path --- .../cli/__tests__/commands/update.test.ts | 25 +++++++++++++++--- packages/cli/src/commands/update.ts | 26 +++++++++++++------ 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/packages/cli/__tests__/commands/update.test.ts b/packages/cli/__tests__/commands/update.test.ts index 47e408ad8..843bcc1bb 100644 --- a/packages/cli/__tests__/commands/update.test.ts +++ b/packages/cli/__tests__/commands/update.test.ts @@ -600,7 +600,10 @@ describe("update command", () => { expect(mockSpawn.mock.calls[0]).toEqual([ "ao", ["stop", "--yes"], - expect.objectContaining({ stdio: "inherit" }), + expect.objectContaining({ + stdio: "inherit", + env: expect.objectContaining({ AO_CONFIG_PATH: "/tmp/test-global-config.yaml" }), + }), ]); expect(mockSpawn.mock.calls[1][0]).toBe("pnpm"); expect(mockSpawn.mock.calls[1][1]).toEqual(["add", "-g", "@aoagents/ao@latest"]); @@ -609,7 +612,10 @@ describe("update command", () => { expect(mockSpawn.mock.calls[3]).toEqual([ "ao", ["start", "my-app", "--restore"], - expect.objectContaining({ stdio: "inherit" }), + expect.objectContaining({ + stdio: "inherit", + env: expect.objectContaining({ AO_CONFIG_PATH: "/tmp/test-global-config.yaml" }), + }), ]); const stopOrder = mockSpawn.mock.invocationCallOrder[0]; @@ -662,6 +668,11 @@ describe("update command", () => { expect(mockSpawn.mock.calls[0][0]).toBe("ao"); expect(mockSpawn.mock.calls[0][1]).toEqual(["stop", "--yes"]); + expect(mockSpawn.mock.calls[0][2]).toEqual( + expect.objectContaining({ + env: expect.objectContaining({ AO_CONFIG_PATH: "/tmp/test-global-config.yaml" }), + }), + ); expect(mockSpawn.mock.calls[1][0]).toBe("pnpm"); expect(mockSpawn.mock.calls[2][0]).toBe("ao"); expect(mockSpawn.mock.calls[2][1]).toEqual(["--version"]); @@ -683,7 +694,10 @@ describe("update command", () => { expect(mockSpawn.mock.calls.at(-1)).toEqual([ "ao", ["start", "my-app", "--no-restore"], - expect.objectContaining({ stdio: "inherit" }), + expect.objectContaining({ + stdio: "inherit", + env: expect.objectContaining({ AO_CONFIG_PATH: "/tmp/test-global-config.yaml" }), + }), ]); }); @@ -705,7 +719,10 @@ describe("update command", () => { expect(mockSpawn.mock.calls[0][0]).toBe("ao"); expect(mockSpawn.mock.calls[0][1]).toEqual(["stop", "--yes"]); expect(mockSpawn.mock.calls.some((call) => call[0] === "pnpm")).toBe(false); - const stderr = vi.mocked(console.error).mock.calls.map((c) => String(c[0])).join("\n"); + const stderr = vi + .mocked(console.error) + .mock.calls.map((c) => String(c[0])) + .join("\n"); expect(stderr).toContain("AO still appears to be running after `ao stop --yes`"); }); diff --git a/packages/cli/src/commands/update.ts b/packages/cli/src/commands/update.ts index 16f13ede0..b181d4370 100644 --- a/packages/cli/src/commands/update.ts +++ b/packages/cli/src/commands/update.ts @@ -147,12 +147,14 @@ async function handleCheck(): Promise { */ interface UpdateLifecyclePlan { runningBeforeUpdate: boolean; + configPath?: string; primaryProjectId?: string; activeSessions: Session[]; } async function getUpdateLifecyclePlan(): Promise { let sessions: Session[]; + let configPath: string | undefined; let primaryProjectId: string | undefined; let runningBeforeUpdate = false; try { @@ -168,6 +170,7 @@ async function getUpdateLifecyclePlan(): Promise { const running = await getRunning(); if (running && running.projects.length > 0) { runningBeforeUpdate = true; + configPath = running.configPath; primaryProjectId = running.projects[0]; // running.configPath could be local-wrapped (a project's // agent-orchestrator.yaml) OR the canonical global path. loadConfig @@ -184,15 +187,16 @@ async function getUpdateLifecyclePlan(): Promise { // sessions to `killed`, so terminal statuses don't block the update. const globalPath = getGlobalConfigPath(); if (!existsSync(globalPath)) { - return { runningBeforeUpdate, primaryProjectId, activeSessions: [] }; + return { runningBeforeUpdate, configPath, primaryProjectId, activeSessions: [] }; } const globalConfig = loadGlobalConfig(globalPath); if (!globalConfig || Object.keys(globalConfig.projects).length === 0) { - return { runningBeforeUpdate, primaryProjectId, activeSessions: [] }; + return { runningBeforeUpdate, configPath, primaryProjectId, activeSessions: [] }; } if (!isCanonicalGlobalConfigPath(globalPath)) { - return { runningBeforeUpdate, primaryProjectId, activeSessions: [] }; + return { runningBeforeUpdate, configPath, primaryProjectId, activeSessions: [] }; } + configPath = globalPath; const config = loadConfig(globalPath); primaryProjectId = Object.keys(config.projects)[0]; const sm = await getSessionManager(config); @@ -204,11 +208,11 @@ async function getUpdateLifecyclePlan(): Promise { console.error( chalk.yellow("⚠ Could not check for active sessions before updating. Proceeding anyway."), ); - return { runningBeforeUpdate, primaryProjectId, activeSessions: [] }; + return { runningBeforeUpdate, configPath, primaryProjectId, activeSessions: [] }; } const active = sessions.filter((s) => ACTIVE_SESSION_STATUSES.has(s.status)); - return { runningBeforeUpdate, primaryProjectId, activeSessions: active }; + return { runningBeforeUpdate, configPath, primaryProjectId, activeSessions: active }; } async function pauseAoForUpdate(plan: UpdateLifecyclePlan): Promise { @@ -232,7 +236,9 @@ async function pauseAoForUpdate(plan: UpdateLifecyclePlan): Promise { console.log(chalk.dim("\nAO is running; it will be restarted after the update.")); } - const stopExit = await runAoLifecycleCommand(["stop", "--yes"]); + const stopExit = await runAoLifecycleCommand(["stop", "--yes"], { + configPath: plan.configPath, + }); if (stopExit !== 0) { recordActivityEvent({ source: "cli", @@ -288,7 +294,7 @@ async function restartAoAfterUpdate( args.push(opts.restore ? "--restore" : "--no-restore"); console.log(chalk.dim(`\nRestarting AO: ao ${args.join(" ")}`)); - const exitCode = await runAoLifecycleCommand(args); + const exitCode = await runAoLifecycleCommand(args, { configPath: plan.configPath }); if (exitCode !== 0) { recordActivityEvent({ source: "cli", @@ -307,12 +313,16 @@ async function restartAoAfterUpdate( } } -function runAoLifecycleCommand(args: string[]): Promise { +function runAoLifecycleCommand( + args: string[], + opts: { configPath?: string } = {}, +): Promise { return new Promise((resolveExit) => { const child = spawn("ao", args, { stdio: "inherit", shell: isWindows(), windowsHide: true, + env: opts.configPath ? { ...process.env, AO_CONFIG_PATH: opts.configPath } : process.env, }); child.on("error", (error) => { console.error(chalk.yellow(`Could not run ao ${args.join(" ")}: ${error.message}`));