Tencent/WeKnora · error

sandbox: workspace write path %q is a directory, not a file

Error message

sandbox: workspace write path %q is a directory, not a file

What it means

Validation guard in cleanSessionWorkspaceWritePath: the cleaned workspace write path resolves to a directory root (e.g. the workspace root itself or a '.'-equivalent), so it names a directory rather than a file. Writes must target a concrete file path inside the session workspace.

Source

Thrown at internal/sandbox/session_manager.go:1025

	if clean == SessionInputRoot || strings.HasPrefix(clean, SessionInputRoot+"/") {
		return clean, nil
	}
	return "", fmt.Errorf(
		"sandbox: session input path %q is outside %s",
		filePath, SessionInputRoot,
	)
}

// cleanSessionWorkspaceWritePath keeps model-authored writes inside the
// session workspace and out of the attachment tree. Validation is lexical
// (path.Clean plus prefix checks), matching cleanSessionWorkDir.
func cleanSessionWorkspaceWritePath(filePath string) (string, error) {
	clean := path.Clean(strings.TrimSpace(filePath))
	if !path.IsAbs(clean) || clean == "." || clean == "/" {
		return "", fmt.Errorf("sandbox: workspace write path %q must be an absolute file path", filePath)
	}
	if clean == SessionWorkspaceRoot || clean == SessionOutputRoot || clean == SessionInputRoot {
		return "", fmt.Errorf("sandbox: workspace write path %q is a directory, not a file", filePath)
	}
	if !strings.HasPrefix(clean, SessionWorkspaceRoot+"/") {
		return "", fmt.Errorf("sandbox: workspace write path %q is outside %s", filePath, SessionWorkspaceRoot)
	}
	if strings.HasPrefix(clean, SessionInputRoot+"/") {
		return "", fmt.Errorf("sandbox: session input %s is read-only", SessionInputRoot)
	}
	return clean, nil
}

// cleanSessionWorkDir keeps shell_exec inside directories we are willing to let
// an agent work in. Ordinary sessions get /workspace only.
//
// Validation is lexical (path.Clean plus prefix checks): a symlink under an
// allowed root that resolves elsewhere at execution time is not detected and
// that is intentional. The only caller that passes allowSkillsRoot also passes
// AsRoot and runs arbitrary install shell commands, so a symlink would grant
// nothing those commands cannot already reach via cd or absolute paths. For

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Append a file name to the workspace path before writing
  2. Choose a file path one or more levels below the workspace root
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at internal/sandbox/session_manager.go:1025 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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