fix(exec): re-run install-tool when switching back to an earlier tool version (#46823)

`install-tool` installs a version and also makes it the active one. The
memo skipped `install-tool` for any tool version already installed in the
run, so after A -> B -> A the last command ran on B. Track the active
version per tool instead and skip only when the requested version is
already active.

Co-authored-by: Claude Opus 5.5 <michael.kriese+claude-code@mend.io>
This commit is contained in:
Michael Kriese
2026-10-07 16:15:34 +00:00
committed by GitHub
parent 318b38676e
commit 2ca2bc0ba0
2 changed files with 27 additions and 4 deletions
+21
View File
@@ -337,6 +337,27 @@ describe('util/exec/containerbase', () => {
generateInstallCommands(toolConstraints, true),
).resolves.toEqual(['install-tool composer 2.2.0']);
});
it('re-issues the install command when switching back to an earlier version', async () => {
const toolConstraints: ToolConstraint[] = [{ toolName: 'composer' }];
await expect(
generateInstallCommands(toolConstraints, true),
).resolves.toEqual(['install-tool composer 2.1.0']);
datasource.getPkgReleases.mockResolvedValueOnce({
releases: [{ version: '2.2.0' }],
});
await expect(
generateInstallCommands(toolConstraints, true),
).resolves.toEqual(['install-tool composer 2.2.0']);
datasource.getPkgReleases.mockResolvedValueOnce({
releases: [{ version: '2.1.0' }],
});
await expect(
generateInstallCommands(toolConstraints, true),
).resolves.toEqual(['install-tool composer 2.1.0']);
});
});
});
});
+6 -4
View File
@@ -382,7 +382,9 @@ export async function resolveConstraint(
// Docker execs run in a fresh `--rm` container each time, so `memoize` must
// stay false there - only the persistent-host (binarySource=install) path
// can safely skip a tool it has already installed this run.
// can safely skip a tool whose requested version is already active.
// `install-tool` also activates the version it installs, so the memo tracks
// the active version per tool, not every installed version.
export async function generateInstallCommands(
toolConstraints: Opt<ToolConstraint[]>,
memoize = false,
@@ -393,11 +395,11 @@ export async function generateInstallCommands(
const toolVersion = await resolveConstraint(toolConstraint);
const { toolName } = toolConstraint;
if (memoize) {
const cacheKey = `containerbase-installed:${toolName}:${toolVersion}`;
if (memCache.get<boolean | undefined>(cacheKey)) {
const cacheKey = `containerbase-active:${toolName}`;
if (memCache.get<string | undefined>(cacheKey) === toolVersion) {
continue;
}
memCache.set(cacheKey, true);
memCache.set(cacheKey, toolVersion);
}
const installCommand = `install-tool ${toolName} ${quote(toolVersion)}`;
installCommands.push(installCommand);