diff --git a/CHANGELOG.md b/CHANGELOG.md index 05b29a0f909..289674db4f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ Docs: https://docs.openclaw.ai - Auto-reply/docking: require `/dock-*` route switches to start from direct chats, so group or channel participants cannot reroute a shared session's future replies into a linked DM. Thanks @vincentkoc. - Discord: keep text-DM main-session route updates pinned to the configured DM owner, matching component interactions so another direct-message sender cannot redirect future main-session replies. Thanks @vincentkoc. - Mattermost/Matrix: keep direct-message main-session route updates pinned to the configured DM owner so paired or temporarily allowed senders cannot redirect future shared-session replies. Thanks @vincentkoc. +- Updates: run package post-install doctor repair with the managed Gateway service profile and state paths when a daemon is installed, so shell/profile mismatches no longer repair the caller state while the restarted Gateway keeps stale config. Thanks @vincentkoc. - CLI/update: verify managed gateway restarts against the installed service port instead of the caller shell port, so package updates do not report a healthy daemon as failed when profiles use different gateway ports. Thanks @vincentkoc. - Gateway/agent: reject strict `openclaw agent --deliver` requests with missing delivery targets before starting the agent run, so users do not wait for a completed turn that cannot send anywhere. Thanks @vincentkoc. - Setup/import: honor non-interactive `--import-from` onboarding flags by running the migration import path instead of silently completing normal setup without importing anything. Thanks @vincentkoc. diff --git a/src/cli/update-cli/update-command.test.ts b/src/cli/update-cli/update-command.test.ts index 3a087c58b9c..f066b77c5d1 100644 --- a/src/cli/update-cli/update-command.test.ts +++ b/src/cli/update-cli/update-command.test.ts @@ -5,6 +5,7 @@ import { resolveGatewayInstallEntrypoint, } from "../../daemon/gateway-entrypoint.js"; import { + resolvePostInstallDoctorEnv, shouldPrepareUpdatedInstallRestart, resolveUpdatedGatewayRestartPort, shouldUseLegacyProcessRestartAfterUpdate, @@ -106,6 +107,48 @@ describe("resolveUpdatedGatewayRestartPort", () => { }); }); +describe("resolvePostInstallDoctorEnv", () => { + it("uses the managed service profile paths for post-install doctor", () => { + const env = resolvePostInstallDoctorEnv({ + invocationCwd: "/srv/openclaw", + baseEnv: { + PATH: "/bin", + OPENCLAW_STATE_DIR: "/wrong/state", + OPENCLAW_CONFIG_PATH: "/wrong/openclaw.json", + OPENCLAW_PROFILE: "wrong", + }, + serviceEnv: { + OPENCLAW_STATE_DIR: "daemon-state", + OPENCLAW_CONFIG_PATH: "daemon-state/openclaw.json", + OPENCLAW_PROFILE: "work", + }, + }); + + expect(env.PATH).toBe("/bin"); + expect(env.NODE_DISABLE_COMPILE_CACHE).toBe("1"); + expect(env.OPENCLAW_STATE_DIR).toBe(path.join("/srv/openclaw", "daemon-state")); + expect(env.OPENCLAW_CONFIG_PATH).toBe( + path.join("/srv/openclaw", "daemon-state", "openclaw.json"), + ); + expect(env.OPENCLAW_PROFILE).toBe("work"); + }); + + it("keeps the caller env when no managed service env is available", () => { + const env = resolvePostInstallDoctorEnv({ + baseEnv: { + PATH: "/bin", + OPENCLAW_STATE_DIR: "/caller/state", + OPENCLAW_PROFILE: "caller", + }, + }); + + expect(env.PATH).toBe("/bin"); + expect(env.NODE_DISABLE_COMPILE_CACHE).toBe("1"); + expect(env.OPENCLAW_STATE_DIR).toBe("/caller/state"); + expect(env.OPENCLAW_PROFILE).toBe("caller"); + }); +}); + describe("shouldUseLegacyProcessRestartAfterUpdate", () => { it("never restarts package updates through the pre-update process", () => { expect(shouldUseLegacyProcessRestartAfterUpdate({ updateMode: "npm" })).toBe(false); diff --git a/src/cli/update-cli/update-command.ts b/src/cli/update-cli/update-command.ts index 9ff87552bc3..ea74ef9d405 100644 --- a/src/cli/update-cli/update-command.ts +++ b/src/cli/update-cli/update-command.ts @@ -103,6 +103,10 @@ const SERVICE_REFRESH_PATH_ENV_KEYS = [ "OPENCLAW_STATE_DIR", "OPENCLAW_CONFIG_PATH", ] as const; +const POST_INSTALL_DOCTOR_SERVICE_ENV_KEYS = [ + ...SERVICE_REFRESH_PATH_ENV_KEYS, + "OPENCLAW_PROFILE", +] as const; const UPDATE_QUIPS = [ "Leveled up! New skills unlocked. You're welcome.", @@ -320,6 +324,26 @@ function resolveUpdatedInstallCommandEnv( return disableUpdatedPackageCompileCacheEnv(resolveServiceRefreshEnv(env, invocationCwd)); } +export function resolvePostInstallDoctorEnv(params?: { + baseEnv?: NodeJS.ProcessEnv; + serviceEnv?: NodeJS.ProcessEnv; + invocationCwd?: string; +}): NodeJS.ProcessEnv { + const resolvedEnv = disableUpdatedPackageCompileCacheEnv(params?.baseEnv ?? process.env); + if (!params?.serviceEnv) { + return resolvedEnv; + } + + const serviceEnv = resolveServiceRefreshEnv(params.serviceEnv, params.invocationCwd); + for (const key of POST_INSTALL_DOCTOR_SERVICE_ENV_KEYS) { + const value = serviceEnv[key]?.trim(); + if (value) { + resolvedEnv[key] = serviceEnv[key]; + } + } + return resolvedEnv; +} + export function resolveUpdatedGatewayRestartPort(params: { config?: OpenClawConfig; processEnv?: NodeJS.ProcessEnv; @@ -515,6 +539,8 @@ async function runPackageInstallUpdate(params: { startedAt: number; progress: ReturnType["progress"]; jsonMode: boolean; + managedServiceEnv?: NodeJS.ProcessEnv; + invocationCwd?: string; }): Promise { const manager = await resolveGlobalManager({ root: params.root, @@ -579,7 +605,10 @@ async function runPackageInstallUpdate(params: { name: `${CLI_NAME} doctor`, argv: [resolveNodeRunner(), entryPath, "doctor", "--non-interactive", "--fix"], env: { - ...disableUpdatedPackageCompileCacheEnv(process.env), + ...resolvePostInstallDoctorEnv({ + serviceEnv: params.managedServiceEnv, + invocationCwd: params.invocationCwd, + }), OPENCLAW_UPDATE_IN_PROGRESS: "1", [UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE_ENV]: "1", }, @@ -1619,6 +1648,8 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise { startedAt, progress, jsonMode: Boolean(opts.json), + managedServiceEnv: prePackageServiceStop?.serviceEnv, + invocationCwd, }) : await runGitUpdate({ root,