Skip to content

Support command piping and chaining via split-and-spawn #232

@esengine

Description

@esengine

What

run_command currently rejects any input containing |, &&, ||, ;, >, 2>&1, etc. — see src/tools/shell.ts:225-230. Models routinely try seq 10 | grep 5 or cmd1 && cmd2 and have to re-plan after the rejection. Discussion #231 raised this.

I want to support the four most common chain operators by parsing them ourselves and spawning each segment as a separate process — without invoking any system shell.

Why not just invoke a shell

Cross-shell semantics make shell:true a non-starter on Windows:

Operator bash/zsh PowerShell 5.1 PowerShell 7+ cmd.exe
| bytes objects, not bytes objects bytes
&& || yes parse error yes (v7.0+) yes
; yes yes yes no
2>&1 merge wraps stderr as ErrorRecord on native exes same wrapping merge

PS 5.1 is the default shell on Windows 10/11. Letting it parse cmd1 && cmd2 would fail outright. Splitting and spawning ourselves sidesteps all of this.

Proposed approach

Phase 1 — support |, &&, ||, ;:

  1. Use shell-quote's parse() to split the input into segments + ops. (Active maintenance, POSIX, no fork, ~10 KB.)
  2. For each segment, run the existing isAllowed() check independently. Side effect: granular permissions — git status | grep main passes if both segments are individually allowed, which is exactly what Why not allow command piping and stderr redirection? #231 asked for.
  3. Spawn each segment via the existing prepareSpawn() path (still shell:false, keeps .cmd/.bat + PATHEXT + PowerShell UTF-8 handling). Wire stdout→stdin for \|; for &&/\|\|/; chain by exit code.
  4. Reuse existing timeout / abort / byte-buffer / smartDecodeOutput logic at the chain level.

Phase 2 (later) — per-segment redirects: > file, >> file, 2>&1, implemented with fs.createWriteStream and stream piping. No shell.

Out of scope — reject with clear error:

  • <() process substitution
  • $(…) command substitution
  • heredocs
  • * glob expansion (let grep/find/rg do their own)
  • background &
  • PowerShell-specific syntax (\$env:FOO, @'…'@)

Known compromises

  • findstr/where/type on Windows are cmd built-ins, not real binaries — they won't resolve. Doc fix: recommend rg/grep. Affects models that emit cmd.exe-style commands.
  • Glob expansion not done. Most tools the model uses already glob themselves.

Tests

  • Extend tests/shell-tools.test.ts: chain parsing, per-segment allowlist, exit-code chaining for &&/||, stream piping for |, mixed chains, ordering of ;.
  • Reject path tests for everything in "out of scope".
  • Cross-platform: keep Windows runner if there is one; otherwise mark Windows-specific behavior with a process.platform === "win32" guard in tests.

Non-goals

  • no shell invocation
  • no full bash AST
  • no behavior change to single-command path

Tracks discussion #231.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions