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. ForView on GitHub (pinned to 988cbb0330)
Solutions
- Append a file name to the workspace path before writing
- 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.