Bash tool: "wc" via command substitution fails with "command not found" inside a for-loop with 2+ items

Status Open
Reported on v2.1.227
Maintainer reply ✓ Yes — bcherny
Activity 3 comments · opened Aug 13, 2026
💡 Likely answer: A maintainer (bcherny, collaborator) responded on this thread — see the highlighted reply below.

Description

Calling wc via command substitution ($(... | wc -l)) inside a for loop with 2 or more items fails with command not found: wc, on the very first iteration — not something that appears only after repeated calls.

grep -c "" in the exact same shape works fine, so this isn't a generic loop issue — it's specific to wc.

Minimal reproduction

for n in 1 2; do y=$(echo "test" | wc -l); echo "$y"; done

Expected: prints 1 twice.
Actual:

(eval):1: command not found: wc
1: 
(eval):1: command not found: wc
2: 

A single-item loop (or no loop at all) with the identical command works correctly:

for n in 1; do y=$(echo "test" | wc -l); echo "$y"; done
# → 1: 1   (works)

type wc resolves correctly to /usr/bin/wc both inside and outside the loop.

What I ruled out

  • A third-party PreToolUse hook (RTK, rtk-ai/rtk) — directly simulated rtk hook claude against the exact failing command via its documented stdin JSON contract; it produced no rewrite at all (empty stdout, exit 0), both for this minimal case and for the original real-world case that surfaced it (a find | wc -l count inside a repo-survey loop).
  • The find shell function that Claude Code injects into shell snapshots (find() { ... exec -a bfs "$_cc_bin" ... }, shelling out to the Claude binary itself for bfs-style semantics) — bypassing it with command find did not fix anything, and find alone across 2 iterations works fine on its own, so it's unrelated.
  • Any wc-specific entry in my own Bash permission settings (allow/deny lists) — none exist.

This points at something in the Bash tool's own sandboxing/permission-parsing layer mishandling wc specifically when it appears inside a multi-iteration loop body, but I don't have visibility into that layer to go further.

Environment

  • Claude Code version: 2.1.227 (from the running process path; a separately-installed claude CLI on PATH reports a different, older 2.1.112 — worth noting in case that's relevant)
  • OS: macOS 26.6 (build 25G72), arm64
  • Shell: zsh 5.9 (/bin/zsh)

Impact

Silent, misleading failure — the loop continues (doesn't crash), so the wrong/empty result can go unnoticed rather than erroring loudly. Workaround is straightforward (grep -c "" instead of wc -l, or shell out to Python for counting inside loops), but the underlying behavior is surprising and cost real debugging time to trace.

View original on GitHub ↗

3 Comments

bcherny collaborator · 15 days ago

Could not reproduce on v2.1.233 (macOS, arm64, zsh 5.9).

Steps tried:

  1. claude in a fresh empty project (also claude -p, and with the Bash sandbox enabled via "sandbox": {"enabled": true} in .claude/settings.json).
  2. Asked Claude to run via the Bash tool: for n in 1 2; do y=$(echo "test" | wc -l); echo "$n: $y"; done
  3. Also tried the find ... | wc -l shape: for d in . ..; do c=$(find "$d" -maxdepth 1 -type f | wc -l); echo "$d: $c"; done

Observed (every variant):

1:        1
2:        1

Expected: the same, so no failure seen. wc resolved fine on both iterations, in interactive mode, -p mode, and with the sandbox on.

Assessment: If this happens, it's a genuine bug (wc should work anywhere in a Bash tool command), but Claude Code doesn't treat wc specially, and it doesn't rewrite loop bodies. The (eval):1: prefix on the error is normal for every Bash tool command and isn't a clue on its own. Because Claude Code replays your zsh startup files (functions, aliases, options, PATH) into the tool shell, this looks environment-specific. To narrow it down, could you:

  • Run type -a wc; alias wc; which -a wc in a normal terminal and paste the output.
  • Try claude with a clean shell config (ZDOTDIR pointing at an empty directory) and check whether the loop works.
  • Temporarily disable the RTK PreToolUse hook and retry inside a real session (rather than simulating the hook via stdin).
  • Share a /share link from a session where it fails.

🤖 Generated with Claude Code

github-actions[bot] · 15 days ago

We weren't able to reproduce this. Could you provide steps to trigger the issue — what you ran, what happened, and what you expected? This issue will be closed automatically if there's no activity within 7 days.

bcherny collaborator · 13 days ago

Thanks for the detailed report and the minimal repro. I tried to reproduce this on 2.1.233 (Linux, zsh 5.9, default settings, and also on 2.1.229 to match your version): the exact command for n in 1 2; do y=$(echo "test" | wc -l); echo "$n: $y"; done prints 1: 1 / 2: 1 correctly, type wc resolves normally, and there's no wc shell function inside the Bash tool. I couldn't exercise the macOS sandbox path from this environment, and that's the leading suspect here — the (eval):1: error prefix matches how sandboxed commands run.

To pin this down, could you share:

  1. Do you have sandboxing enabled (check /sandbox or "sandbox": {"enabled": true} in settings)? If so, does the loop work with sandboxing temporarily disabled?
  2. The output of the control: for n in 1 2; do y=$(echo test | command wc -l); echo $y; done
  3. Whether it still happens on the latest release (claude update, currently 2.1.233), plus your macOS version.
  4. If possible, a /share link from a session that hits the error.

Not reproducible on Linux so far, so this looks macOS-specific — likely in the sandboxed execution path rather than wc itself.

🤖 Generated with Claude Code