diff --git a/CHANGELOG.md b/CHANGELOG.md
index c117d6ca146..ba4978eff2f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Plugins/runtime-deps: prune legacy version-scoped plugin runtime-deps roots during bundled dependency repair and cover the path in Package Acceptance's upgrade-survivor matrix, so upgrades from 2026.4.x no longer leave stale per-plugin runtime trees after doctor runs. Thanks @vincentkoc.
+- Plugins/runtime-deps: keep Gateway startup plugin imports and runtime plugin fallback loads verify-only after startup/config repair planning, so packaged installs no longer spawn package-manager repair from hot paths after readiness. Refs #75283 and #75069. Thanks @brokemac79 and @xiaohuaxi.
- Google Meet: interrupt Realtime provider output when local barge-in clears playback, so command-pair audio stops model speech instead of only restarting Chrome playback. Fixes #73850. (#73834) Thanks @shhtheonlyperson.
- Gateway/config: cap oversized plugin-owned schemas in the full `config.schema` response so large installed plugin sets cannot balloon Gateway RSS or crash schema clients. Thanks @vincentkoc.
- Gateway/sessions: use bounded tail reads for sessions-list transcript usage fallbacks and cap bulk title/last-message hydration, keeping large session stores responsive when rows request derived previews. Thanks @vincentkoc.
diff --git a/docs/gateway/doctor.md b/docs/gateway/doctor.md
index 63fe501cc74..6da7c85eff7 100644
--- a/docs/gateway/doctor.md
+++ b/docs/gateway/doctor.md
@@ -342,7 +342,7 @@ That stages grounded durable candidates into the short-term dreaming store while
Doctor verifies runtime dependencies only for bundled plugins that are active in the current config or enabled by their bundled manifest default, for example `plugins.entries.discord.enabled: true`, legacy `channels.discord.enabled: true`, configured `models.providers.*` / agent model refs, or a default-enabled bundled plugin without provider ownership. If any are missing, doctor reports the packages and installs them in `openclaw doctor --fix` / `openclaw doctor --repair` mode. External plugins still use `openclaw plugins install` / `openclaw plugins update`; doctor does not install dependencies for arbitrary plugin paths.
- During doctor repair, bundled runtime-dependency npm installs report spinner progress in TTY sessions and periodic line progress in piped/headless output. The Gateway and local CLI can also repair active bundled plugin runtime dependencies on demand before importing a bundled plugin. These installs are scoped to the plugin runtime install root, run with scripts disabled, do not write a package lock, and are guarded by an install-root lock so concurrent CLI or Gateway starts do not mutate the same `node_modules` tree at the same time. Stale legacy locks from killed Docker/container starts are reclaimed when their owner metadata cannot prove a current process incarnation and the lock files are old.
+ During doctor repair, bundled runtime-dependency npm installs report spinner progress in TTY sessions and periodic line progress in piped/headless output. Gateway startup and config reload enter plugin-plan mode before importing bundled plugin runtime modules; normal runtime imports are verify-only and do not spawn package-manager repair. These installs are scoped to the plugin runtime install root, run with scripts disabled, do not write a package lock, and are guarded by an install-root lock so concurrent CLI or Gateway starts do not mutate the same `node_modules` tree at the same time. Stale legacy locks from killed Docker/container starts are reclaimed when their owner metadata cannot prove a current process incarnation and the lock files are old.
diff --git a/src/agents/runtime-plugins.test.ts b/src/agents/runtime-plugins.test.ts
index 4b0d1efddd4..927655a2a53 100644
--- a/src/agents/runtime-plugins.test.ts
+++ b/src/agents/runtime-plugins.test.ts
@@ -48,6 +48,7 @@ describe("ensureRuntimePluginsLoaded", () => {
expect(hoisted.resolveRuntimePluginRegistry).toHaveBeenCalledWith({
config: {} as never,
+ installBundledRuntimeDeps: false,
workspaceDir: "/tmp/workspace",
runtimeOptions: {
allowGatewaySubagentBinding: true,
@@ -63,6 +64,7 @@ describe("ensureRuntimePluginsLoaded", () => {
expect(hoisted.resolveRuntimePluginRegistry).toHaveBeenCalledWith({
config: {} as never,
+ installBundledRuntimeDeps: false,
workspaceDir: "/tmp/workspace",
runtimeOptions: undefined,
});
@@ -78,6 +80,7 @@ describe("ensureRuntimePluginsLoaded", () => {
expect(hoisted.resolveRuntimePluginRegistry).toHaveBeenCalledWith({
config: {} as never,
+ installBundledRuntimeDeps: false,
workspaceDir: "/tmp/workspace",
runtimeOptions: {
allowGatewaySubagentBinding: true,
diff --git a/src/agents/runtime-plugins.ts b/src/agents/runtime-plugins.ts
index 6838c258a5d..8860b8bd905 100644
--- a/src/agents/runtime-plugins.ts
+++ b/src/agents/runtime-plugins.ts
@@ -18,6 +18,7 @@ export function ensureRuntimePluginsLoaded(params: {
const loadOptions = {
config: params.config,
workspaceDir,
+ installBundledRuntimeDeps: false,
runtimeOptions: allowGatewaySubagentBinding
? {
allowGatewaySubagentBinding: true,
diff --git a/src/gateway/server-startup-plugins.test.ts b/src/gateway/server-startup-plugins.test.ts
index d581ae5824b..b81c6a90fa9 100644
--- a/src/gateway/server-startup-plugins.test.ts
+++ b/src/gateway/server-startup-plugins.test.ts
@@ -218,7 +218,7 @@ describe("prepareGatewayPluginBootstrap runtime-deps staging", () => {
runStartupSessionMigration.mockClear();
});
- it("falls back to loader-level runtime-deps staging after failed pre-start staging", async () => {
+ it("loads startup plugins in verify-only mode after failed pre-start staging", async () => {
repairBundledRuntimeDepsPackagePlanAsync.mockRejectedValueOnce(new Error("offline registry"));
const log = createLog();
const { prepareGatewayPluginBootstrap } = await import("./server-startup-plugins.js");
@@ -245,7 +245,7 @@ describe("prepareGatewayPluginBootstrap runtime-deps staging", () => {
pluginLookUpTable: expect.objectContaining({
manifestRegistry: pluginManifestRegistry,
}),
- installBundledRuntimeDeps: true,
+ installBundledRuntimeDeps: false,
}),
);
expect(repairBundledRuntimeDepsPackagePlanAsync).toHaveBeenCalledOnce();
@@ -296,7 +296,7 @@ describe("prepareGatewayPluginBootstrap runtime-deps staging", () => {
}),
);
expect(loadGatewayStartupPlugins).toHaveBeenCalledWith(
- expect.objectContaining({ installBundledRuntimeDeps: true }),
+ expect.objectContaining({ installBundledRuntimeDeps: false }),
);
});
@@ -321,7 +321,7 @@ describe("prepareGatewayPluginBootstrap runtime-deps staging", () => {
}),
);
expect(loadGatewayStartupPlugins).toHaveBeenCalledWith(
- expect.objectContaining({ installBundledRuntimeDeps: true }),
+ expect.objectContaining({ installBundledRuntimeDeps: false }),
);
});
@@ -491,7 +491,7 @@ describe("prepareGatewayPluginBootstrap runtime-deps staging", () => {
);
});
- it("falls back to loader-level runtime-deps staging after failed pre-start scan", async () => {
+ it("keeps startup plugin loading verify-only after failed pre-start scan", async () => {
repairBundledRuntimeDepsPackagePlanAsync.mockRejectedValueOnce(
new Error("unsupported runtime dependency spec"),
);
@@ -518,7 +518,7 @@ describe("prepareGatewayPluginBootstrap runtime-deps staging", () => {
expect.stringContaining("unsupported runtime dependency spec"),
);
expect(loadGatewayStartupPlugins).toHaveBeenCalledWith(
- expect.objectContaining({ installBundledRuntimeDeps: true }),
+ expect.objectContaining({ installBundledRuntimeDeps: false }),
);
expect(loadGatewayStartupPlugins.mock.calls[0]?.[0]).not.toHaveProperty(
"bundledRuntimeDepsInstaller",
diff --git a/src/gateway/server-startup-plugins.ts b/src/gateway/server-startup-plugins.ts
index 753b53de100..93ab7da7aca 100644
--- a/src/gateway/server-startup-plugins.ts
+++ b/src/gateway/server-startup-plugins.ts
@@ -290,7 +290,7 @@ export async function loadGatewayStartupPluginRuntime(params: {
baseMethods: params.baseMethods,
pluginIds: params.startupPluginIds,
pluginLookUpTable: params.pluginLookUpTable,
- installBundledRuntimeDeps: true,
+ installBundledRuntimeDeps: false,
bundledRuntimeDepsRepairError: prestageResult.repairError,
preferSetupRuntimeForChannelPlugins: params.preferSetupRuntimeForChannelPlugins,
suppressPluginInfoLogs: params.suppressPluginInfoLogs,
diff --git a/src/plugins/capability-provider-runtime.test.ts b/src/plugins/capability-provider-runtime.test.ts
index abd4937e91b..9bb540db323 100644
--- a/src/plugins/capability-provider-runtime.test.ts
+++ b/src/plugins/capability-provider-runtime.test.ts
@@ -103,6 +103,7 @@ function expectBundledCompatLoadPath(params: {
config: params.enablementCompat,
onlyPluginIds: ["openai"],
activate: false,
+ installBundledRuntimeDeps: false,
});
}
@@ -408,6 +409,7 @@ describe("resolvePluginCapabilityProviders", () => {
}),
onlyPluginIds: ["microsoft"],
activate: false,
+ installBundledRuntimeDeps: false,
});
});
@@ -616,6 +618,7 @@ describe("resolvePluginCapabilityProviders", () => {
config: expect.anything(),
onlyPluginIds: [],
activate: false,
+ installBundledRuntimeDeps: false,
});
});
@@ -660,6 +663,7 @@ describe("resolvePluginCapabilityProviders", () => {
config: compatConfig,
onlyPluginIds: ["google"],
activate: false,
+ installBundledRuntimeDeps: false,
});
});
@@ -795,6 +799,7 @@ describe("resolvePluginCapabilityProviders", () => {
config: compatConfig,
onlyPluginIds: ["microsoft"],
activate: false,
+ installBundledRuntimeDeps: false,
});
});
@@ -818,6 +823,7 @@ describe("resolvePluginCapabilityProviders", () => {
config: expect.anything(),
onlyPluginIds: [],
activate: false,
+ installBundledRuntimeDeps: false,
});
});
@@ -955,6 +961,7 @@ describe("resolvePluginCapabilityProviders", () => {
config: enablementCompat,
onlyPluginIds: ["google"],
activate: false,
+ installBundledRuntimeDeps: false,
});
});
@@ -1077,6 +1084,7 @@ describe("resolvePluginCapabilityProviders", () => {
config: enablementCompat,
onlyPluginIds: ["microsoft"],
activate: false,
+ installBundledRuntimeDeps: false,
});
});
});
diff --git a/src/plugins/capability-provider-runtime.ts b/src/plugins/capability-provider-runtime.ts
index 4aa9f7510fe..881cd9745cd 100644
--- a/src/plugins/capability-provider-runtime.ts
+++ b/src/plugins/capability-provider-runtime.ts
@@ -315,7 +315,7 @@ export function resolvePluginCapabilityProvider {
workspaceDir: "/resolved-workspace",
onlyPluginIds: ["demo-channel"],
throwOnLoadError: true,
+ installBundledRuntimeDeps: false,
}),
);
});
diff --git a/src/plugins/runtime/runtime-registry-loader.ts b/src/plugins/runtime/runtime-registry-loader.ts
index e1ab00b429b..f05d7dd6d2c 100644
--- a/src/plugins/runtime/runtime-registry-loader.ts
+++ b/src/plugins/runtime/runtime-registry-loader.ts
@@ -175,7 +175,7 @@ export function ensurePluginRegistryLoaded(options?: {
},
{
throwOnLoadError: true,
- installBundledRuntimeDeps: options?.installBundledRuntimeDeps,
+ installBundledRuntimeDeps: options?.installBundledRuntimeDeps ?? false,
...(hasExplicitPluginIdScope(requestedPluginIds) ||
shouldForwardChannelScope({ scope, scopedLoad }) ||
hasNonEmptyPluginIdScope(expectedChannelPluginIds)
diff --git a/src/plugins/tools.optional.test.ts b/src/plugins/tools.optional.test.ts
index f4cbee7b30f..37f761410df 100644
--- a/src/plugins/tools.optional.test.ts
+++ b/src/plugins/tools.optional.test.ts
@@ -457,6 +457,7 @@ describe("resolvePluginTools optional tools", () => {
expect(loadOpenClawPluginsMock).toHaveBeenCalledWith(
expect.objectContaining({
+ installBundledRuntimeDeps: false,
runtimeOptions: {
allowGatewaySubagentBinding: true,
},
diff --git a/src/plugins/tools.ts b/src/plugins/tools.ts
index f9ef9fc48cb..c19fe767ecc 100644
--- a/src/plugins/tools.ts
+++ b/src/plugins/tools.ts
@@ -133,7 +133,10 @@ export function resolvePluginTools(params: {
const runtimeOptions = params.allowGatewaySubagentBinding
? { allowGatewaySubagentBinding: true as const }
: undefined;
- const loadOptions = buildPluginRuntimeLoadOptions(context, { runtimeOptions });
+ const loadOptions = buildPluginRuntimeLoadOptions(context, {
+ installBundledRuntimeDeps: false,
+ runtimeOptions,
+ });
const registry = resolvePluginToolRegistry({
loadOptions,
allowGatewaySubagentBinding: params.allowGatewaySubagentBinding,