{"record":{"id":"86f6563fc7ea40d9","repo":"flipped-aurora/gin-vue-admin","slug":"error-86f656","errorCode":null,"errorMessage":"工具类型不支持","messagePattern":"工具类型不支持","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/service/system/sys_skills.go","lineNumber":521,"sourceCode":"\t\t\trc.Close()\n\t\t\treturn err\n\t\t}\n\n\t\t_, err = io.Copy(out, rc)\n\t\trc.Close()\n\t\tout.Close()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (s *SkillsService) toolSkillsDir(tool string) (string, error) {\n\ttoolDir, ok := skillToolDirs[tool]\n\tif !ok {\n\t\treturn \"\", errors.New(\"工具类型不支持\")\n\t}\n\troot := strings.TrimSpace(global.GVA_CONFIG.AutoCode.Root)\n\tif root == \"\" {\n\t\troot = \".\"\n\t}\n\tskillsDir := filepath.Join(root, toolDir, \"skills\")\n\tif err := os.MkdirAll(skillsDir, os.ModePerm); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn skillsDir, nil\n}\n\nfunc (s *SkillsService) skillDir(tool, skill string) (string, error) {\n\tskillsDir, err := s.toolSkillsDir(tool)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn filepath.Join(skillsDir, skill), nil","sourceCodeStart":503,"sourceCodeEnd":539,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/service/system/sys_skills.go#L503-L539","documentation":"toolSkillsDir maps a tool key to its config directory via skillToolDirs (copilot, claude, cursor, trae, codex). Any other value returns \"工具类型不支持\". It is the entry gate for almost every skill operation, so a bad tool key fails List/Tools/Get/Download/SkillDir calls.","triggerScenarios":"Calling any skills API (Tools, List, GetGlobalConstraint, DownloadOnlineSkill, Save, CreateScript, ...) with tool not exactly one of \"copilot\",\"claude\",\"cursor\",\"trae\",\"codex\" — e.g. \"vscode\", \"Claude\" (case mismatch), or empty.","commonSituations":"Frontend sending a display label (\"Claude\") instead of the key; new tool added to the frontend but not to skillToolDirs; typo or different casing; client caching an old tool list.","solutions":["Send the exact lowercase key: copilot, claude, cursor, trae, or codex","Add the new tool to skillToolDirs (and skillToolOrder/skillToolLabels) in sys_skills.go if support is genuinely missing","Normalize/trim and lowercase the tool value before calling the service"],"exampleFix":"// before\nList(ctx, \"VSCode\")\n// after\nList(ctx, \"claude\") // exact key from skillToolDirs","handlingStrategy":"validation","validationCode":"var validTools = map[string]bool{\"copilot\": true, \"claude\": true, \"cursor\": true, \"trae\": true, \"codex\": true}\nif !validTools[tool] { return fmt.Errorf(\"不支持的工具类型: %q\", tool) }","typeGuard":"func isKnownTool(t string) bool {\n    switch t { case \"copilot\", \"claude\", \"cursor\", \"trae\", \"codex\": return true }\n    return false\n}","tryCatchPattern":"if err := svc.List(ctx, tool); err != nil {\n    if err.Error() == \"工具类型不支持\" {\n        return fmt.Errorf(\"工具必须是小写 key 之一 copilot/claude/cursor/trae/codex: %w\", err)\n    }\n    return err\n}","preventionTips":["Always send the lowercase key, never the display label","Derive tool values from the Tools() API rather than hardcoding","When adding a new tool, update skillToolDirs/skillToolOrder/skillToolLabels together"],"tags":["validation","enum","go"],"backgroundTag":"unsupported-enum-value","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}