{"record":{"id":"5ca3592a3167d6e2","repo":"flipped-aurora/gin-vue-admin","slug":"error-5ca359","errorCode":null,"errorMessage":"工具类型不能为空","messagePattern":"工具类型不能为空","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/service/system/sys_skills.go","lineNumber":168,"sourceCode":"\t\tfor _, tool := range req.SyncTools {\n\t\t\tif tool == req.Tool {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ttargetDir, err := s.ensureSkillDir(tool, req.Skill)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := copySkillDir(skillDir, targetDir); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *SkillsService) Delete(_ context.Context, req request.SkillDeleteRequest) error {\n\tif strings.TrimSpace(req.Tool) == \"\" {\n\t\treturn errors.New(\"工具类型不能为空\")\n\t}\n\tif !isSafeName(req.Skill) {\n\t\treturn errors.New(\"技能名称不合法\")\n\t}\n\tskillDir, err := s.skillDir(req.Tool, req.Skill)\n\tif err != nil {\n\t\treturn err\n\t}\n\tinfo, err := os.Stat(skillDir)\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn errors.New(\"技能不存在\")\n\t\t}\n\t\treturn err\n\t}\n\tif !info.IsDir() {\n\t\treturn errors.New(\"技能目录异常\")\n\t}","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/service/system/sys_skills.go#L150-L186","documentation":"SkillsService.Delete first requires a non-empty Tool field (trimmed). The tool type selects the skills root directory, so without it the target location is ambiguous and the request is rejected with this error.","triggerScenarios":"Calling Delete with request.SkillDeleteRequest where Tool is missing or only whitespace (e.g. empty string or spaces).","commonSituations":"Frontend deletes a skill after the tool selector was cleared; API calls constructed with only the skill name; tool value lost when serializing the request payload.","solutions":["Include the tool type (e.g. 'claude', 'codex') in the delete request.","On the client, disable the delete action until a tool is selected.","Verify the JSON payload field name matches request.SkillDeleteRequest's Tool binding."],"exampleFix":"// before\ndeleteReq := request.SkillDeleteRequest{Skill: \"my-skill\"}\n// after\ndeleteReq := request.SkillDeleteRequest{Tool: \"claude\", Skill: \"my-skill\"}","handlingStrategy":"validation","validationCode":"if (!req.Tool || !req.Tool.trim()) {\n  throw new Error(\"tool is required\")\n}\nawait api.deleteSkill(req)","typeGuard":"func hasTool(req request.SkillDeleteRequest) bool {\n\treturn strings.TrimSpace(req.Tool) != \"\"\n}","tryCatchPattern":"try {\n  await api.deleteSkill(req)\n} catch (e) {\n  if (e.response?.data?.msg === \"工具类型不能为空\") {\n    notify(\"请先选择工具类型\")\n  }\n}","preventionTips":["Keep the tool selector required in delete dialogs.","Serialize the full request object so Tool is not dropped.","Assert required fields in API client wrappers."],"tags":["validation","missing-parameter","request-binding"],"backgroundTag":"missing-required-parameter","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}