Skip to content

fix(bash): narrow the shell-search opt-in to file lists and counts; close the bash -c and loop dodges - #40

Merged
wongk merged 2 commits into
mainfrom
fix/shell-search-optin-lists-only
Oct 9, 2026
Merged

wongk merged 2 commits into
mainfrom
fix/shell-search-optin-lists-only

Conversation

@wongk

@wongk wongk commented Oct 9, 2026

Copy link
Copy Markdown

Why

Four days after #38, agents were mostly getting around the block instead of switching to code_search:

  • The LEMONCROW_SHELL_SEARCH=1 prefix appeared on 713 shell calls, against 115 calls that were actually blocked. 46% of blocks were followed by the same search rerun with the prefix; only 19% by a code search.
  • Agents learn it from one block and then prefix nearly every search for the rest of the session. Parent agents also write it into sub-agents' prompts (20 sessions).
  • What they prefix is mostly ordinary lookups ("does symbol X exist", matching lines for a pattern), which code_search answers. Very little is the exhaustive enumeration the opt-in was for.
  • Two dodges needed no prefix at all: a search inside bash -c '...' and one inside a for ... do ... done loop body were never checked. And export LEMONCROW_SHELL_SEARCH=1; early in a chain unlocked everything after it.

Net: Opus 5.5 build agents moved from 15% to 21% of repo searches through code_search, while still running ~17 shell repo searches per 100 tool calls.

What

  • The opt-in now only lets through searches that list files or count matches: grep -rl/-L/-c, rg -l/-c/--count-matches, git grep -l/--name-only/-c. That is what a rename needs (the file list, then edit). A prefixed search that prints matching lines is blocked like any other.
  • The opt-in counts only on the search itself: LEMONCROW_SHELL_SEARCH=1 grep -rl ..., env LEMONCROW_SHELL_SEARCH=1 ..., or on an inline shell running it (LEMONCROW_SHELL_SEARCH=1 bash -c '... grep -rl ...'). An export, or an assignment on another command in the chain, no longer counts.
  • Inline shells and loop/if bodies are checked: bash -c '...' / sh -c payloads are followed (with the current cd), and a segment's leading do/then/else/if/while/! is skipped to reach the real command.
  • The block message now shows the list-only form: LEMONCROW_SHELL_SEARCH=1 grep -rl PATTERN DIR.

Unchanged: everything the index can't answer still runs without any prefix (single files, outside the repo, dependency/build/gitignored dirs, other revisions, stdin, output feeding a command).

Replay against real sessions

All 12,059 lc bash commands since Sep 22 whose working directory still exists, classified by the installed (#38) check and this one:

count
Prefixed, printed matching lines: run before, blocked now 56
Blocked before, blocked now 543
Not prefixed: decision changed 0
Blocked before, runs now 0
Prefixed, still runs 476

Of the 476 prefixed commands that still run, 259 are single-file or non-recursive greps (never blocked; the prefix is superstition), 31 feed their output into a command, 20 list files (the intended use), and 153 search worktrees deleted since then, which the replay can't judge (at the time they were live and would now be blocked).

Verification

  • test_bash_exec_repo_search_block.py: 57 cases (16 new): prefixed line-printing searches blocked; -l/-c/--count-matches/--name-only/--files-with-matches allowed with the prefix; export and other-segment prefixes ignored; bash -c/sh -c, for ... do, if ... then bodies blocked; prefixed bash -c running a list-only search allowed; end-to-end MCP bash run of a prefixed grep -rl.
  • 385 tool-supervision and bash tests pass; ruff, black, mypy clean.

…h; check bash -c and loop bodies

Co-Authored-By: lemoncrow <302591943+lemoncrow-agent[bot]@users.noreply.github.com>
@wongk
wongk merged commit 397f4a4 into main Oct 9, 2026
9 checks passed
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