{"record":{"id":"c4b42e89f17b5573","repo":"infiniflow/ragflow","slug":"skill-not-found","errorCode":null,"errorMessage":"Skill not found","messagePattern":"Skill not found","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/pages/skills/hooks.ts","lineNumber":1043,"sourceCode":"          );\n          if (spaceFolderId) {\n            const { data: listData } = await fileManagerService.listFile({\n              parent_id: spaceFolderId,\n            });\n\n            if (listData.code === 0) {\n              const skillFolder = (listData.data?.files || []).find(\n                (f: any) => f.type === 'folder' && f.name === skillId,\n              );\n              if (skillFolder) {\n                targetFolderId = skillFolder.id;\n              }\n            }\n          }\n        }\n\n        if (!targetFolderId) {\n          throw new Error('Skill not found');\n        }\n\n        // Get versions by listing the skill folder\n        const { data: versionData } = await fileManagerService.listFile({\n          parent_id: targetFolderId,\n        });\n\n        let versionsToDelete: string[] = ['latest'];\n        if (versionData.code === 0) {\n          const versionFolders = (versionData.data?.files || []).filter(\n            (f: any) => f.type === 'folder' && /^\\d+\\.\\d+\\.\\d+/.test(f.name),\n          );\n          if (versionFolders.length > 0) {\n            versionsToDelete = versionFolders.map((f: any) => f.name);\n          }\n        }\n\n        // Delete search index for all versions","sourceCodeStart":1025,"sourceCodeEnd":1061,"githubUrl":"https://github.com/infiniflow/ragflow/blob/554fb1133ac3861732235ad9c377eb5e0a770665/web/src/pages/skills/hooks.ts#L1025-L1061","documentation":"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'.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nif (!targetFolderId) {\n  throw new Error('Skill not found');\n}\n\n// after\nif (!targetFolderId) {\n  const normalized = skillId.replace(/\\s+/g, '-').toLowerCase();\n  if (normalized !== skillId) {\n    const skillFolder = (listData.data?.files || []).find(\n      (f: any) => f.type === 'folder' && f.name === normalized,\n    );\n    if (skillFolder) targetFolderId = skillFolder.id;\n  }\n}\nif (!targetFolderId) {\n  throw new Error(\n    `Skill not found (skillId=${skillId}, space=${normalizedSpaceName})`,\n  );\n}","handlingStrategy":"type-guard","validationCode":"const skillFolderIdFor = (skills: Skill[], skillId: string) =>\n  skills.find((s) => s.id === skillId)?.['_folderId'] ?? null;","typeGuard":"const hasFolderRef = (s: any): s is Skill & { _folderId: string } =>\n  typeof s?._folderId === 'string' && s._folderId.length > 0;","tryCatchPattern":"try {\n  await deleteSkill(...);\n} catch (e) {\n  if (e.message === 'Skill not found') await fetchSkills(spaceName, spaceId); // resync\n  else throw e;\n}","preventionTips":["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"],"tags":["skills","file-manager","delete","data-sync"],"backgroundTag":null,"analyzedSha":"554fb1133ac3861732235ad9c377eb5e0a770665","analyzedAt":"2026-08-15T09:20:16.380Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}