Tencent/WeKnora · error

path %q is outside this install's skill directory (%s); an i

Error message

path %q is outside this install's skill directory (%s); an install may only write its own skill

What it means

resolveSkillFilePath performs a containment check: after joining and Clean-ing the requested path, it must live strictly inside the bound skill directory (not equal to it, not outside its prefix). Otherwise the write could escape the skill installation — including via "..", absolute paths, or symlink-like spellings — so the tool refuses with this error, protecting the principle that an install may only write its own skill.

Source

Thrown at internal/agent/tools/skill_file.go:406

func resolveSkillFilePath(skillDir, requested string) (string, error) {
	dir := path.Clean(strings.TrimSpace(skillDir))
	if dir == "" || dir == "." || dir == "/" {
		return "", fmt.Errorf("this tool is not bound to a skill directory")
	}
	trimmed := strings.TrimSpace(requested)
	if trimmed == "" {
		return "", fmt.Errorf("path is required; write a file inside %s", dir)
	}
	if strings.ContainsRune(trimmed, 0) {
		return "", fmt.Errorf("path %q is not a valid file path", requested)
	}
	candidate := trimmed
	if !path.IsAbs(candidate) {
		candidate = path.Join(dir, candidate)
	}
	clean := path.Clean(candidate)
	if clean == dir || !strings.HasPrefix(clean, dir+"/") {
		return "", fmt.Errorf(
			"path %q is outside this install's skill directory (%s); "+
				"an install may only write its own skill",
			requested, dir,
		)
	}
	return clean, nil
}

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Pass a path relative to the skill directory that stays inside it (e.g. "docs/readme.md").
  2. Normalize user/model input (filepath.Clean) and verify the prefix yourself before the call.
  3. If writing to another skill is genuinely needed, use the tool bound to that skill's directory instead.

Example fix

// before
in := SkillFileInput{Path: "../other-skill/file.txt", Content: data}
// after
p := path.Clean("../other-skill/file.txt")
if !strings.HasPrefix(p, skillDir+"/") {
    return fmt.Errorf("refusing to write outside %s", skillDir)
}
in := SkillFileInput{Path: p, Content: data}
Defensive patterns

Strategy: validation

Validate before calling

clean := path.Clean(path.Join(skillDir, requested))
if clean == skillDir || !strings.HasPrefix(clean, skillDir+"/") {
    return fmt.Errorf("path %q escapes skill directory %s", requested, skillDir)
}

Type guard

func insideSkillDir(skillDir, requested string) bool {
    clean := path.Clean(path.Join(skillDir, requested))
    return clean != skillDir && strings.HasPrefix(clean, skillDir+"/")
}

Try / catch

if _, err := tool.Execute(ctx, input); err != nil && strings.Contains(err.Error(), "outside this install's skill directory") {
    // clamp the path inside skillDir or reject the operation; do not retry as-is
}

Prevention

When it happens

Trigger: Calling Execute with a path that resolves outside skillDir: an absolute path into another skill, "../" traversal above the skill root, or the skill directory itself (clean == dir).

Common situations: Model attempts to write ../../other-skill/file.txt; caller passes an absolute path from a different install; traversal probing in multi-tenant skill setups.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/672178ba6cba1d7c. Report an issue: GitHub.