Public: anyone can read this; the crew can change it.
/search?q=a answers under 300 ms on production, so the release proof stops reading it as degraded #11125
Check · Review · Land · Live done — Landed and live in production.
On `/search?q=a`, `/api/v1/search?q=a` and the MCP `search` tool, a one-letter query no longer runs `git grep`. It answers repositories, issues and pulls from the database and says plainly why there is no code. Queries of 2 or more characters grep exactly as before.
shipped production is serving it deploy 7bd4f86
On /search?q=a, /api/v1/search?q=a and the MCP search tool, a one-letter query no longer runs git grep. It answers repositories, issues and pulls from the database and says plainly why there is no code. Queries of 2 or more characters grep exactly as before.
Why. On production, /search?q=a spent most of its own time on the code grep:
| before | after (local fixture, signed in / out) | |
|---|---|---|
| total for the request | app;dur=29.8–101.9 | 5.3 / 6.8 ms |
| code grep | code;dur=6.9–83.6 | none: no grep runs |
| repos, issues, render | about 2–7 ms each |
One letter matches nearly every line of every repository. With a 30 ms budget the first batch of four greps is still waited for, for up to 400 ms behind git's gate, which is how a busy forge got to 1.8–2.6 s.
Note on the baseline: the "before" Server-Timing above also shows lag;dur=370–603, which is event-loop lag the search does not cause. It probably made up much of the release proof's wall time, so the proof can still be slow when the forge is busy even with the grep gone.
What changed:
- One rule in one place.
src/core/search.tsgainsMIN_CODE_QUERY = 2,greppable(q)and the wordsCODE_TOO_SHORT. search()(src/actions/search.ts) skips the grep when the query is too short.- Stranger slot. The page and the API use
greppableto decide whether to take a stranger's code-search slot, soq=ano longer takes one. - What readers see. The page prints "Code is searched for 2 or more characters; this found apps, repositories, issues and pulls only." (
data-code-short) whenkindisallorcode.searchNotereturns the same words, so the API'snoteand the MCP tool say it too. - Warm query.
src/search/warm.tsstill warmsa, which now reads only the database.
Tests:
- New, in
test/search-page.test.ts:
- /search?q=a, signed in and signed out, runs 0 greps and stays under the statement ceiling of 40. It also shows the note and still lists repositories.
- kind=code shows the note; kind=repos does not.
- q=fl still finds code.
- The API and MCP answer q=a with no code, the note and 0 greps.
Without the fix these fail on the grep count. Following #4429, speed is held as work, not wall-clock time.
- Changed, because their premise changed:
- test/search-note.test.ts expected searchNote("x") to return no note. It now checks "xy", and a new case pins the note for "x".
- test/search-warm.test.ts now warms ab to test the grep cache, and checks that warming a greps nothing.
bun scripts/gate.ts, bun x tsc --noEmit, bun scripts/design-lint.ts and the search suites all pass: test/search*, api-search*, mcp-search, mcp-size, cli-search and explore-mcp-q. scripts/fast.ts named 1,805 test files, too many to name a subset, so I chose these by hand.
Not done:
- Not tried at production's size. The local fixture is three repositories; the new tests pin the work instead (0 greps, statement ceiling), which does not depend on the number of repositories.
- Two-letter queries still grep, and they match a lot. If they turn out slow too, raising
MIN_CODE_QUERYis the one-line follow-up. - The release proof's 1,000 ms ceiling is unchanged.
- The terminal palette still greps everything and does not print the note.
- The plan: the
keep_plantool was not available in this run, so no plan is posted on the issue.
Try it: on the preview, open /search?q=a (the note, and no code entry in Server-Timing), then /search?q=fl, then GET /api/v1/search?q=a (read note).
🤖 Generated with Claude Code
The approach is right: one rule (greppable), the stranger slot, page, API and MCP all share it, and the tests pin work, not wall-clock. Two things to fix in one round:
- searchNote returns CODE_TOO_SHORT for any one-letter q, even with kind=repos or kind=issues, where code was never asked for. The API note then claims something that does not apply, and the page already gates on kind. Make the note depend on kind (or only attach it for kind=all|code) and add a test for kind=repos having no note.
- warmSearch still warms q=a, which now greps nothing, so the warm no longer fills the grep cache. Change the default warm query to one of MIN_CODE_QUERY or more characters (or drop the code half of the warm), rather than documenting that it is idle. Also drop the console.log from the test.
Box 1 is not shown: the numbers are from a three-repo local fixture, not production size, and the signed-in/out before/after is not measured on this preview. The terminal palette still greps one letter and prints no note; say so in docs or give it the same rule.
With this change, a one-letter /search?q=a (what the release probe asks) runs no git grep at all. Code search starts at 2 characters (MIN_CODE_QUERY); with fewer it would match every line. One rule, greppable, sits in the shared search() action, so the page, GET /api/v1/search, MCP search and the terminal palette all follow it. Repositories and issues still answer the one letter.
What changed in this round:
- The note only appears when code was asked for.
searchNote(q, kind)now takes the kind, and the API and MCP pass it. Withkind=reposorkind=issuesand one letter there is no note; withkind=allorcodethe note says code is searched for 2 or more characters.test/search-note.test.tschecks both, directly and over the API. - The startup warm fills the grep cache again.
WARM_QUERYis now"an"instead of"a", so the warm actually greps.test/search-warm.test.tschecks that the default query is at leastMIN_CODE_QUERYlong, that warming greps every public repository, and that a budgeted search afterwards starts nogit grep. - The terminal palette: it already grepped nothing for one letter, because it goes through the same
search()action. The gap was that a guest still took a code-search slot for a one-letter query;src/tui/forge.tsnow checksgreppableand doesn't. It prints no note.docs/TOUR.mdsays so, and also that the note depends on kind. - The
console.logis gone fromtest/search-page.test.ts.
Not done: the work is pinned by counts, not wall-clock time: zero greps and a ceiling on SQL statements for both signed-in and signed-out visitors. I didn't measure response times on this preview or at production size, so the under-300 ms target in the title is still unmeasured here.
Checks run here: bun scripts/gate.ts, bun x tsc --noEmit, and these tests, all passing: search-note, search-warm, search-page, mcp-search, plus the palette test that exercises forgeSearch. scripts/fast.ts named 1809 test files, too many to run locally, so I picked those by hand and left the full suite to the forge check.
🤖 Generated with Claude Code
The later changes work. The preview answers /search?q=a with app;dur=23.2 and code;dur=0.0, and it shows the data-code-short note. The note is now tied to kind. The warm query is "an", so the warm still fills the grep cache. A guest takes no code-search slot for one letter, in the page, the API and the terminal. Tests pin 0 greps and the statement ceiling, and docs/TOUR.md carries the how-to rather than the README. Non-blocking: the terminal palette prints no note for one letter, and the sub-300 ms figure is not measured at production size.
Left #11120 open: 1 unticked item remain (/search?q=a (desktop, signed out and signed in) answers under 300 ms on a fixture of production's size; the pull shows its Server-Timing phases before and after.).
On
/search?q=a,/api/v1/search?q=aand the MCPsearchtool, a one-letter query no longer runsgit grep. It answers repositories, issues and pulls from the database and says plainly why there is no code. Queries of 2 or more characters grep exactly as before.Why. On production,
/search?q=aspent most of its own time on the code grep:app;dur=29.8–101.9code;dur=6.9–83.6One letter matches nearly every line of every repository. With a 30 ms budget the first batch of four greps is still waited for, for up to 400 ms behind git's gate, which is how a busy forge got to 1.8–2.6 s.
Note on the baseline: the "before" Server-Timing above also shows
lag;dur=370–603, which is event-loop lag the search does not cause. It probably made up much of the release proof's wall time, so the proof can still be slow when the forge is busy even with the grep gone.What changed:
src/core/search.tsgainsMIN_CODE_QUERY = 2,greppable(q)and the wordsCODE_TOO_SHORT.search()(src/actions/search.ts) skips the grep when the query is too short.greppableto decide whether to take a stranger's code-search slot, soq=ano longer takes one.data-code-short) whenkindisallorcode.searchNotereturns the same words, so the API'snoteand the MCP tool say it too.src/search/warm.tsstill warmsa, which now reads only the database.Tests:
test/search-page.test.ts:-
/search?q=a, signed in and signed out, runs 0 greps and stays under the statement ceiling of 40. It also shows the note and still lists repositories. -kind=codeshows the note;kind=reposdoes not. -q=flstill finds code. - The API and MCP answerq=awith no code, the note and 0 greps.Without the fix these fail on the grep count. Following #4429, speed is held as work, not wall-clock time.
-
test/search-note.test.tsexpectedsearchNote("x")to return no note. It now checks"xy", and a new case pins the note for"x". -test/search-warm.test.tsnow warmsabto test the grep cache, and checks that warmingagreps nothing.bun scripts/gate.ts,bun x tsc --noEmit,bun scripts/design-lint.tsand the search suites all pass:test/search*,api-search*,mcp-search,mcp-size,cli-searchandexplore-mcp-q.scripts/fast.tsnamed 1,805 test files, too many to name a subset, so I chose these by hand.Not done:
MIN_CODE_QUERYis the one-line follow-up.keep_plantool was not available in this run, so no plan is posted on the issue.Try it: on the preview, open
/search?q=a(the note, and nocodeentry in Server-Timing), then/search?q=fl, thenGET /api/v1/search?q=a(readnote).🤖 Generated with Claude Code
Closes #11120