infiniflow/ragflow · error · Error
Failed to delete skill
Error message
Failed to delete skill
What it means
Thrown in web/src/pages/skills/hooks.ts:1123 at the final step of deleteSkill: after resolving the folder, cleaning up per-version index entries (whose errors are deliberately ignored), the actual removeFile({ ids: [targetFolderId] }) returns code !== 0. The folder delete was rejected server-side; the outer catch converts it into a generic 'skills.deleteError' toast and returns false.
Source
Thrown at web/src/pages/skills/hooks.ts:1123
await skillSpaceService.deleteSkillIndex(
indexId,
normalizedSpaceId,
);
// oxlint-disable-next-line no-console
console.log(
`[deleteSkill] Successfully deleted index: ${indexId}`,
);
} catch {
// Ignore errors for versions that don't exist
}
}
}
const { data } = await fileManagerService.removeFile({
ids: [targetFolderId],
});
if (data.code !== 0) throw new Error('Failed to delete skill');
message.success(t('skills.deleteSuccess'));
// Refresh skills list using space name and space id
await fetchSkills(normalizedSpaceName, normalizedSpaceId);
return true;
} catch (error) {
console.error('Error deleting skill:', error);
message.error(t('skills.deleteError'));
return false;
}
},
// oxlint-disable-next-line react/exhaustive-deps
[t, fetchSkills, ensureSkillSpaceFolder, skills],
);
// Recursively find file by path in folder structure
// For versioned skills, automatically finds the version folder first
const findFileByPath = async (View on GitHub (pinned to 554fb1133a)
Solutions
- Distinguish 'already deleted' (retry the listFile for targetFolderId; if gone, treat as success) from real failures
- Surface data.message from removeFile in the error/log instead of the generic string
- Re-fetch the skills list even on failure so UI reflects actual state
- Disable the delete button while a deletion is in flight to prevent double submits
Example fix
// before
const { data } = await fileManagerService.removeFile({
ids: [targetFolderId],
});
if (data.code !== 0) throw new Error('Failed to delete skill');
// after
const { data } = await fileManagerService.removeFile({
ids: [targetFolderId],
});
if (data.code !== 0) {
const { data: recheck } = await fileManagerService.listFile({
parent_id: spaceFolderId ?? targetFolderId,
});
const stillThere = (recheck.data?.files || []).some(
(f: any) => f.id === targetFolderId,
);
if (!stillThere) {
await fetchSkills(normalizedSpaceName, normalizedSpaceId);
return true;
}
throw new Error(data.message || 'Failed to delete skill');
} Defensive patterns
Strategy: try-catch
Validate before calling
const folderStillExists = async (parentId: string, folderId: string) =>
(await fileManagerService.listFile({ parent_id: parentId }))
.data.data?.files?.some((f) => f.id === folderId) ?? false; Try / catch
try {
const { data } = await fileManagerService.removeFile({ ids: [folderId] });
if (data.code !== 0) throw new Error(data.message);
} catch (e) {
if (!(await folderStillExists(parent, folderId))) return true; // already gone
throw e;
} Prevention
- Disable the delete button while a deletion is in flight (double-submit guard)
- Always refetch the skills list after delete attempts, success or failure
When it happens
Trigger: The folder was deleted by another session between resolution and removeFile (stale targetFolderId); the folder contains files the backend refuses to cascade-delete; permission loss; file-manager service error. Note the index-cleanup loop before it swallows its own errors, so index state may already be partially modified when this fires.
Common situations: Double-delete from two tabs. Shared space where another user reorganized folders. Backend restart between steps. Folder containing large uploads timing out server-side.
Related errors
- Failed to list skills folder
- Skill not found
- Skills space not found
- Failed to create skill folder
- Failed to get skill folder ID
AI-assisted analysis of infiniflow/ragflow@554fb1133a (2026-08-15).
Data as JSON: /api/errors/c1804d4fa9cd20eb.
Report an issue: GitHub.