fix(cli): keep update completion refresh lightweight

This commit is contained in:
Peter Steinberger
2026-04-27 08:23:44 +01:00
parent 1ee893bc5f
commit f427ddc220
7 changed files with 55 additions and 9 deletions

View File

@@ -12,6 +12,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- CLI/update: keep the automatic post-update completion refresh on the core-command tree so it no longer stages bundled plugin runtime deps before the Gateway restart path, avoiding `.24` update hangs and 1006 disconnect cascades. Fixes #72665. Thanks @sakalaboator and @He-Pin.
- Agents/Bedrock: stop heartbeat runs from persisting blank user transcript turns and repair existing blank user text messages before replay, preventing AWS Bedrock `ContentBlock` blank-text validation failures. Fixes #72640 and #72622. Thanks @goldzulu.
- LM Studio: allow interactive onboarding to leave the API key blank for unauthenticated local servers, using local synthetic auth while clearing stale LM Studio auth profiles. Fixes #66937. Thanks @olamedia.
- Process/Windows: decode command stdout and stderr from raw bytes with console-codepage awareness, while preserving valid UTF-8 output and multibyte characters split across chunks. Fixes #50519. Thanks @iready, @kevinten10, @zhangyongjie1997, @knightplat-blip, @heiqishi666, and @slepybear.

View File

@@ -87,9 +87,10 @@ The Gateway core auto-updater (when enabled via config) reuses this same update
For package-manager installs, `openclaw update` resolves the target package
version before invoking the package manager. Even when the installed version
already matches the target, the command refreshes the global package install,
then runs plugin sync, completion refresh, and restart work. This keeps packaged
sidecars and channel-owned plugin records aligned with the installed OpenClaw
build.
then runs plugin sync, a core-command completion refresh, and restart work. This
keeps packaged sidecars and channel-owned plugin records aligned with the
installed OpenClaw build while leaving full plugin-command completion rebuilds to
explicit `openclaw completion --write-state` runs.
## Git checkout flow

View File

@@ -10,6 +10,7 @@ import {
} from "./completion-fish.js";
import {
COMPLETION_SHELLS,
COMPLETION_SKIP_PLUGIN_COMMANDS_ENV,
installCompletion,
isCompletionShell,
resolveCompletionCachePath,
@@ -106,10 +107,12 @@ export function registerCompletionCli(program: Command) {
// Eagerly register all subcommands except completion itself to build the full tree.
await registerSubcommandsForCompletion(program);
const { registerPluginCliCommandsFromValidatedConfig } = await import("../plugins/cli.js");
await registerPluginCliCommandsFromValidatedConfig(program, undefined, undefined, {
mode: "eager",
});
if (process.env[COMPLETION_SKIP_PLUGIN_COMMANDS_ENV] !== "1") {
const { registerPluginCliCommandsFromValidatedConfig } = await import("../plugins/cli.js");
await registerPluginCliCommandsFromValidatedConfig(program, undefined, undefined, {
mode: "eager",
});
}
if (options.writeState) {
const writeShells = options.shell ? [shell] : [...COMPLETION_SHELLS];

View File

@@ -106,4 +106,35 @@ describe("completion-cli write-state", () => {
await fs.rm(stateDir, { recursive: true, force: true });
await fs.rm(homeDir, { recursive: true, force: true });
});
it("can skip plugin command registration for update-triggered cache writes", async () => {
const [{ COMPLETION_SKIP_PLUGIN_COMMANDS_ENV }, { registerCompletionCli }] = await Promise.all([
import("./completion-runtime.js"),
import("./completion-cli.js"),
]);
const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-completion-state-"));
const homeDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-completion-home-"));
process.env.OPENCLAW_STATE_DIR = stateDir;
process.env.HOME = homeDir;
process.env[COMPLETION_SKIP_PLUGIN_COMMANDS_ENV] = "1";
try {
const program = new Command();
program.name("openclaw");
registerCompletionCli(program);
await program.parseAsync(["completion", "--write-state"], { from: "user" });
expect(registerSubCliByNameMock).toHaveBeenCalledWith(program, "qa");
expect(registerPluginCliCommandsFromValidatedConfigMock).not.toHaveBeenCalled();
expect(await fs.readdir(path.join(stateDir, "completions"))).toEqual(
expect.arrayContaining(["openclaw.bash", "openclaw.fish", "openclaw.ps1", "openclaw.zsh"]),
);
} finally {
delete process.env[COMPLETION_SKIP_PLUGIN_COMMANDS_ENV];
await fs.rm(stateDir, { recursive: true, force: true });
await fs.rm(homeDir, { recursive: true, force: true });
}
});
});

View File

@@ -10,6 +10,7 @@ import { pathExists } from "../utils.js";
export const COMPLETION_SHELLS = ["zsh", "bash", "powershell", "fish"] as const;
export type CompletionShell = (typeof COMPLETION_SHELLS)[number];
export const COMPLETION_SKIP_PLUGIN_COMMANDS_ENV = "OPENCLAW_COMPLETION_SKIP_PLUGIN_COMMANDS";
export function isCompletionShell(value: string): value is CompletionShell {
return COMPLETION_SHELLS.includes(value as CompletionShell);

View File

@@ -540,7 +540,12 @@ describe("update-cli", () => {
expect(spawnSync).toHaveBeenCalledWith(
expect.any(String),
[path.join(root, "openclaw.mjs"), "completion", "--write-state"],
expect.objectContaining({ timeout: 30_000 }),
expect.objectContaining({
env: expect.objectContaining({
OPENCLAW_COMPLETION_SKIP_PLUGIN_COMMANDS: "1",
}),
timeout: 30_000,
}),
);
});

View File

@@ -22,6 +22,7 @@ import { defaultRuntime } from "../../runtime.js";
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
import { theme } from "../../terminal/theme.js";
import { pathExists } from "../../utils.js";
import { COMPLETION_SKIP_PLUGIN_COMMANDS_ENV } from "../completion-runtime.js";
export type UpdateCommandOptions = {
json?: boolean;
@@ -268,7 +269,10 @@ export async function tryWriteCompletionCache(root: string, jsonMode: boolean):
const result = spawnSync(resolveNodeRunner(), [binPath, "completion", "--write-state"], {
cwd: root,
env: process.env,
env: {
...process.env,
[COMPLETION_SKIP_PLUGIN_COMMANDS_ENV]: "1",
},
encoding: "utf-8",
timeout: COMPLETION_CACHE_WRITE_TIMEOUT_MS,
});