Skip to content

Catch bare head/tail in the truncation guard - #24

Open
chaosisnotrandomitisrhythmic wants to merge 1 commit into
AnswerDotAI:mainfrom
chaosisnotrandomitisrhythmic:fix-truncation-guard-bare-head-tail
Open

Catch bare head/tail in the truncation guard#24
chaosisnotrandomitisrhythmic wants to merge 1 commit into
AnswerDotAI:mainfrom
chaosisnotrandomitisrhythmic:fix-truncation-guard-bare-head-tail

Conversation

@chaosisnotrandomitisrhythmic

Copy link
Copy Markdown

_TRUNC requires an explicit line count, so a bare head or tail at the end of a pipe never matches. Both default to 10 lines, which is the same cut as the -10 form the guard already rejects.

_TRUNC = re.compile(r'\|\s*(tail|head)\s+(-n\s*)?-?([1-9]|1[0-9])\b')

The \s+ and the digit class are both mandatory. The guard names the two commands it is about, and then misses them whenever the count is left off.

Why it matters

The hook exists to stop truncation that is decided before the output exists. Leaving the count off does not weaken that decision, it just spells it differently:

command lines kept current guard
tar tzf x.tgz | head -10 10 rejected
tar tzf x.tgz | head 10 allowed
ls ~ | tail 10 allowed

Measured over four days of one developer's sessions, the guard fired 55 times on truncating pipes. In the same corpus a rejected | head -1 was retried moments later as | sed -n 1p and passed, which is the same shape of hole: the rule matches a notation rather than the behaviour. This PR closes the head/tail half, where the bypass is the guard's own two commands. sed -n '1,10p' and awk 'NR<=10' truncate identically and are still allowed; they feel like a separate question, since neither is what the message tells you to fix.

The fix

Two alternatives instead of one: an explicit count under 20, or no count at all.

_TRUNC = re.compile(r'\|\s*(?:tail|head)'
                    r'(?:\s+(?:-n\s*)?-?(?:[1-9]|1[0-9])\b'  # explicit: -5, -n 5, -19
                    r'|(?=\s*(?:$|[|;&\n])))')                # bare: defaults to 10

The bare branch is a lookahead, so it only fires where the command actually ends: at end of string, or before |, ;, & or a newline. That keeps head -30, head -c 200, tail -f and ls | header out of it.

Tests

Added to test_bash_guard, all failing before the change:

assert bash_guard_msg('ls ~ | head')
assert bash_guard_msg('ls ~ | tail')
assert bash_guard_msg('cat big.log | head | wc -l')
assert bash_guard_msg('ls | head; echo done')
assert bash_guard_msg('ls | head && echo ok')
assert bash_guard_msg('ls | head\necho next')
assert bash_guard_msg('ls | header') is None           # a command that merely starts with `head`
assert bash_guard_msg('tail -f app.log') is None       # no pipe, and follow mode truncates nothing
assert bash_guard_msg('cat f | head -c 200') is None   # bytes, not lines: out of scope either way

Every pre-existing assertion in test_bash_guard still passes unchanged, including the -20 and -50 boundaries and the head -3 file.txt case with no pipe.

Note on running the suite

uv run pytest could not resolve dependencies on this checkout, before and after the change:

Because the requested Python version (>=3.10) does not satisfy Python>=3.11 and
llmdojo>=0.0.3 depends on Python>=3.11, we can conclude that llmdojo>=0.0.3 cannot be used.

pyproject.toml declares requires-python = ">=3.10" while llmdojo needs >=3.11. I verified bash_guard_msg directly instead, which needs only the standard library: 14 cases that must be blocked and 11 that must pass, 0 failures. Happy to split the requires-python bump into its own PR if it is wanted.


🤖 Generated with Claude Code

_TRUNC requires an explicit line count, so a bare `head` or `tail` at the end of
a pipe never matches. Both default to 10 lines, which is the same cut as the
`-10` form the guard already rejects. The guard names the two commands it is
about and then misses them whenever the count is left off.

The regex now has two alternatives: an explicit count under 20, or no count at
all. Counts of 20 and above, byte counts, follow mode, and commands that merely
start with those four letters are all unaffected.
chaosisnotrandomitisrhythmic added a commit to chaosisnotrandomitisrhythmic/aai-coding that referenced this pull request Sep 5, 2026
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