From 73560dbf66a7889b8884628af98a8323bbde3a0a Mon Sep 17 00:00:00 2001 From: dj-oyu <68707227+dj-oyu@users.noreply.github.com> Date: Wed, 25 Feb 2026 16:49:11 +0900 Subject: [PATCH] fix(sandbox): create per-subagent ExecTool to avoid leaking allowPatterns The shared ExecTool instance (sm.execTool) was mutated by SetAllowPatterns when spawning subagents, which leaked sandbox restrictions to the conductor. Basic commands like pwd and ls were blocked on the main agent after any subagent spawned with a preset. Create a new ExecTool per subagent instead of sharing a single instance. Remove the now-unused execTool field from SubagentManager. Co-Authored-By: Claude Opus 4.6 --- pkg/tools/subagent.go | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/pkg/tools/subagent.go b/pkg/tools/subagent.go index 41542791c..33938c200 100644 --- a/pkg/tools/subagent.go +++ b/pkg/tools/subagent.go @@ -3,7 +3,6 @@ package tools import ( "context" "fmt" - "log" "sync" "time" @@ -33,7 +32,6 @@ type SubagentManager struct { workspace string tools *ToolRegistry webSearchOpts WebSearchToolOptions - execTool *ExecTool // Shared exec tool for all presets maxIterations int maxTokens int temperature float64 @@ -53,11 +51,6 @@ func NewSubagentManager( if reporter == nil { reporter = orch.Noop } - // Create a shared exec tool for all presets - execTool, err := NewExecTool(workspace, true) - if err != nil { - log.Printf("subagent: failed to create exec tool: %v (exec disabled for subagents)", err) - } return &SubagentManager{ tasks: make(map[string]*SubagentTask), provider: provider, @@ -66,7 +59,6 @@ func NewSubagentManager( workspace: workspace, tools: NewToolRegistry(), webSearchOpts: webSearchOpts, - execTool: execTool, maxIterations: 10, nextID: 1, reporter: reporter, @@ -306,10 +298,19 @@ func (sm *SubagentManager) buildPresetRegistry(preset Preset, writeRoot string) registry.Register(NewAppendFileTool(writeRoot, true)) } - // Register exec and bg_monitor if allowed + // Register exec and bg_monitor if allowed. + // Each subagent gets its own ExecTool to avoid mutating the shared instance's + // allowPatterns (which would leak sandbox restrictions to the conductor). if config.AllowedTools["exec"] { - // Use the shared exec tool but set allow patterns - execTool := sm.execTool + execWorkDir := writeRoot + if execWorkDir == "" { + execWorkDir = sm.workspace + } + execTool, err := NewExecTool(execWorkDir, true) + if err != nil { + // exec disabled for this subagent; skip registration + return registry + } if config.ExecPolicy != nil { _ = execTool.SetAllowPatterns([]string{config.ExecPolicy.AllowPattern}) execTool.SetLocalNetOnly(config.ExecPolicy.LocalNetOnly)