fix: propagate update timeout to plugin installs

This commit is contained in:
Peter Steinberger
2026-04-26 10:44:58 +01:00
parent d1f40731e3
commit b67d9bf7f0
6 changed files with 99 additions and 6 deletions

View File

@@ -594,6 +594,7 @@ async function resolveCompatiblePackageVersion(params: {
requestedVersion?: string;
baseUrl?: string;
token?: string;
timeoutMs?: number;
}): Promise<
| {
ok: true;
@@ -617,6 +618,7 @@ async function resolveCompatiblePackageVersion(params: {
version: requestedVersion,
baseUrl: params.baseUrl,
token: params.token,
timeoutMs: params.timeoutMs,
});
} catch (error) {
return mapClawHubRequestError(error, {
@@ -747,6 +749,7 @@ export async function installPluginFromClawHub(
logger?: PluginInstallLogger;
mode?: "install" | "update";
extensionsDir?: string;
timeoutMs?: number;
dryRun?: boolean;
expectedPluginId?: string;
},
@@ -775,6 +778,7 @@ export async function installPluginFromClawHub(
name: parsed.name,
baseUrl: params.baseUrl,
token: params.token,
timeoutMs: params.timeoutMs,
});
} catch (error) {
return mapClawHubRequestError(error, {
@@ -787,6 +791,7 @@ export async function installPluginFromClawHub(
requestedVersion: parsed.version,
baseUrl: params.baseUrl,
token: params.token,
timeoutMs: params.timeoutMs,
});
if (!versionState.ok) {
return versionState;
@@ -821,6 +826,7 @@ export async function installPluginFromClawHub(
version: versionState.version,
baseUrl: params.baseUrl,
token: params.token,
timeoutMs: params.timeoutMs,
});
} catch (error) {
return buildClawHubInstallFailure(formatErrorMessage(error));
@@ -864,6 +870,7 @@ export async function installPluginFromClawHub(
logger: params.logger,
mode: params.mode,
extensionsDir: params.extensionsDir,
timeoutMs: params.timeoutMs,
dryRun: params.dryRun,
expectedPluginId: params.expectedPluginId,
});

View File

@@ -1156,6 +1156,7 @@ export async function installPluginFromMarketplace(
logger: params.logger,
mode: params.mode,
extensionsDir: params.extensionsDir,
timeoutMs: params.timeoutMs,
dryRun: params.dryRun,
expectedPluginId: params.expectedPluginId,
});

View File

@@ -214,12 +214,14 @@ function expectNpmUpdateCall(params: {
spec: string;
expectedIntegrity?: string;
expectedPluginId?: string;
timeoutMs?: number;
}) {
expect(installPluginFromNpmSpecMock).toHaveBeenCalledWith(
expect.objectContaining({
spec: params.spec,
expectedIntegrity: params.expectedIntegrity,
...(params.expectedPluginId ? { expectedPluginId: params.expectedPluginId } : {}),
...(params.timeoutMs ? { timeoutMs: params.timeoutMs } : {}),
}),
);
}
@@ -355,6 +357,48 @@ describe("updateNpmInstalledPlugins", () => {
},
);
it("passes timeout budget to npm plugin metadata checks and installs", async () => {
const installPath = createInstalledPackageDir({
name: "@martian-engineering/lossless-claw",
version: "0.9.0",
});
mockNpmViewMetadata({
name: "@martian-engineering/lossless-claw",
version: "0.10.0",
integrity: "sha512-next",
});
installPluginFromNpmSpecMock.mockResolvedValue(
createSuccessfulNpmUpdateResult({
pluginId: "lossless-claw",
targetDir: installPath,
version: "0.10.0",
}),
);
await updateNpmInstalledPlugins({
config: createNpmInstallConfig({
pluginId: "lossless-claw",
spec: "@martian-engineering/lossless-claw",
installPath,
resolvedName: "@martian-engineering/lossless-claw",
resolvedSpec: "@martian-engineering/lossless-claw@0.9.0",
resolvedVersion: "0.9.0",
}),
pluginIds: ["lossless-claw"],
timeoutMs: 1_800_000,
});
const npmViewCall = runCommandWithTimeoutMock.mock.calls.find(
([argv]) => Array.isArray(argv) && argv[0] === "npm" && argv[1] === "view",
);
expect(npmViewCall?.[1]).toEqual(expect.objectContaining({ timeoutMs: 1_800_000 }));
expectNpmUpdateCall({
spec: "@martian-engineering/lossless-claw",
expectedPluginId: "lossless-claw",
timeoutMs: 1_800_000,
});
});
it("skips npm reinstall and config rewrite when the installed artifact is unchanged", async () => {
const installPath = createInstalledPackageDir({
name: "@martian-engineering/lossless-claw",
@@ -798,6 +842,7 @@ describe("updateNpmInstalledPlugins", () => {
clawhubChannel: "official",
}),
pluginIds: ["demo"],
timeoutMs: 1_800_000,
});
expect(installPluginFromClawHubMock).toHaveBeenCalledWith(
@@ -806,6 +851,7 @@ describe("updateNpmInstalledPlugins", () => {
baseUrl: "https://clawhub.ai",
expectedPluginId: "demo",
mode: "update",
timeoutMs: 1_800_000,
}),
);
expect(result.config.plugins?.installs?.demo).toMatchObject({
@@ -930,6 +976,7 @@ describe("updateNpmInstalledPlugins", () => {
marketplacePlugin: "claude-bundle",
}),
pluginIds: ["claude-bundle"],
timeoutMs: 1_800_000,
dryRun: true,
});
@@ -939,6 +986,7 @@ describe("updateNpmInstalledPlugins", () => {
plugin: "claude-bundle",
expectedPluginId: "claude-bundle",
dryRun: true,
timeoutMs: 1_800_000,
}),
);
expect(result.outcomes).toEqual([

View File

@@ -469,6 +469,7 @@ export async function updateNpmInstalledPlugins(params: {
logger?: PluginUpdateLogger;
pluginIds?: string[];
skipIds?: Set<string>;
timeoutMs?: number;
dryRun?: boolean;
dangerouslyForceUnsafeInstall?: boolean;
specOverrides?: Record<string, string>;
@@ -567,7 +568,10 @@ export async function updateNpmInstalledPlugins(params: {
});
if (!params.dryRun && record.source === "npm" && currentVersion) {
const metadataResult = await resolveNpmSpecMetadata({ spec: effectiveSpec! });
const metadataResult = await resolveNpmSpecMetadata({
spec: effectiveSpec!,
timeoutMs: params.timeoutMs,
});
if (metadataResult.ok) {
if (
shouldSkipUnchangedNpmInstall({
@@ -604,6 +608,7 @@ export async function updateNpmInstalledPlugins(params: {
spec: effectiveSpec!,
mode: "update",
extensionsDir,
timeoutMs: params.timeoutMs,
dryRun: true,
dangerouslyForceUnsafeInstall: params.dangerouslyForceUnsafeInstall,
expectedPluginId: pluginId,
@@ -622,6 +627,7 @@ export async function updateNpmInstalledPlugins(params: {
baseUrl: record.clawhubUrl,
mode: "update",
extensionsDir,
timeoutMs: params.timeoutMs,
dryRun: true,
dangerouslyForceUnsafeInstall: params.dangerouslyForceUnsafeInstall,
expectedPluginId: pluginId,
@@ -632,6 +638,7 @@ export async function updateNpmInstalledPlugins(params: {
plugin: record.marketplacePlugin!,
mode: "update",
extensionsDir,
timeoutMs: params.timeoutMs,
dryRun: true,
dangerouslyForceUnsafeInstall: params.dangerouslyForceUnsafeInstall,
expectedPluginId: pluginId,
@@ -708,6 +715,7 @@ export async function updateNpmInstalledPlugins(params: {
spec: effectiveSpec!,
mode: "update",
extensionsDir,
timeoutMs: params.timeoutMs,
dangerouslyForceUnsafeInstall: params.dangerouslyForceUnsafeInstall,
expectedPluginId: pluginId,
expectedIntegrity,
@@ -725,6 +733,7 @@ export async function updateNpmInstalledPlugins(params: {
baseUrl: record.clawhubUrl,
mode: "update",
extensionsDir,
timeoutMs: params.timeoutMs,
dangerouslyForceUnsafeInstall: params.dangerouslyForceUnsafeInstall,
expectedPluginId: pluginId,
logger,
@@ -734,6 +743,7 @@ export async function updateNpmInstalledPlugins(params: {
plugin: record.marketplacePlugin!,
mode: "update",
extensionsDir,
timeoutMs: params.timeoutMs,
dangerouslyForceUnsafeInstall: params.dangerouslyForceUnsafeInstall,
expectedPluginId: pluginId,
logger,