From 28544cfe5da90e97474c42244ad8796e8029eed0 Mon Sep 17 00:00:00 2001 From: weichen Date: Thu, 15 Oct 2026 14:43:13 +0000 Subject: [PATCH] Execute the managed interpreter through its real path Why: the sandbox hides /home and only remounts the resolved Python prefix, so executing python/current/bin/python3 failed before the base layer could be imported. Evidence: go test ./internal/tools/ -run TestResolveInterpreterFollowsManagedSymlink passed, and env doctor against the built base layer printed doctor ok (SELF). Impact: run_python and env doctor follow the symlink into the mounted prefix or /usr/bin/python3. The layer and the prefix mount stay the same. Refs: Agent-System-Backend PR 207 Co-authored-by: Cursor --- cmd/agent-system/env.go | 4 ++++ internal/tools/exec_interpreter.go | 8 +++++++- internal/tools/exec_interpreter_test.go | 22 ++++++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/cmd/agent-system/env.go b/cmd/agent-system/env.go index 818d51d..845169b 100644 --- a/cmd/agent-system/env.go +++ b/cmd/agent-system/env.go @@ -6,6 +6,7 @@ import ( "flag" "fmt" "os" + "path/filepath" "time" "agent-system-backend/internal/execenv" @@ -137,6 +138,9 @@ func runDoctor(ctx context.Context, root *execenv.Root, py string, sandbox bool) if prefix := root.PythonPrefix(); prefix != "" { mounts = append(mounts, prefix) } + if resolved, err := filepath.EvalSymlinks(py); err == nil { + py = resolved + } if err := execenv.Doctor(ctx, py, pyPath, nil, sandbox, mounts); err != nil { fmt.Fprintln(os.Stderr, "env doctor:", err) return 1 diff --git a/internal/tools/exec_interpreter.go b/internal/tools/exec_interpreter.go index 55f2917..d44aa9f 100644 --- a/internal/tools/exec_interpreter.go +++ b/internal/tools/exec_interpreter.go @@ -203,7 +203,13 @@ func resolveInterpreter(name string) (string, error) { case "python3": if execPythonBin != "" { if st, err := os.Stat(execPythonBin); err == nil && !st.IsDir() { - return execPythonBin, nil + // current/bin/python3 是符号链接。沙箱用 tmpfs 盖住 /home 后只挂真实前缀, + // 执行未解析的 current 路径会找不到文件。 + resolved, err := filepath.EvalSymlinks(execPythonBin) + if err != nil { + return "", err + } + return resolved, nil } return "", fmt.Errorf("python3: configured interpreter missing: %s", execPythonBin) } diff --git a/internal/tools/exec_interpreter_test.go b/internal/tools/exec_interpreter_test.go index da0aa29..90b4364 100644 --- a/internal/tools/exec_interpreter_test.go +++ b/internal/tools/exec_interpreter_test.go @@ -213,6 +213,28 @@ func TestExecEnvIncludesSkillsRoot(t *testing.T) { } } +func TestResolveInterpreterFollowsManagedSymlink(t *testing.T) { + prev := execPythonBin + t.Cleanup(func() { execPythonBin = prev }) + dir := t.TempDir() + link := filepath.Join(dir, "python3") + if err := os.Symlink("/usr/bin/python3", link); err != nil { + t.Fatal(err) + } + execPythonBin = link + got, err := resolveInterpreter("python3") + if err != nil { + t.Fatal(err) + } + want, err := filepath.EvalSymlinks("/usr/bin/python3") + if err != nil { + t.Fatal(err) + } + if got != want { + t.Fatalf("resolved %q, want %q", got, want) + } +} + func TestInterpreterExecConfigForKernel(t *testing.T) { cfg := InterpreterExecConfig() if cfg == nil {