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 and

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Write the output elsewhere under /workspace, e.g. /workspace/output/.
  2. If you need a modified copy of an input, copy it to /workspace and edit the copy.
  3. 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

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


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