_redirects_stdin in host-setup/agent-safety/claude/gh-write-guard.py ends its scan early when a redirect target is an expansion holding a separator or a parenthesis, so it credits an earlier binding and never reaches the later one bash applies.
Allowed on develop, and each loop reads /dev/zero forever:
while read l; do sleep 30; done < ${g:-;} < /dev/zero
while read l; do sleep 30; done < $( echo x ) < /dev/zero
The operator lex splits ${g:-;} at the ; and $( echo x ) at the (, so the scan meets a separator token inside the target, returns the bound the < before it set, and never sees < /dev/zero. Bash reads both expansions as one word, so the last binding is the stream.
The scan should not credit a binding whose target is an expansion it cannot read whole, since what follows that expansion is still the loop's own invocation.
Found by a local strict review pass on feature/auto-2152 (#2152), which made a trailing comment's stop refuse this same class for a #. The separator and parenthesis shapes predate that branch.
_redirects_stdininhost-setup/agent-safety/claude/gh-write-guard.pyends its scan early when a redirect target is an expansion holding a separator or a parenthesis, so it credits an earlier binding and never reaches the later one bash applies.Allowed on develop, and each loop reads
/dev/zeroforever:The operator lex splits
${g:-;}at the;and$( echo x )at the(, so the scan meets a separator token inside the target, returns theboundthe<before it set, and never sees< /dev/zero. Bash reads both expansions as one word, so the last binding is the stream.The scan should not credit a binding whose target is an expansion it cannot read whole, since what follows that expansion is still the loop's own invocation.
Found by a local strict review pass on
feature/auto-2152(#2152), which made a trailing comment's stop refuse this same class for a#. The separator and parenthesis shapes predate that branch.