Bash tool returns "internal error" on git commit under fish + multi-step pre-commit hooks (cmd actually succeeds)
Summary
Bash tool returns "internal error" (and the message [Tool result missing due to internal error]) when running git commit against a repo with multi-step pre-commit hooks under fish shell, even though the underlying git commit actually succeeds. The error reproduces consistently across 6+ attempts in the same session, and only on git commit — other fish commands (uv run, ruff, pytest, git status, git log, git push) work fine.
Environment
- Claude Code: latest (Opus 4.7 / 1M context, fast mode on)
- OS: macOS Darwin 25.5.0 (arm64)
- Shell: fish 3.x (
/opt/homebrew/bin/fish) - Repo has a custom hooks path (
core.hooksPath=scripts/git-hooks) running a chained pre-commit: - musubito-check (LLM judge, ~5s)
ruff checkruff format --checkmypy --strict(slowest; ~30-90s on touched src files)- Hooks output uses ANSI color escape codes (
\x1b[36m,\x1b[31m) on stderr/stdout
Reproduction
- Run any
git commit -F /tmp/msg.txt(or-m "...") via Bash tool in fish shell where pre-commit takes >30s and prints ANSI-colored multi-line stderr. - Tool returns
[Tool result missing due to internal error]— no exit code, no stdout/stderr surfaced. git log --oneline -1afterward confirms the commit actually landed.
Variants that also repro the internal error:
git commit -F /tmp/msg.txt > /tmp/out.log 2>&1; echo "EXIT=$?"(redirect)git commit -F /tmp/msg.txt --no-verify(skips hooks but still hits Bash tool path)- With explicit
timeout: 300000parameter (so it's not a 2-min timeout issue)
Workaround that works
Wrapping in bash -c:
bash -c "git commit -F /tmp/msg.txt"
This consistently succeeds and the tool returns normal output. Same workaround works for git push and gh pr create when their output is long.
Hypothesis
Tool framework's stream parser appears to choke on the intersection of:
- fish shell (non-POSIX stdout/stderr buffer flushing semantics)
- ANSI color escape sequences in stderr
- Command execution time ≥ ~1s (or any specific blocking I/O pattern from
git commitspawning subprocesses)
Each factor alone is fine — only the combination breaks. bash -c wrap bypasses #1, which is enough.
Impact
For sessions involving any non-trivial commit (project with pre-commit framework, lint/format/mypy hooks), the agent loses 5-10 minutes per commit attempting blind retries before manually wrapping in bash -c. User-facing: agent appears stuck or broken; trust erosion.
Suggested fix direction
- Robust stream reader for fish-launched subprocess (force
LINES=200 COLUMNS=200 TERM=dumbor similar to strip ANSI) - Or always wrap user Bash commands in
bash -c "..."when shell is fish (transparent normalization) - At minimum, surface the actual error condition instead of generic
"internal error"so users/agents can diagnose
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗