fix(skills): handle prune type mismatch by using warnings for agent-attached skills
GitHub/sks_sh pruned skills don't have project/workspace context needed for the CompanySkillProjectScanConflict/Skipped types. Orphaned skills are silently deleted; skills still used by agents emit a warning instead of a conflict entry. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -278,31 +278,24 @@ describe("company skill mutation permissions", () => {
|
||||
expect(mockCompanySkillService.scanProjectWorkspaces).toHaveBeenCalledWith("company-1", {});
|
||||
});
|
||||
|
||||
it("returns skipped and conflicts from scan including pruned skill conflicts", async () => {
|
||||
it("returns warnings from scan when removed skills are still used by agents", async () => {
|
||||
mockAgentService.getById.mockResolvedValue({
|
||||
id: "agent-1",
|
||||
companyId: "company-1",
|
||||
permissions: { canCreateAgents: true },
|
||||
});
|
||||
|
||||
const skippedSkill = { id: "skill-1", slug: "removed-skill", name: "Removed Skill" };
|
||||
const conflictEntry = {
|
||||
path: "https://github.com/vercel-labs/agent-browser",
|
||||
existingSkillId: "skill-2",
|
||||
existingSkillKey: "vercel-labs/agent-browser/ghost-skill",
|
||||
existingSourceLocator: "https://github.com/vercel-labs/agent-browser",
|
||||
reason: 'Skill "ghost-skill" was removed from https://github.com/vercel-labs/agent-browser but is still used by Builder. Detach it from those agents first.',
|
||||
};
|
||||
|
||||
mockCompanySkillService.scanProjectWorkspaces.mockResolvedValueOnce({
|
||||
scannedProjects: 1,
|
||||
scannedWorkspaces: 1,
|
||||
discovered: [],
|
||||
imported: [],
|
||||
updated: [],
|
||||
skipped: [skippedSkill],
|
||||
conflicts: [conflictEntry],
|
||||
warnings: [],
|
||||
skipped: [],
|
||||
conflicts: [],
|
||||
warnings: [
|
||||
'Skill "ghost-skill" was removed from https://github.com/vercel-labs/agent-browser but is still used by Builder. It will not be automatically removed.',
|
||||
],
|
||||
});
|
||||
|
||||
const res = await request(await createApp({
|
||||
@@ -316,12 +309,7 @@ describe("company skill mutation permissions", () => {
|
||||
|
||||
expect(res.status, JSON.stringify(res.body)).toBe(200);
|
||||
expect(res.body).toMatchObject({
|
||||
skipped: [{ id: "skill-1", slug: "removed-skill" }],
|
||||
conflicts: [{
|
||||
path: "https://github.com/vercel-labs/agent-browser",
|
||||
existingSkillId: "skill-2",
|
||||
reason: expect.stringContaining("was removed from"),
|
||||
}],
|
||||
warnings: [expect.stringContaining("was removed from")],
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2044,16 +2044,11 @@ export function companySkillService(db: Db) {
|
||||
if (currentSlugs.has(skill.slug)) continue;
|
||||
const usedByAgents = await usage(companyId, skill.key);
|
||||
if (usedByAgents.length > 0) {
|
||||
conflicts.push({
|
||||
path: sourceLocator,
|
||||
existingSkillId: skill.id,
|
||||
existingSkillKey: skill.key,
|
||||
existingSourceLocator: sourceLocator,
|
||||
reason: `Skill "${skill.slug}" was removed from ${sourceLocator} but is still used by ${usedByAgents.map((a) => a.name).join(", ")}. Detach it from those agents first.`,
|
||||
});
|
||||
warnings.push(
|
||||
`Skill "${skill.slug}" was removed from ${sourceLocator} but is still used by ${usedByAgents.map((a) => a.name).join(", ")}. It will not be automatically removed.`,
|
||||
);
|
||||
} else {
|
||||
const deleted = await deleteSkill(companyId, skill.id);
|
||||
if (deleted) skipped.push(deleted);
|
||||
await deleteSkill(companyId, skill.id);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user