Skip to content

fix(bash): block repo-wide shell search in favour of code_search - #38

Merged
wongk merged 2 commits into
mainfrom
fix/block-shell-repo-search
Oct 5, 2026
Merged

wongk merged 2 commits into
mainfrom
fix/block-shell-repo-search

Conversation

@wongk

@wongk wongk commented Oct 5, 2026

Copy link
Copy Markdown

Why

Two instruction changes (#35, #37) asked Opus 5.5 to send repo searches to code_search. The audit of sessions started after #37 shows the wording moved main sessions (6% → 27% of repo searches) but barely moved build agents (5% → 10%; Opus 5 was 15–18%). Build agents added a few code searches without giving up grep: shell repo searches stayed at ~18.6 per 100 tool calls. Asking has stopped working, so this makes the bash tool enforce it.

What

LemonCrow's bash tool now refuses a recursive content search of the indexed repo (grep -r/-R, rg, git grep on the working tree), with a message that points to code_search and names what still runs. Applies to every command in a chain (cd x && grep -rn …, ls; rg …).

Still runs, because the index can't answer it:

  • single files, and non-recursive grep over named files
  • anything outside the repo: logs, scratch dirs, other checkouts
  • dependency/build dirs (node_modules, dist, .venv, …) and gitignored dirs (.closedloop-ai/, caches), judged by the worktree's own checkout
  • other revisions: git grep PAT origin/main, git grep PAT REV -- path
  • stdin (git log | grep, cmd | rg) and searches whose output feeds a command (| xargs, $(…))
  • rg -u / --no-ignore / --files
  • anything it can't follow statically ($VAR paths, cd -): fails open

Escape hatch: code_search is ranked and capped, so an exhaustive list of matches (e.g. every call site before a rename) needs real grep. Prefixing the command with LEMONCROW_SHELL_SEARCH=1 runs it. The block message says so. Future audits can count how often agents reach for it.

Only the MCP bash tool passes search_root, so other classify_command callers are unchanged. The worktree's repo is resolved to its main checkout, so .claude/worktrees/* searches are covered.

Replay against real sessions

I replayed every lc bash command from main and build-agent sessions since Sep 22 whose working directory still exists (6,301 commands) through the new guard:

  • 493 blocked, all repo-source searches in a manual review of samples (git grep -n X -- apps packages, grep -rn X apps/desktop scripts …).
  • 0 blocked among the 4,277 commands that are not searches.
  • The rest of what the audit's coarse classifier called "repo searches" (1,531) still run: mostly single-file greps, git grep … origin/main, and scratch/log searches.

The first replay also caught grep -rl … .closedloop-ai/campaigns (gitignored campaign state) being blocked; the gitignore check fixes that.

Verification

  • New tests/core/capabilities/tool_supervision/test_bash_exec_repo_search_block.py (41 cases): blocked shapes, every allowed shape above, worktree coverage, no search_root → no block, and the MCP bash tool end to end (blocked result; opt-in prefix runs the real search).
  • 380 tool-supervision and gateway bash/shell/grep tests pass, plus all 477 tests in the files that import classify_command; ruff, black, mypy clean.

After merge

Update the install, then rerun the audit after a few days of sessions: build-agent code-search share (10% now), how often the block fires, and how often agents use the opt-in prefix.

… with an opt-in for exhaustive search

Co-Authored-By: lemoncrow <302591943+lemoncrow-agent[bot]@users.noreply.github.com>
@wongk
wongk merged commit 815eb8a into main Oct 5, 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