mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 15:40:44 +00:00
fix(update): fail closed on unknown gateway runtime
This commit is contained in:
@@ -340,6 +340,7 @@ Docs: https://docs.openclaw.ai
|
||||
- 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.
|
||||
- Discord: keep SecretRef-backed bot tokens discoverable for message actions without resolving the token during schema generation, and resolve scoped channel SecretRefs before outbound agent message sends even when the tool is built from a config snapshot. Fixes #75324. Thanks @slideshow-dingo and @Conan-Scott.
|
||||
- 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: treat inherited Gateway service markers as origin hints and only block package replacement when the managed Gateway is still live, so self-updates can stop the service and continue safely. (#75729) Thanks @hxy91819.
|
||||
- Models/DeepInfra: declare DeepInfra manifest catalog discovery and derive its runtime fallback catalog from the manifest, restoring provider-filtered `models list --all --provider deepinfra` rows without duplicated static model data. Thanks @shakkernerd.
|
||||
- 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.
|
||||
|
||||
@@ -184,7 +184,10 @@ vi.mock("../daemon/service.js", () => ({
|
||||
? (command.environment as NodeJS.ProcessEnv | undefined)
|
||||
: undefined),
|
||||
};
|
||||
const [loaded, runtime] = await Promise.all([serviceLoaded({ env }), serviceReadRuntime(env)]);
|
||||
const [loaded, runtime] = await Promise.all([
|
||||
serviceLoaded({ env }).catch(() => false),
|
||||
serviceReadRuntime(env).catch(() => undefined),
|
||||
]);
|
||||
return {
|
||||
installed: command !== null,
|
||||
loaded,
|
||||
@@ -1337,6 +1340,41 @@ describe("update-cli", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("refuses package updates from inherited gateway service env when runtime inspection is inconclusive", async () => {
|
||||
mockPackageInstallStatus(createCaseDir("openclaw-update"));
|
||||
serviceReadCommand.mockResolvedValue({
|
||||
programArguments: ["openclaw", "gateway", "run"],
|
||||
environment: {
|
||||
OPENCLAW_SERVICE_MARKER: "openclaw",
|
||||
OPENCLAW_SERVICE_KIND: "gateway",
|
||||
},
|
||||
});
|
||||
serviceReadRuntime.mockRejectedValueOnce(new Error("runtime probe failed"));
|
||||
|
||||
await withEnvAsync(
|
||||
{
|
||||
OPENCLAW_SERVICE_MARKER: "openclaw",
|
||||
OPENCLAW_SERVICE_KIND: "gateway",
|
||||
},
|
||||
async () => {
|
||||
await updateCommand({ yes: true });
|
||||
},
|
||||
);
|
||||
|
||||
expect(defaultRuntime.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
"Package updates cannot run from inside the gateway service process.",
|
||||
),
|
||||
);
|
||||
expect(defaultRuntime.exit).toHaveBeenCalledWith(1);
|
||||
expect(serviceStop).not.toHaveBeenCalled();
|
||||
expect(runGatewayUpdate).not.toHaveBeenCalled();
|
||||
expect(runCommandWithTimeout).not.toHaveBeenCalledWith(
|
||||
["npm", "i", "-g", "openclaw@latest", "--no-fund", "--no-audit", "--loglevel=error"],
|
||||
expect.any(Object),
|
||||
);
|
||||
});
|
||||
|
||||
it("blocks package updates when the target requires a newer Node runtime", async () => {
|
||||
mockPackageInstallStatus(createCaseDir("openclaw-update"));
|
||||
vi.mocked(fetchNpmPackageTargetStatus).mockResolvedValue({
|
||||
|
||||
@@ -163,6 +163,7 @@ export function shouldUseLegacyProcessRestartAfterUpdate(params: {
|
||||
type PrePackageServiceStop = {
|
||||
stopped: boolean;
|
||||
inspected: boolean;
|
||||
runtimeInspected: boolean;
|
||||
running: boolean;
|
||||
serviceEnv?: NodeJS.ProcessEnv;
|
||||
};
|
||||
@@ -177,13 +178,14 @@ async function maybeStopManagedServiceBeforePackageUpdate(params: {
|
||||
service = resolveGatewayService();
|
||||
serviceState = await readGatewayServiceState(service, { env: process.env });
|
||||
} catch {
|
||||
return { stopped: false, inspected: false, running: false };
|
||||
return { stopped: false, inspected: false, runtimeInspected: false, running: false };
|
||||
}
|
||||
|
||||
if (!serviceState.installed) {
|
||||
return { stopped: false, inspected: true, running: false };
|
||||
return { stopped: false, inspected: true, runtimeInspected: true, running: false };
|
||||
}
|
||||
|
||||
const runtimeInspected = Boolean(serviceState.runtime);
|
||||
if (!params.shouldRestart) {
|
||||
if (!params.jsonMode && serviceState.running) {
|
||||
defaultRuntime.log(
|
||||
@@ -195,20 +197,43 @@ async function maybeStopManagedServiceBeforePackageUpdate(params: {
|
||||
return {
|
||||
stopped: false,
|
||||
inspected: true,
|
||||
runtimeInspected,
|
||||
running: serviceState.running,
|
||||
serviceEnv: serviceState.env,
|
||||
};
|
||||
}
|
||||
|
||||
if (!runtimeInspected) {
|
||||
return {
|
||||
stopped: false,
|
||||
inspected: true,
|
||||
runtimeInspected: false,
|
||||
running: false,
|
||||
serviceEnv: serviceState.env,
|
||||
};
|
||||
}
|
||||
|
||||
if (!serviceState.running) {
|
||||
return { stopped: false, inspected: true, running: false, serviceEnv: serviceState.env };
|
||||
return {
|
||||
stopped: false,
|
||||
inspected: true,
|
||||
runtimeInspected: true,
|
||||
running: false,
|
||||
serviceEnv: serviceState.env,
|
||||
};
|
||||
}
|
||||
|
||||
if (!params.jsonMode) {
|
||||
defaultRuntime.log(theme.muted("Stopping managed gateway service before package update..."));
|
||||
}
|
||||
await service.stop({ env: serviceState.env, stdout: process.stdout });
|
||||
return { stopped: true, inspected: true, running: true, serviceEnv: serviceState.env };
|
||||
return {
|
||||
stopped: true,
|
||||
inspected: true,
|
||||
runtimeInspected: true,
|
||||
running: true,
|
||||
serviceEnv: serviceState.env,
|
||||
};
|
||||
}
|
||||
|
||||
async function maybeRestartServiceAfterFailedPackageUpdate(params: {
|
||||
@@ -256,10 +281,13 @@ function shouldBlockPackageUpdateFromGatewayServiceEnv(params: {
|
||||
if (!stopState?.inspected) {
|
||||
return true;
|
||||
}
|
||||
if (!stopState.running) {
|
||||
if (stopState.stopped) {
|
||||
return false;
|
||||
}
|
||||
return !stopState.stopped;
|
||||
if (!stopState.runtimeInspected) {
|
||||
return true;
|
||||
}
|
||||
return stopState.running;
|
||||
}
|
||||
|
||||
function formatCommandFailure(stdout: string, stderr: string): string {
|
||||
|
||||
Reference in New Issue
Block a user