fix(fleet): tolerate harness metadata files in the managed install root
ci/woodpecker/pr/ci Pipeline was canceled
ci/woodpecker/pr/ci Pipeline was canceled
Claude Code writes installed_plugins.json and other metadata files into the seat's plugins directory during a session, so refusing every real entry made composition fail on each seat's second launch. Only a real directory is an unmanaged entry the pruner would orphan; plain files are harness state and pass through untouched. Found by the in-box hour-gate relaunch of the probe seat. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Dtdjx4Gxude9fwyLezCrhh
This commit is contained in:
co-authored by
Claude Fable 5
parent
c16256d48c
commit
5e15431027
@@ -242,6 +242,28 @@ describe('managed plugin and skill links', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('tolerates harness metadata files in the install root and still refuses real directories', () => {
|
||||||
|
const fx = fixture({ schema: 1, harness: 'claude', plugins: [] });
|
||||||
|
const pluginHome = join(fx.agentDir, '.claude', 'plugins');
|
||||||
|
mkdirSync(pluginHome, { recursive: true });
|
||||||
|
writeFileSync(join(pluginHome, 'installed_plugins.json'), '{}\n');
|
||||||
|
|
||||||
|
const plan = resolveFleetLaunchComposition('fred', {
|
||||||
|
systemHome: fx.systemHome,
|
||||||
|
userHome: fx.userHome,
|
||||||
|
});
|
||||||
|
expect(plan.prune).toEqual([]);
|
||||||
|
expect(readFileSync(join(pluginHome, 'installed_plugins.json'), 'utf8')).toBe('{}\n');
|
||||||
|
|
||||||
|
mkdirSync(join(pluginHome, 'stray-plugin'), { recursive: true });
|
||||||
|
expect(() =>
|
||||||
|
resolveFleetLaunchComposition('fred', {
|
||||||
|
systemHome: fx.systemHome,
|
||||||
|
userHome: fx.userHome,
|
||||||
|
}),
|
||||||
|
).toThrowError(/real plugin directory occupies managed install root.*refusing to prune/i);
|
||||||
|
});
|
||||||
|
|
||||||
it('surfaces a real directory at a managed link path without deleting it', () => {
|
it('surfaces a real directory at a managed link path without deleting it', () => {
|
||||||
const fx = fixture({ schema: 1, harness: 'claude', plugins: ['keep'] });
|
const fx = fixture({ schema: 1, harness: 'claude', plugins: ['keep'] });
|
||||||
mkdirSync(join(fx.userHome, 'plugins', 'keep'), { recursive: true });
|
mkdirSync(join(fx.userHome, 'plugins', 'keep'), { recursive: true });
|
||||||
|
|||||||
@@ -498,11 +498,17 @@ function resolveManagedLinks(
|
|||||||
const path = join(installRoot, entry.name);
|
const path = join(installRoot, entry.name);
|
||||||
if (desired.has(entry.name)) continue;
|
if (desired.has(entry.name)) continue;
|
||||||
if (!entry.isSymbolicLink()) {
|
if (!entry.isSymbolicLink()) {
|
||||||
|
// The harness writes its own metadata files (e.g. installed_plugins.json)
|
||||||
|
// beside the managed links; only a real directory is an unmanaged entry
|
||||||
|
// the pruner would orphan.
|
||||||
|
if (entry.isDirectory()) {
|
||||||
throw new FleetLaunchError(
|
throw new FleetLaunchError(
|
||||||
'COMPOSITION_FAILED',
|
'COMPOSITION_FAILED',
|
||||||
`real ${kind} entry occupies managed install root ${path}; refusing to prune it.`,
|
`real ${kind} directory occupies managed install root ${path}; refusing to prune it.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
prune.push(path);
|
prune.push(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user