Tencent/WeKnora · error

sandbox: work dir is outside allowed roots ( )

Error message

sandbox: work dir %q is outside allowed roots (%s)

What it means

cleanSessionWorkDir keeps shell_exec inside directories the sandbox is willing to let the agent work in. By default only /workspace is allowed; with the skills-install mode the skills image root is added. It throws this when the requested working directory is not under one of those roots.

Solutions

  1. Set the command's work dir to a path under /workspace.
  2. If the work must happen in the skills image root, run in skills-install mode (allowSkillsRoot) so that root is permitted.
  3. Create the needed directory under /workspace and cd there in the command instead.

Example fix

// before
opts.WorkDir = "/opt/tools"
// after
opts.WorkDir = "/workspace/tools"
Defensive patterns

Strategy: validation

Validate before calling

func allowedWorkDir(dir string, allowSkills bool) bool {
    c := filepath.Clean(dir)
    ok := strings.HasPrefix(c, "/workspace/")
    if allowSkills {
        ok = ok || strings.HasPrefix(c, skillsImageRoot+"/")
    }
    return ok
}

Type guard

func inRoot(dir, root string) bool {
    c := filepath.Clean(dir)
    return strings.HasPrefix(c, root+"/")
}

Try / catch

err := exec.ExecShellCommandWithOptions(ctx, s, opts)
if err != nil && strings.Contains(err.Error(), "outside allowed roots") {
    opts.WorkDir = "/workspace"
    err = exec.ExecShellCommandWithOptions(ctx, s, opts)
}

Prevention

When it happens

Trigger: ExecShellCommandWithOptions invoked with WorkDir set to anything outside /workspace (and, when allowSkillsRoot is set, outside the skills image root) — e.g. "/", "/home/user", "/opt/tool". Also reachable via ValidatedSessionOutputDir and the two cleanSessionWorkDir tests.

Common situations: Trying to cd into an installed tool's directory outside the workspace; reusing host-style working directories in remote sessions; assuming skills directories are executable workspaces in ordinary sessions.

Related errors


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

Appendix: source

Thrown at internal/sandbox/session_manager.go:1066

// 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
// run ordinary relative commands instead of composing long absolute paths. It
// is a widening, not a removal: everything outside these two roots is still
// refused.
func cleanSessionWorkDir(workDir string, allowSkillsRoot bool) (string, error) {
	clean := path.Clean(strings.TrimSpace(workDir))
	if clean == SessionWorkspaceRoot || strings.HasPrefix(clean, SessionWorkspaceRoot+"/") {
		return clean, nil
	}
	if allowSkillsRoot &&
		(clean == SkillsImageRoot || strings.HasPrefix(clean, SkillsImageRoot+"/")) {
		return clean, nil
	}
	allowed := SessionWorkspaceRoot
	if allowSkillsRoot {
		allowed = SessionWorkspaceRoot + ", " + SkillsImageRoot
	}
	return "", fmt.Errorf(
		"sandbox: work dir %q is outside allowed roots (%s)",
		workDir, allowed,
	)
}

// buildSessionCreateRequest projects Config into a provider-neutral remote
// create request. The metadata block is populated per-session by the
// lifecycle coordinator; env vars propagate as-is.
//
// The provider parameter (derived from RemoteSandboxClient.Provider()) is the
// authoritative source of identity — it selects the correct Config fields so
// Cube and E2B never read each other's templates or TTLs.
func buildSessionCreateRequest(provider RemoteProvider, cfg *Config) (RemoteCreateRequest, error) {
	envVars := withWorkspaceEnvDefaults(cloneMetadata(cfg.EnvVars))

	switch provider {
	case SandboxTypeCube:
		ttl := cfg.CubeSandboxTTL

View on GitHub (pinned to 988cbb0330)