Bash tool (fish): block constructs (`for…end`, `if…end`) fail inline with `parse error near 'end'` though valid in fish
Summary
When the Bash tool runs under fish, an inline command containing a fish block construct (verified for all of for…end, if…end, while…end, switch…end, begin…end) fails at runtime with:
(eval):1: parse error near `end'
…even though the exact same text is valid fish — it runs via fish -c, passes fish -n (parse check), runs as a sourced script, and runs through fish's own eval builtin. Plain multi-statement inline commands (a; b; c, pipes, &&, ||, ; and) work fine — only …end blocks break. So a command that is valid in the user's actual shell fails when routed through the tool.
Environment
- Claude Code: 2.1.169
- Shell: fish 4.7.1 (Bash tool executing via fish)
- macOS: 26.5.1 (25F80), arm64
Reproduction
With fish as the shell the Bash tool uses, have it run this as a single Bash tool call:
for f in a b; echo $f; end
Actual result:
(eval):1: parse error near `end'
Verified across all five block types (each as its own single Bash tool call):
| Inline command | Result |
|---|---|
| for f in a b; echo $f; end | ❌ parse error near 'end' |
| if true; echo hi; end | ❌ parse error near 'end' |
| while false; echo x; end | ❌ parse error near 'end' |
| begin; echo hi; end | ❌ parse error near 'end' |
| switch foo; case foo; echo hi; end | ❌ parse error near 'echo' |
Note the switch case fails near echo, not end — i.e. at the first fragment that becomes invalid once the block is split. That's consistent with the hypothesis below (the command is split on ; rather than passed whole).
The same text is valid fish — it works every other way
| Invocation | Result |
|---|---|
| Inline Bash tool call | ❌ (eval):1: parse error near 'end' |
| fish -c 'for f in a b; echo $f; end' | ✅ works |
| fish -n parse-check of the same text | ✅ exit 0 (no parse error) |
| Sourced as a .fish script file | ✅ works |
| fish eval 'for f in a b; echo $f; end' (builtin, quoted) | ✅ works |
So fish itself has no problem with the construct; the failure is in how the tool hands the command to the shell.
Expected
A valid fish command — including block constructs — should execute the same through the Bash tool as it does in an interactive fish session / fish -c.
Likely cause (hypothesis — I can't see the internals)
The (eval):1: prefix suggests the command is run through an eval-style wrapper, and the command does not appear to be handed to fish as a single quoted string. If a block is eval'd unquoted, eval consumes only up to the first ; and the trailing end is left with no matching opener → "parse error near end". Passing the command as one quoted string (or via fish -c -- "$cmd") would likely fix it. The reproduction above is the solid part; this paragraph is inference.
Impact / workarounds
Low severity but misleading: it reads like a syntax error in the user's command or shell (it initially fooled me into blaming fish), when the text is valid. Easy workarounds exist — wrap in fish -c '…', or put the block in a script file and run that — so it doesn't block anyone, but it's a papercut for fish users and for agents that emit inline loops.
Related (not duplicates)
- #62418 — Bash tool "internal error" on
git commitunder fish + multi-step pre-commit hooks (different trigger) - #25779 — SSH + fish, empty PID from
$!(different) - #7490 — feature request to configure the Bash tool's shell (related context)
---
<sub>Diagnosed during a Claude Code session (fish on macOS); reproduction steps above were all run and verified.</sub>