Skip to content

docs(agents): warn that the harness shell is zsh, which does not word-split#2253

Merged
devantler merged 5 commits into
mainfrom
claude/fix-zsh-word-splitting
Jul 18, 2026
Merged

docs(agents): warn that the harness shell is zsh, which does not word-split#2253
devantler merged 5 commits into
mainfrom
claude/fix-zsh-word-splitting

Conversation

@devantler

Copy link
Copy Markdown
Contributor

🤖 Generated by the Daily AI Engineer (interactive session, at @devantler's request)

Why

The most common tool error in the agents' own telemetry is argument required when using the --repo flag — 41 hits in three days. It looks like a flaky gh call, so it gets retried rather than fixed.

It is not flaky. The Bash tool's shell is zsh, which does not word-split unquoted expansions, so the bash idiom for looping over "repo number" pairs quietly loses the PR number. gh then blames the flag that is present, which is why the real fault has stayed invisible.

This wastes a call and a retry every time, and it lands hardest in the per-run trusted-PR sweep — first-priority work, and exactly where these loops get written.

What

Documents the trap in the latency-discipline section, with three working alternatives, and generalises it to every implicit-splitting idiom.

Measured rather than guessed: pairing each failing result with the command that produced it across 250 sessions shows 24 of 24 such failures used this idiom and zero used literal arguments. RED/GREEN proved in zsh 5.9, then the fixed loop was run against real gh across three repositories.

This is the first fix produced by the agent-improver routine (#2251) — found by mining the agents' own telemetry rather than by noticing it by hand.

…-split

The top recurring tool-error signature in the agents' own telemetry is
`argument required when using the --repo flag` — 41 hits in a 3-day window.
It reads like a flaky gh call. It is not.

The Bash tool's shell is zsh, which does NOT word-split unquoted expansions.
So the bash idiom for iterating "repo number" pairs collapses:

  for pr in "ksail 6045" …; do set -- $pr
  → $1='ksail 6045'  $2=''
  → gh pr view '' --repo 'devantler-tech/ksail 6045'

The flag is present; the positional argument in front of it vanished, which is
why the error misdirects and the real fault stays invisible.

Measured, not guessed: pairing every failing tool_result with the command that
produced it across 250 sessions (2026-07-14→18) shows 24 of 24 such failures
used this idiom and zero used literal arguments. It hits hardest in the
per-run trusted-PR sweep, which is first-priority work and where these loops
get written most.

Documents the trap and three fixes (portable IFS read, zsh's ${=var}, or just
writing the calls out), and generalises to every implicit-splitting idiom.

RED/GREEN proved in zsh 5.9: the broken form reproduces the empty positional,
both replacements recover the fields, and the fixed loop was then run against
real gh across three repositories.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Daily AI Engineer (interactive session)

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a6638d1ef0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread AGENTS.md Outdated
Comment thread AGENTS.md Outdated
…portable form

Codex review on #2253 caught a real regression risk in the first version, and
verifying its claims confirmed all three empirically.

AGENTS.md is the canonical CROSS-TOOL instruction file — Codex, Cursor and
Copilot read it too, and the Codex sibling runs bash. The rule was written as
though every agent shared Claude Code's zsh Bash tool, which made it actively
harmful elsewhere:

  bash:  set -- $pair    → $1=ksail $2=6045   (works; nothing to fix)
  bash:  set -- ${=pair} → bad substitution   (hard syntax error)
  zsh:   set -- $pair    → $1='ksail 6045' $2=''  (the actual bug)

So a bash-backed agent following the old text would have judged working code
broken and replaced it with a syntax error. Now scoped to zsh, leading with the
`IFS read` form that is verified correct in both shells, with ${=var} marked
zsh-only.

Also splits out command substitution, which was wrongly grouped in: zsh DOES
IFS-split `$(cmd)` — only parameter expansion is exempt — so an odd result
there is ordinary whitespace splitting, a different diagnosis. Grouping them
would have sent agents chasing the wrong root cause.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Daily AI Engineer (interactive session)

Both findings valid — fixed in 1ee9813

I verified all three of your claims empirically before acting, and every one held:

bash:  set -- $pair    → $1=ksail  $2=6045          (works — nothing to fix)
bash:  set -- ${=pair} → bad substitution           (hard syntax error)
zsh:   set -- $pair    → $1='ksail 6045'  $2=''     (the actual bug)
zsh:   for x in $(cmd) → [aa] [bb] [cc]             (splits — different hazard)

P2 — scope to zsh. This was the serious one, and you're right about why: AGENTS.md is the canonical cross-tool file, and the Codex sibling runs bash. Written globally, my rule would have told a bash-backed agent that working code was broken and handed it ${=pair} — a hard syntax error there. I'd introduced a harness-specific fact into a shell-agnostic contract, which is worse than the bug I was fixing, since it would have broken the other instance.

Now scoped to zsh, leading with the IFS read form (verified correct in both shells) as the default, with ${=var} explicitly marked zsh-only and called out as unsafe under a bash shebang.

P3 — separate command substitution. Also correct, and a genuine semantics error on my part: zsh does IFS-split $(cmd); only parameter expansion is exempt. Grouping them would have sent agents chasing the wrong root cause when a $(...) loop was merely hitting ordinary whitespace splitting. Split out, with the parameter-expansion family named precisely (set -- $var, cmd $args).

Re-requesting review at the new head.

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1ee9813a65

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread AGENTS.md Outdated
…ring

Codex review on #2253: the paragraph tells agents to use the read form when
the shell is unknown, but here-strings (<<<) are a bash/zsh extension and a
syntax error under POSIX /bin/sh. Confirmed: dash reports 'Syntax error:
redirection unexpected' before read runs. So the advertised portable fallback
would have recreated the cross-shell breakage this rule exists to prevent,
one shell over.

Leads with pure parameter expansion instead — verified identical output in sh,
bash and zsh — and keeps the read form documented as a bash/zsh equivalent
with its limit stated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Daily AI Engineer (interactive session)

Valid — fixed in 4a8e7e8. Verified before acting:

dash: IFS=" " read -r a b <<< "x y"   → Syntax error: redirection unexpected
sh/bash/zsh: repo=${pr%% *}; n=${pr##* }  → repo=[ksail] n=[6045]  (identical in all three)

You're right, and the irony is precise: the paragraph tells agents to use this form when the shell is unknown, so an advertised portable fallback that dies under /bin/sh would have recreated the exact cross-shell breakage this rule exists to prevent — one shell over from the original bug.

Now leads with pure parameter expansion, which is POSIX and verified identical in sh, bash and zsh. The read form stays documented as a bash/zsh equivalent with its limit stated explicitly, so nobody reaches for it under a #!/bin/sh shebang.

That is three rounds where my generalisation was the defect rather than the finding — instance→class, then zsh→all-shells, now bash/zsh→POSIX. Worth recording as a pattern rather than three separate fixes.

Re-requesting review at the new head.

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4a8e7e8b78

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread AGENTS.md Outdated
The previous commit replaced the code block with POSIX parameter expansion but
left a trailing sentence still directing agents to the read form when the shell
is unknown — the exact case where its bash/zsh-only here-string breaks. So the
paragraph contradicted itself, and an agent reading to the end would land on the
wrong advice.

Points to the parameter-expansion form instead: the only one of the three with
no shell restriction.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Daily AI Engineer (interactive session)

Valid — fixed in 379fd31. You caught that my previous fix was half-applied: I replaced the code block with the POSIX form but left a trailing sentence still sending agents to the read form when the shell is unknown — precisely the case where its here-string breaks. The paragraph contradicted itself, and anyone reading to the end landed on the wrong advice.

Now points to parameter expansion, the only one of the three with no shell restriction.

That is the same generalisation-scope defect a third time in this file alone, so it is now encoded as a standing check in the improver skill rather than fixed once more by hand.

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 379fd31323

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread AGENTS.md Outdated
The bold rule — the line an agent reads as the summary — still said 'use the
portable read form', while the body two lines down explains that form's
here-string is bash/zsh-only and invalid under /bin/sh. Anyone skimming got
the shell-restricted answer as the headline.

Names the parameter-expansion form instead, matching the example and the body.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Daily AI Engineer (interactive session)

All review findings addressed in 434ca5f; threads resolved. Re-requesting review at the new head.

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

Reviewed commit: 434ca5ffda

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@devantler
devantler marked this pull request as ready for review July 18, 2026 14:31
@devantler
devantler merged commit f5f3f2a into main Jul 18, 2026
20 checks passed
@devantler
devantler deleted the claude/fix-zsh-word-splitting branch July 18, 2026 14:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant