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

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

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

opened this

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:

beforeafter (local fixture, signed in / out)
total for the requestapp;dur=29.8–101.95.3 / 6.8 ms
code grepcode;dur=6.9–83.6none: no grep runs
repos, issues, renderabout 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.ts gains MIN_CODE_QUERY = 2, greppable(q) and the words CODE_TOO_SHORT.
  • search() (src/actions/search.ts) skips the grep when the query is too short.
  • Stranger slot. The page and the API use greppable to decide whether to take a stranger's code-search slot, so q=a no 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) when kind is all or code. searchNote returns the same words, so the API's note and the MCP tool say it too.
  • Warm query. src/search/warm.ts still warms a, 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_QUERY is 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_plan tool 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

Closes #11120

commented

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:

beforeafter (local fixture, signed in / out)
total for the requestapp;dur=29.8–101.95.3 / 6.8 ms
code grepcode;dur=6.9–83.6none: no grep runs
repos, issues, renderabout 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.ts gains MIN_CODE_QUERY = 2, greppable(q) and the words CODE_TOO_SHORT.
  • search() (src/actions/search.ts) skips the grep when the query is too short.
  • Stranger slot. The page and the API use greppable to decide whether to take a stranger's code-search slot, so q=a no 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) when kind is all or code. searchNote returns the same words, so the API's note and the MCP tool say it too.
  • Warm query. src/search/warm.ts still warms a, 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_QUERY is 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_plan tool 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

Changes requested — before the latest push

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:

  1. 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.
  2. 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.

commented

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. With kind=repos or kind=issues and one letter there is no note; with kind=all or code the note says code is searched for 2 or more characters. test/search-note.test.ts checks both, directly and over the API.
  • The startup warm fills the grep cache again. WARM_QUERY is now "an" instead of "a", so the warm actually greps. test/search-warm.test.ts checks that the default query is at least MIN_CODE_QUERY long, that warming greps every public repository, and that a budgeted search afterwards starts no git 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.ts now checks greppable and doesn't. It prints no note. docs/TOUR.md says so, and also that the note depends on kind.
  • The console.log is gone from test/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

Approved

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.

commented

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.).

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs