infiniflow/ragflow · error · Error
Skill not found
Error message
Skill not found
What it means
Thrown in web/src/pages/skills/hooks.ts:1043 when deleteSkill cannot resolve a target folder id after all fallbacks: the caller-supplied folderId, the _folderId stashed on the in-state skill, and a listFile search for a folder whose name equals skillId all failed. The skill's file-manager folder is what gets deleted, so without it the flow cannot proceed and reports 'Skill not found'.
Source
Thrown at web/src/pages/skills/hooks.ts:1043
);
if (spaceFolderId) {
const { data: listData } = await fileManagerService.listFile({
parent_id: spaceFolderId,
});
if (listData.code === 0) {
const skillFolder = (listData.data?.files || []).find(
(f: any) => f.type === 'folder' && f.name === skillId,
);
if (skillFolder) {
targetFolderId = skillFolder.id;
}
}
}
}
if (!targetFolderId) {
throw new Error('Skill not found');
}
// Get versions by listing the skill folder
const { data: versionData } = await fileManagerService.listFile({
parent_id: targetFolderId,
});
let versionsToDelete: string[] = ['latest'];
if (versionData.code === 0) {
const versionFolders = (versionData.data?.files || []).filter(
(f: any) => f.type === 'folder' && /^\d+\.\d+\.\d+/.test(f.name),
);
if (versionFolders.length > 0) {
versionsToDelete = versionFolders.map((f: any) => f.name);
}
}
// Delete search index for all versionsView on GitHub (pinned to 554fb1133a)
Solutions
- Compare the folder-name normalization (lowercase, dashes) between deleteSkill's lookup and uploadSkill's creation
- Persist folderId on the skill record at fetch time so deletion never needs name guessing
- If folder resolution fails but the skill exists in the index, still remove the index entry instead of aborting entirely
- Re-fetch the skills list (fetchSkills) so _folderId is fresh, then retry delete
Example fix
// before
if (!targetFolderId) {
throw new Error('Skill not found');
}
// after
if (!targetFolderId) {
const normalized = skillId.replace(/\s+/g, '-').toLowerCase();
if (normalized !== skillId) {
const skillFolder = (listData.data?.files || []).find(
(f: any) => f.type === 'folder' && f.name === normalized,
);
if (skillFolder) targetFolderId = skillFolder.id;
}
}
if (!targetFolderId) {
throw new Error(
`Skill not found (skillId=${skillId}, space=${normalizedSpaceName})`,
);
} Defensive patterns
Strategy: type-guard
Validate before calling
const skillFolderIdFor = (skills: Skill[], skillId: string) => skills.find((s) => s.id === skillId)?.['_folderId'] ?? null;
Type guard
const hasFolderRef = (s: any): s is Skill & { _folderId: string } =>
typeof s?._folderId === 'string' && s._folderId.length > 0; Try / catch
try {
await deleteSkill(...);
} catch (e) {
if (e.message === 'Skill not found') await fetchSkills(spaceName, spaceId); // resync
else throw e;
} Prevention
- Persist the folder id on every skill at fetch time instead of name-guessing at delete time
- Apply the same name normalization (lowercase, dashes) in delete lookups as in upload creation
When it happens
Trigger: The skill appears in the list (from the search index) but its backing folder was already deleted or renamed in the file manager; skillId is the index's id, which differs from the folder name so the name-based lookup misses; the skills state was refetched from index-only data without _folderId; listFile returned code !== 0 so the search branch was skipped.
Common situations: Index out of sync with the file manager after a partial manual cleanup. Skill names containing spaces/uppercase normalized differently for the folder (name.replace(/\s+/g,'-').toLowerCase()) so the lookup name does not match. Stale component state across space switches.
Related errors
- Failed to delete skill
- Skills space not found
- Failed to create skill folder
- Failed to list skills folder
- Failed to get skill folder ID
AI-assisted analysis of infiniflow/ragflow@554fb1133a (2026-08-15).
Data as JSON: /api/errors/c4b42e89f17b5573.
Report an issue: GitHub.