Bash tool (fish): block constructs (`for…end`, `if…end`) fail inline with `parse error near 'end'` though valid in fish

Open 💬 0 comments Opened Jun 9, 2026 by aholland

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 commit under 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>

View original on GitHub ↗