Tencent/WeKnora · error
sandbox: session input %s is read-only
Error message
sandbox: session input %s is read-only
What it means
cleanSessionWorkspaceWritePath rejects any write target under /workspace/input (SessionInputRoot). The input root holds durable user attachments restored read-only into the session; the library throws this to prevent the agent or caller from overwriting user-supplied inputs.
Source
Thrown at internal/sandbox/session_manager.go:1031
)
}
// 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
// ordinary sessions the allowlist is unchanged and its lexical nature is
// pre-existing. The allowlist stops casual wandering and makes intent
// auditable; the real isolation boundary is the remote sandbox itself.
//
// allowSkillsRoot widens it to the skills image root for install/maintenance
// sessions, so the installer agent can set work_dir to the skill directory andView on GitHub (pinned to 988cbb0330)
Solutions
- Write the output elsewhere under /workspace, e.g. /workspace/output/.
- If you need a modified copy of an input, copy it to /workspace and edit the copy.
- Update agent prompts/system instructions to state that /workspace/input is read-only.
Example fix
// before p := "/workspace/input/report.txt" // after p := "/workspace/output/report.txt"
Defensive patterns
Strategy: validation
Validate before calling
func writablePath(p string) bool {
c := filepath.Clean(p)
return strings.HasPrefix(c, "/workspace/") && !strings.HasPrefix(c, "/workspace/input/")
}
if !writablePath(p) { /* redirect output before calling */ } Type guard
func isReadOnlyInputPath(p string) bool {
c := filepath.Clean(p)
return c == "/workspace/input" || strings.HasPrefix(c, "/workspace/input/")
} Try / catch
err := mgr.WriteSessionWorkspaceFile(ctx, s, path, data)
if errors.Is(err, errReadOnly) || strings.Contains(err.Error(), "read-only") {
path = strings.Replace(path, "/workspace/input/", "/workspace/output/", 1)
err = mgr.WriteSessionWorkspaceFile(ctx, s, path, data)
} Prevention
- Treat /workspace/input as immutable; always write results to /workspace/output.
- State the read-only rule in agent system prompts.
- Derive output paths from input paths programmatically instead of reusing them.
When it happens
Trigger: Calling WriteSessionWorkspaceFile with a path like "/workspace/input/notes.txt" — the path passes the /workspace prefix check but fails the read-only input-root check.
Common situations: Agent-generated code that writes results next to where it read attachments; scripts that treat /workspace/input like any other scratch directory; tools that default outputs to the same directory as their inputs.
Related errors
- path %q is outside this install's skill directory (%s); an i
- sandbox: remote docker host %q requires a TLS certificate di
- sandbox: docker network mode %q is not allowed; use "bridge"
- sandbox: workspace write path %q is outside %s
- join request not found
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/087fb04cd5f21311.
Report an issue: GitHub.