Repository navigation
Description
Summary
On the guardrails team plugin currently present in local branch fork/pr-20963-notifications (6c8eefa73), read-only bash commands that include stderr/stdout fd redirection such as 2>&1 are classified as mutating bash.
That causes the parallel-implementation gate to block purely investigative child tasks and push them back into team fan-out again. In practice this creates deep queued subagent trees and apparent hangs instead of running the original read-only commands.
This is distinct from #32. In this repro the parent team run already has 5 tasks, so the problem is not the minimum-2-tasks rule.
Concrete evidence
1. Mutation classifier marks any > as mutating
packages/guardrails/profile/plugins/team.ts
function mut(cmd: string) {
return [
/\brm\b/i,
/\bmv\b/i,
/\bcp\b/i,
/\bchmod\b/i,
/\bchown\b/i,
/\btouch\b/i,
/\btruncate\b/i,
/\btee\b/i,
/\bsed\s+-i\b/i,
/\bperl\s+-pi\b/i,
/>/,
/\bgit\s+(apply|am|merge|rebase|cherry-pick|checkout\s+--|reset\s+--hard)\b/i,
].some((item) => item.test(cmd))
}The bare />/ means fd-only redirections like 2>&1 are treated the same as actual file writes.
2. The gate blocks any bash command classified by mut()
Same file:
if (item.tool === \"bash\" && !mut(String(out.args.command ?? \"\"))) return
throw new Error(
`Parallel implementation is enforced for this turn. Call the team tool before mutating the worktree. Reason: ${gate.reason}`,
)3. Real run used read-only gcloud / bq commands with 2>&1
Recorded team run:
BenevolentDirector/.opencode/guardrails/team-runs/65792c68-c4f7-4a1c-acbb-38a9e91c85b3.json
Examples:
gcloud config list core/project --format=json 2>&1gcloud sql instances list --format=json 2>&1bq ls --format=json 2>&1gcloud storage buckets list --format=json 2>&1
These prompts are explicitly labeled read-only in the stored task descriptions.
4. Session logs show the exact 2>&1 commands being permission-gated, then more child sessions being created immediately after
BenevolentDirector/.opencode/guardrails/events.jsonl
Observed sequence:
- first-level child attempts plain read-only bash (
gcloud ...,bq ls) - next child attempts the exact
--format=json 2>&1commands - more sessions are then created right after those blocked requests
This matches the UI symptom of queued subagents and repeated re-decomposition.
5. Recursive fan-out is a predictable consequence of current agent guidance
Official docs say the built-in general subagent is for multiple units of work in parallel:
packages/web/src/content/docs/agents.mdx
So once a read-only child task is incorrectly told its bash is mutating, it is unsurprising that the model tries to parallelize again instead of executing the command directly.
Why this matters
The bug makes the team guardrail much more disruptive than intended:
- read-only investigations can look like they are hanging for a long time
- child sessions keep spawning additional work instead of executing the original bash
- operator trust drops because the UI shows many queued workers with little forward progress
- the issue is triggered by a very common shell idiom (
2>&1)
Expected behavior
- fd-only redirections such as
2>&1or1>&2should not be treated as worktree mutation - null-device redirections such as
>/dev/null 2>&1should also not be treated as file mutation - read-only
teamsubtasks should be able to run their bash directly without re-entering theteamgate
Actual behavior
- any
>in the command string makesmut()return true - the guardrail then blocks the bash call as if it would mutate the worktree
- the model responds by invoking more
team/ subagent fan-out, creating queued trees and long stalls
Suggested fix
Prefer a real redirection classifier instead of the bare />/ heuristic.
Minimum safe fix:
- stop treating
2>&1,1>&2, and/dev/nullredirections as mutation - only classify output redirection as mutating when it targets an actual filesystem path in the worktree
Better fix:
- reuse or adapt the existing bash parsing approach already present in
packages/opencode/src/tool/bash.tsinstead of regex-only command matching
Regression coverage to add
Add a scenario test for the team plugin where:
- a broad request activates the parallel gate
- a child task runs a read-only bash command with
2>&1 tool.execute.beforedoes not reject it as mutation- no second-level
teamfan-out is required just to execute that read-only command
Related but not duplicate
- bug: team tool enforces minimum 2 tasks on single-task investigation requests #32 is about
team requires at least two taskson single-task investigations - this bug reproduces even when the parent run already has multiple tasks and only fails because
2>&1is misclassified
Activity