fix: harden update dev switch and refresh changelog

This commit is contained in:
Peter Steinberger
2026-03-23 10:53:50 -07:00
parent 848414d7f2
commit ffb287e1de
7 changed files with 216 additions and 33 deletions

View File

@@ -355,4 +355,34 @@ describe("runDaemonInstall", () => {
}),
);
});
it("reuses env-backed service secrets during forced reinstall when the current shell is missing them", async () => {
service.isLoaded.mockResolvedValue(true);
service.readCommand.mockResolvedValue({
programArguments: ["openclaw", "gateway", "run"],
environment: {
OPENAI_API_KEY: "service-openai-key",
},
} as never);
const previous = process.env.OPENAI_API_KEY;
delete process.env.OPENAI_API_KEY;
try {
await runDaemonInstall({ json: true, force: true });
expect(buildGatewayInstallPlanMock).toHaveBeenCalledWith(
expect.objectContaining({
env: expect.objectContaining({
OPENAI_API_KEY: "service-openai-key",
}),
}),
);
expect(installDaemonServiceAndEmitMock).toHaveBeenCalledTimes(1);
} finally {
if (previous === undefined) {
delete process.env.OPENAI_API_KEY;
} else {
process.env.OPENAI_API_KEY = previous;
}
}
});
});

View File

@@ -18,6 +18,19 @@ import {
} from "./shared.js";
import type { DaemonInstallOptions } from "./types.js";
function mergeInstallInvocationEnv(params: {
env: NodeJS.ProcessEnv;
existingServiceEnv?: Record<string, string>;
}): NodeJS.ProcessEnv {
if (!params.existingServiceEnv || Object.keys(params.existingServiceEnv).length === 0) {
return params.env;
}
return {
...params.existingServiceEnv,
...params.env,
};
}
export async function runDaemonInstall(opts: DaemonInstallOptions) {
const { json, stdout, warnings, emit, fail } = createDaemonInstallActionContext(opts.json);
if (failIfNixDaemonInstallMode(fail)) {
@@ -43,6 +56,7 @@ export async function runDaemonInstall(opts: DaemonInstallOptions) {
const service = resolveGatewayService();
let loaded = false;
let existingServiceEnv: Record<string, string> | undefined;
try {
loaded = await service.isLoaded({ env: process.env });
} catch (err) {
@@ -53,6 +67,13 @@ export async function runDaemonInstall(opts: DaemonInstallOptions) {
return;
}
}
if (loaded) {
existingServiceEnv = (await service.readCommand(process.env).catch(() => null))?.environment;
}
const installEnv = mergeInstallInvocationEnv({
env: process.env,
existingServiceEnv,
});
if (loaded) {
if (!opts.force) {
if (await gatewayServiceNeedsAutoNodeExtraCaCertsRefresh({ service, env: process.env })) {
@@ -82,7 +103,7 @@ export async function runDaemonInstall(opts: DaemonInstallOptions) {
const tokenResolution = await resolveGatewayInstallToken({
config: cfg,
env: process.env,
env: installEnv,
explicitToken: opts.token,
autoGenerateWhenMissing: true,
persistGeneratedToken: true,
@@ -100,7 +121,7 @@ export async function runDaemonInstall(opts: DaemonInstallOptions) {
}
const { programArguments, workingDirectory, environment } = await buildGatewayInstallPlan({
env: process.env,
env: installEnv,
port,
runtime: runtimeRaw,
warn: (message) => {
@@ -121,7 +142,7 @@ export async function runDaemonInstall(opts: DaemonInstallOptions) {
fail,
install: async () => {
await service.install({
env: process.env,
env: installEnv,
stdout,
programArguments,
workingDirectory,