fix(update): forward finalize --channel only when configured

Codex follow-up: defaulting the channel to dev and passing --channel made
update finalize persist update.channel into openclaw.json (persistRequestedUpdateChannel
treats any --channel as an explicit request). Only forward --channel when the
caller has a configured channel so the finalizer never writes a channel the user
did not request; when omitted it converges on the stored/default channel and the
reconcile still resolves a host-compatible version. Keeps the per-step vs
whole-process timeout decoupling.
This commit is contained in:
masatohoshino
2026-06-18 18:46:18 +08:00
committed by Vincent Koc
parent 6214762461
commit 07c29c20d6
2 changed files with 12 additions and 13 deletions
+4 -6
View File
@@ -119,7 +119,7 @@ describe("runPostCoreFinalizeAfterGatewayUpdate", () => {
expect(env.OPENCLAW_GATEWAY_SERVICE_PID).toBeUndefined();
});
it("defaults to the git/dev channel and omits --timeout when not provided", async () => {
it("omits --channel (no config mutation) and --timeout when not provided", async () => {
const spawnFinalize = vi.fn<PostCoreFinalizeSpawner>(async () => ({ code: 0 }));
await runPostCoreFinalizeAfterGatewayUpdate({
result: gitOkResult(),
@@ -127,11 +127,9 @@ describe("runPostCoreFinalizeAfterGatewayUpdate", () => {
spawnFinalize,
});
const call = spawnFinalize.mock.calls[0][0];
// git/source updates default to the dev channel (matches runGatewayUpdate),
// not the package (stable) channel.
const channelIdx = call.argv.indexOf("--channel");
expect(channelIdx).toBeGreaterThan(-1);
expect(call.argv[channelIdx + 1]).toBe("dev");
// No `--channel` unless the caller has a configured channel: `update finalize`
// persists any `--channel`, so a defaulted one would mutate openclaw.json.
expect(call.argv).not.toContain("--channel");
expect(call.argv).not.toContain("--timeout");
// No per-step timeout requested → outer backstop is the floor.
expect(call.timeoutMs).toBe(30 * 60_000);
+8 -7
View File
@@ -21,7 +21,7 @@ import { GATEWAY_SERVICE_RUNTIME_PID_ENV } from "../daemon/constants.js";
import { resolveGatewayInstallEntrypoint } from "../daemon/gateway-entrypoint.js";
import { runCommandWithTimeout } from "../process/exec.js";
import { resolveStableNodePath } from "./stable-node-path.js";
import { DEFAULT_GIT_CHANNEL, type UpdateChannel } from "./update-channels.js";
import type { UpdateChannel } from "./update-channels.js";
import type { UpdateRunResult } from "./update-runner.js";
// Whole-process backstop for the finalizer. `update finalize` runs several timed
@@ -158,16 +158,17 @@ export async function runPostCoreFinalizeAfterGatewayUpdate(params: {
typeof params.timeoutMs === "number" && Number.isFinite(params.timeoutMs)
? params.timeoutMs
: undefined;
// This helper only runs for git/source updates, where `runGatewayUpdate`
// defaults the core update to the git/dev channel (`opts.channel ?? "dev"`).
// Match it so the finalizer converges official plugins on the same channel as
// the core update instead of falling back to the package (stable) channel.
const channel = params.channel ?? DEFAULT_GIT_CHANNEL;
// Only forward `--channel` when the caller actually has a configured channel.
// `update finalize` treats any `--channel` as an explicit request and persists
// it to `openclaw.json` (`persistRequestedUpdateChannel`); emitting a defaulted
// channel here would write a channel the user never requested. When omitted,
// the finalizer converges on the stored/default channel — the reconcile still
// resolves a host-compatible version, it just does not mutate config.
const nodePath = await resolveStableNodePath(process.execPath);
const argv = buildFinalizeArgv({
nodePath,
entrypoint,
channel,
...(params.channel ? { channel: params.channel } : {}),
...(perStepTimeoutMs === undefined ? {} : { timeoutMs: perStepTimeoutMs }),
});
// Pin the finalizer's host-compat resolution to the just-installed core