Tencent/WeKnora · error

this tool is not bound to a skill directory

Error message

this tool is not bound to a skill directory

What it means

resolveSkillFilePath rejects a skillDir that is empty, ".", or "/" after Clean+TrimSpace, because the skill-file tool must be bound to a concrete skill installation directory to safely scope file writes. Returning this error means the tool instance was constructed without a valid skill directory binding, not that the requested path itself is bad.

Source

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

}

// Cleanup releases any resources.
func (t *EditSkillFileTool) Cleanup(ctx context.Context) error {
	return nil
}

// resolveSkillFilePath turns a model-supplied path into an absolute path
// proven to sit inside skillDir.
//
// A relative path is resolved against skillDir, which is what the model
// reaches for after being told the directory once. Everything is then cleaned
// and re-checked against the prefix, so "..", a symlink-looking spelling or an
// absolute path into a neighbouring skill all fail here rather than reaching
// the image. The directory itself is refused: it is not a file.
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,

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Construct/configure the tool with the concrete installed skill directory path.
  2. Fail earlier at setup if the skill directory cannot be resolved instead of registering the tool.
  3. In tests, point the tool at a temporary fixture skill directory.

Example fix

// before
tool := tools.NewSkillFileTool("") // unbound
// after
skillDir := filepath.Join(cfg.SkillsRoot, install.Name)
if skillDir == "" {
    return errors.New("cannot register skill file tool: no install dir")
}
tool := tools.NewSkillFileTool(skillDir)
Defensive patterns

Strategy: type-guard

Validate before calling

d := path.Clean(strings.TrimSpace(skillDir))
if d == "" || d == "." || d == "/" {
    return errors.New("skill file tool requires a concrete skill directory")
}

Type guard

func isBoundToSkillDir(dir string) bool {
    d := path.Clean(strings.TrimSpace(dir))
    return d != "" && d != "." && d != "/"
}

Try / catch

out, err := tool.Execute(ctx, input)
if err != nil && strings.Contains(err.Error(), "not bound to a skill directory") {
    // resolve the install dir, rebind the tool, then retry once
}

Prevention

When it happens

Trigger: Calling Execute on a skill-file tool whose skillDir binding is empty/missing, or explicitly "." or "/" — e.g. the tool was created before the skill install directory was resolved.

Common situations: Tool constructed with a zero-value config; skill installation lookup failed earlier and an empty dir was propagated; tests instantiating the tool without a fixture directory.

Related errors


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