{"record":{"id":"80e4e347fd115c07","repo":"Tencent/WeKnora","slug":"path-is-required-write-a-file-inside-s","errorCode":null,"errorMessage":"path is required; write a file inside %s","messagePattern":"path is required; write a file inside (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/agent/tools/skill_file.go","lineNumber":395,"sourceCode":"\treturn nil\n}\n\n// resolveSkillFilePath turns a model-supplied path into an absolute path\n// proven to sit inside skillDir.\n//\n// A relative path is resolved against skillDir, which is what the model\n// reaches for after being told the directory once. Everything is then cleaned\n// and re-checked against the prefix, so \"..\", a symlink-looking spelling or an\n// absolute path into a neighbouring skill all fail here rather than reaching\n// the image. The directory itself is refused: it is not a file.\nfunc resolveSkillFilePath(skillDir, requested string) (string, error) {\n\tdir := path.Clean(strings.TrimSpace(skillDir))\n\tif dir == \"\" || dir == \".\" || dir == \"/\" {\n\t\treturn \"\", fmt.Errorf(\"this tool is not bound to a skill directory\")\n\t}\n\ttrimmed := strings.TrimSpace(requested)\n\tif trimmed == \"\" {\n\t\treturn \"\", fmt.Errorf(\"path is required; write a file inside %s\", dir)\n\t}\n\tif strings.ContainsRune(trimmed, 0) {\n\t\treturn \"\", fmt.Errorf(\"path %q is not a valid file path\", requested)\n\t}\n\tcandidate := trimmed\n\tif !path.IsAbs(candidate) {\n\t\tcandidate = path.Join(dir, candidate)\n\t}\n\tclean := path.Clean(candidate)\n\tif clean == dir || !strings.HasPrefix(clean, dir+\"/\") {\n\t\treturn \"\", fmt.Errorf(\n\t\t\t\"path %q is outside this install's skill directory (%s); \"+\n\t\t\t\t\"an install may only write its own skill\",\n\t\t\trequested, dir,\n\t\t)\n\t}\n\treturn clean, nil\n}","sourceCodeStart":377,"sourceCodeEnd":413,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/agent/tools/skill_file.go#L377-L413","documentation":"resolveSkillFilePath requires a non-empty requested path; when it is empty or whitespace-only it returns \"path is required\" with the bound skill directory embedded so the model/caller knows where files must be written. It is the first check on the requested path after validating the skill directory itself.","triggerScenarios":"Calling Execute (write-file operation) with an empty or blank requested path argument — e.g. the model's tool call omitted the path key or passed \"\" / \"   \".","commonSituations":"Model emits a write call with only content but no path; programmatic callers forget the path field; template prompts that leave the path placeholder unfilled.","solutions":["Pass a non-empty relative path (inside the skill directory) in the tool call.","Validate the path argument exists before invoking Execute.","Re-prompt the model to include the target file path when the argument is blank."],"exampleFix":"// before\nin := SkillFileInput{Content: \"# Readme\"} // path missing\n// after\nin := SkillFileInput{Path: \"README.md\", Content: \"# Readme\"}","handlingStrategy":"validation","validationCode":"if strings.TrimSpace(input.Path) == \"\" {\n    return fmt.Errorf(\"a file path inside %s is required\", skillDir)\n}","typeGuard":"func hasPath(in SkillFileInput) bool { return strings.TrimSpace(in.Path) != \"\" }","tryCatchPattern":"if _, err := tool.Execute(ctx, input); err != nil && strings.Contains(err.Error(), \"path is required\") {\n    // re-request the path from the model or supply a default filename\n}","preventionTips":["Make the path field required in the tool schema","Check for a non-blank path before dispatching write operations","Re-prompt the model when the path argument is missing or blank"],"tags":["validation","path-safety","skill"],"backgroundTag":"missing-required-argument","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}