#4413 · Request and suggestion responsiveness
Verdict: PARTIALLY REPRODUCED. Root-cause confidence: high for the three directly exercised backend mechanisms. Reproduction label: partial-repro.
1. TL;DR
A broad thread search executes SQLite matching on the same event loop that handles unrelated HTTP requests. Sequential path queries traverse the unchanged workspace again because only pending listings are retained. A completed mention provider cannot reach the aggregate endpoint's caller until every provider finishes. All three mechanisms were reproduced twice against the same current trusted main commit, using entirely synthetic data. The verdict is partial because rendered suggestions, query replacement, packaged cold-start behavior, and the historical performance magnitudes were not remeasured.
2. Claims vs findings
| Claim | Finding | Direct evidence |
|---|---|---|
| Full-text matching delays unrelated requests | Verified | An independent client worker sent a health request 5 ms after each search; health latency tracks the search duration. The trusted route directly invokes synchronous db.all. |
| Successive settled file queries rediscover the workspace | Verified | Each of four query prefixes performed 21 directory reads over the same unchanged 1,000-file tree, in both runs. |
| A slow provider holds back fast suggestions | Verified at aggregate API | The installed fast provider wrote its completion marker; the response remained unpublished for another 100 ms until the other provider's gate was released. Final groups were ordered fast, gated. |
| Previous-query suggestions appear as current | UI unverified | Trusted source retains previous query data when the trigger matches; no browser replacement scenario was executed. |
| Historical latency, sorting overhead, and prototype improvements | Unverified here | No historical runtime, linked branch, attached binary, profiling harness, or issue command was executed. These local observations are not a production benchmark or before/after speedup. |
3. Environment
- Public repository
get-bb/bb; fetched trustedorigin/maincommit0e7b518f135d43005dae201ef34ebb3001607eb4. - Linux 4.19.0-gvisor x86_64, Node v22.19.0, pnpm 9.15.0, Vitest 4.1.1; one visible CPU. No model inference or real user data.
- Two separate detached worktrees, frozen installs, and successful Turbo builds for server, daemon, and app. The second build used existing Turbo artifacts; the verification test runs were forced live, not replayed from cache.
- Migrated in-memory SQLite from the repository's test harness. It creates a connection from its migrated test template; no database mocks. The search fixture contains 24 visible threads, 12 archived, and 24,000 synthetic message segments. Synthetic index rows are inserted directly to isolate matching, not to test indexing ingestion.
- Search HTTP listeners: first:
127.0.0.1:33333, temporary data directory/tmp/bb-server-test-USER; second:127.0.0.1:49322, temporary data directory/tmp/bb-server-test-USER. Each listener was closed and its temporary directory removed by the harness. - Mention fixtures used separate temporary directories
/tmp/bb-server-test-USERand/tmp/bb-server-test-USER; installed through the real plugin loader and called through the actual app route. No network plugin, user plugin, or production core was started.
4. Minimal reproduction
- Create a clean checkout at the exact trusted commit, install frozen dependencies, and build.
- Save the two newly authored probes in the listed owning package paths. They use only the repository's existing dependencies.
- Run both targeted Turbo commands with
--silent=falseto retain observations. The file probe restores itsfs.readdirspy, and both probes clean up their temporary state.
git clone https://github.com/get-bb/bb.git bb-4413 cd bb-4413 git checkout --detach 0e7b518f135d43005dae201ef34ebb3001607eb4 pnpm install --frozen-lockfile --prefer-offline pnpm exec turbo run build --filter=@bb/server --filter=@bb/host-daemon --filter=@bb/app --concurrency=1 # Save the two linked, newly authored probe files at these paths: # apps/server/test/public/issue-4413-repro.test.ts # apps/host-daemon/src/command-handlers/issue-4413-repro.test.ts pnpm exec turbo run test --force --filter=@bb/server -- test/public/issue-4413-repro.test.ts --silent=false pnpm exec turbo run test --force --filter=@bb/host-daemon -- src/command-handlers/issue-4413-repro.test.ts --silent=false
Both complete probe sources, deterministic fixture recipe, source hashes, and all measurement samples are embedded below. Raw logs and separate source files are retained privately in the originating thread's evidence storage; they are not committed to the public reports repository.
Expected: unrelated requests should remain responsive during matching, consecutive typeahead queries should share bounded discovery, and a ready provider should be publishable without awaiting an unrelated provider.
Actual: unrelated requests wait through matching, discovery repeats for each settled prefix, and an already-completed provider is withheld. The existing aggregate endpoint is correctly preserving its shipped all-groups response contract; independent UI delivery needs an additive path or other compatible design, not a silent change to that contract.
| Run | Idle health median (5) | Concurrent health range (5) | Search range (5) | Directory reads by query | Fast provider |
|---|---|---|---|---|---|
| first | 2.8 ms | 46.4–53.9 ms | 50.1–58.3 ms | 21, 21, 21, 21 | 10.1 ms; unpublished for another ≥100 ms |
| second | 2.5 ms | 45.4–53.7 ms | 49.7–57.8 ms | 21, 21, 21, 21 | 11.2 ms; unpublished for another ≥100 ms |
The first idle sample includes connection/route warm-up. Ranges and medians describe these tiny local samples only; no p95 or universal SLA is inferred. The health requests originate on a separate Node worker with independent sockets, so a blocked server event loop cannot delay the client's dispatch timer.
RUN first
REPRO_SEARCH {"segments":24000,"serverPort":33333,"dataDir":"bb-server-test-USER","idleHealthMs":[23.017848999999842,3.7472109999998793,2.7930539999997563,2.2107420000002094,2.4004600000007486],"samples":[{"searchMs":58.27050899999995,"healthMs":53.89915099999962,"status":200,"healthStatus":200},{"searchMs":52.14386000000013,"healthMs":49.54676300000028,"status":200,"healthStatus":200},{"searchMs":50.62804200000028,"healthMs":46.873639999999796,"status":200,"healthStatus":200},{"searchMs":50.06107200000042,"healthMs":46.399311000000125,"status":200,"healthStatus":200},{"searchMs":52.34198100000049,"healthMs":50.90359999999964,"status":200,"healthStatus":200}]}
REPRO_FILES {"files":1000,"directories":20,"samples":[{"query":"pr","discoveryCalls":21,"elapsedMs":48.73935600000004},{"query":"pro","discoveryCalls":21,"elapsedMs":44.48033600000008},{"query":"prob","discoveryCalls":21,"elapsedMs":46.065967},{"query":"probe","discoveryCalls":21,"elapsedMs":46.547165999999834}]}
REPRO_MENTIONS {"fastReady":true,"dataDir":"bb-server-test-USER","readyMs":10.10323900000003,"observedPublished":false,"withheldForAtLeastMs":100,"totalMs":114.12359399999968,"providers":["fast","gated"]}
RUN second
REPRO_SEARCH {"segments":24000,"serverPort":49322,"dataDir":"bb-server-test-USER","idleHealthMs":[20.786345999999867,3.6964140000000043,2.5027650000001813,1.8010600000006889,2.101426000000174],"samples":[{"searchMs":57.80066699999952,"healthMs":53.720030000000406,"status":200,"healthStatus":200},{"searchMs":51.74314200000026,"healthMs":47.746643000000404,"status":200,"healthStatus":200},{"searchMs":50.575071999999636,"healthMs":45.4449939999995,"status":200,"healthStatus":200},{"searchMs":49.69389599999977,"healthMs":46.12322099999983,"status":200,"healthStatus":200},{"searchMs":50.994267000000036,"healthMs":49.34266299999945,"status":200,"healthStatus":200}]}
REPRO_FILES {"files":1000,"directories":20,"samples":[{"query":"pr","discoveryCalls":21,"elapsedMs":47.369461},{"query":"pro","discoveryCalls":21,"elapsedMs":42.923167999999805},{"query":"prob","discoveryCalls":21,"elapsedMs":45.271524},{"query":"probe","discoveryCalls":21,"elapsedMs":46.421035000000074}]}
REPRO_MENTIONS {"fastReady":true,"dataDir":"bb-server-test-USER","readyMs":11.196205000000191,"observedPublished":false,"withheldForAtLeastMs":100,"totalMs":117.41043600000012,"providers":["fast","gated"]}
Probe semantics: these three tests pass by asserting the observed problematic mechanisms and final result shape. They are repeatable diagnostic evidence, not fail-before/pass-after fix regressions. No production fix or performance improvement is claimed.
Server probe source
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { basename, join } from "node:path";
import { Worker } from "node:worker_threads";
import { archiveThread } from "@bb/db";
import { threadSearchResponseSchema } from "@bb/server-contract";
import { expect, it } from "vitest";
import { z } from "zod";
import {
seedHostSession,
seedProjectWithSource,
seedThread,
} from "../helpers/seed.js";
import { startTestServer, withTestHarness } from "../helpers/test-app.js";
it("records independent health requests delayed by full-text matching", async () => {
const harness = await startTestServer();
try {
const { host } = seedHostSession(harness.deps);
const { project } = seedProjectWithSource(harness.deps, {
hostId: host.id,
});
const insertion = harness.db.$client.prepare(
"INSERT INTO thread_search_segments (id, thread_id, source_kind, source_key, source_seq, text, created_at, updated_at) VALUES (?, ?, 'user_message', ?, ?, ?, 1, 1)",
);
harness.db.$client.transaction(() => {
for (let threadIndex = 0; threadIndex < 24; threadIndex += 1) {
const thread = seedThread(harness.deps, {
projectId: project.id,
title: `Fixture ${threadIndex}`,
});
if (threadIndex >= 12)
archiveThread(harness.db, harness.hub, thread.id);
for (let segmentIndex = 0; segmentIndex < 1000; segmentIndex += 1) {
const key = `${threadIndex}-${segmentIndex}`;
insertion.run(
`segment-${key}`,
thread.id,
key,
segmentIndex,
`probe phrase alpha fixture ${key}`,
);
}
}
})();
const client = new Worker(
`
const { parentPort, workerData } = require("node:worker_threads");
const http = require("node:http");
function request(route) {
return new Promise((resolve, reject) => {
const started = performance.now();
http.get(workerData + route, { agent: false }, (response) => {
let body = "";
response.on("data", (chunk) => { body += chunk; });
response.on("end", () => resolve({ ms: performance.now() - started, status: response.statusCode, body }));
}).on("error", reject);
});
}
(async () => {
const idleHealthMs = [];
for (let sampleIndex = 0; sampleIndex < 5; sampleIndex += 1) {
idleHealthMs.push((await request("/health")).ms);
}
const samples = [];
for (let sampleIndex = 0; sampleIndex < 5; sampleIndex += 1) {
const search = request("/api/v1/threads/search?query=probe&limitPerGroup=6");
await new Promise((resolve) => setTimeout(resolve, 5));
const health = request("/health");
const [searchResult, healthResult] = await Promise.all([search, health]);
samples.push({ searchMs: searchResult.ms, healthMs: healthResult.ms, status: searchResult.status, healthStatus: healthResult.status, body: JSON.parse(searchResult.body) });
}
parentPort.postMessage({ idleHealthMs, samples });
})().catch((error) => { throw error; });
`,
{ eval: true, workerData: harness.baseUrl },
);
try {
const result = await new Promise<unknown>((resolve, reject) => {
client.once("message", resolve);
client.once("error", reject);
});
const measurements = z
.object({
idleHealthMs: z.array(z.number()),
samples: z.array(
z.object({
searchMs: z.number(),
healthMs: z.number(),
status: z.number(),
healthStatus: z.number(),
body: threadSearchResponseSchema,
}),
),
})
.parse(result);
for (const sample of measurements.samples) {
expect(sample.status).toBe(200);
expect(sample.healthStatus).toBe(200);
expect(sample.body.active.total).toBe(12);
expect(sample.body.archived.total).toBe(12);
expect(sample.body.active.results).toHaveLength(6);
expect(sample.body.archived.results).toHaveLength(6);
}
console.log(
"REPRO_SEARCH",
JSON.stringify({
segments: 24000,
serverPort: harness.config.serverPort,
dataDir: basename(harness.config.dataDir),
idleHealthMs: measurements.idleHealthMs,
samples: measurements.samples.map(({ body, ...sample }) => sample),
}),
);
} finally {
await client.terminate();
}
} finally {
await harness.close();
}
}, 30000);
it("records a ready mention group withheld until a second provider is released", async () => {
await withTestHarness(async (harness) => {
const root = join(harness.config.dataDir, "fixtures", "bb-plugin-probe");
const fastDone = join(root, "fast-done");
const release = join(root, "release");
await mkdir(root, { recursive: true });
await writeFile(
join(root, "package.json"),
JSON.stringify({
name: "bb-plugin-probe",
version: "0.0.1",
bb: {
name: "Timing probe",
description: "Synthetic local fixture",
branding: { icon: "Zap" },
server: "./server.ts",
},
}),
);
await writeFile(
join(root, "server.ts"),
`
import { access, writeFile } from "node:fs/promises";
export default function plugin(bb) {
bb.ui.registerMentionProvider({
id: "fast", label: "Fast", triggers: ["#"],
async search(ctx) {
await writeFile(${JSON.stringify(fastDone)}, "ready");
return [{ id: "first", title: ctx.query }];
},
async resolve() { return { context: "fixture" }; },
});
bb.ui.registerMentionProvider({
id: "gated", label: "Gated", triggers: ["#"],
async search(ctx) {
for (;;) {
try { await access(${JSON.stringify(release)}); break; } catch {}
await new Promise((resolve) => setTimeout(resolve, 5));
}
return [{ id: "second", title: ctx.query }];
},
async resolve() { return { context: "fixture" }; },
});
}
`,
);
const installed = await harness.pluginService.installPath(root);
expect(installed.status).toBe("running");
let responsePublished = false;
const started = performance.now();
const response = harness.app
.request("/api/v1/plugins/mentions/search?q=probe&trigger=%23")
.then((value) => {
responsePublished = true;
return value;
});
let fastReady = false;
let observedPublished = false;
let readyMs = 0;
try {
for (let attempt = 0; attempt < 200; attempt += 1) {
try {
fastReady = (await readFile(fastDone, "utf8")) === "ready";
if (fastReady) break;
} catch {}
await new Promise((resolve) => setTimeout(resolve, 5));
}
readyMs = performance.now() - started;
await new Promise((resolve) => setTimeout(resolve, 100));
observedPublished = responsePublished;
} finally {
await writeFile(release, "release");
}
const completed = await response;
expect(completed.status).toBe(200);
const body = z
.object({ groups: z.array(z.object({ providerId: z.string() })) })
.parse(await completed.json());
console.log(
"REPRO_MENTIONS",
JSON.stringify({
fastReady,
dataDir: basename(harness.config.dataDir),
readyMs,
observedPublished,
withheldForAtLeastMs: 100,
totalMs: performance.now() - started,
providers: body.groups.map((group) => group.providerId),
}),
);
expect(fastReady).toBe(true);
expect(observedPublished).toBe(false);
expect(body.groups.map((group) => group.providerId)).toEqual([
"fast",
"gated",
]);
});
}, 30000);
File probe source
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { expect, it, vi } from "vitest";
import { runGit } from "@bb/host-workspace";
import { listHostPaths } from "./host-files.js";
it("records repeated discovery for settled consecutive path queries", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "bb-4413-files-"));
try {
await runGit(["init", "-b", "main"], { cwd: root });
for (let directoryIndex = 0; directoryIndex < 20; directoryIndex += 1) {
const directory = path.join(root, `group-${directoryIndex}`);
await fs.mkdir(directory);
for (let fileIndex = 0; fileIndex < 50; fileIndex += 1) {
await fs.writeFile(
path.join(directory, `probe-${fileIndex}.ts`),
"fixture",
);
}
}
const command = {
type: "host.list_paths" as const,
path: root,
includeFiles: true,
includeDirectories: true,
includeHidden: false,
respectGitIgnore: true,
excludeNames: [],
limit: 8,
query: "pr",
};
const discovery = vi.spyOn(fs, "readdir");
try {
const samples = [];
for (const query of ["pr", "pro", "prob", "probe"]) {
discovery.mockClear();
const started = performance.now();
const result = await listHostPaths({ ...command, query });
expect(result.paths).toHaveLength(8);
expect(result.truncated).toBe(true);
expect(
result.paths.every((entry) => entry.path.includes("probe-")),
).toBe(true);
samples.push({
query,
discoveryCalls: discovery.mock.calls.length,
elapsedMs: performance.now() - started,
});
}
console.log(
"REPRO_FILES",
JSON.stringify({ files: 1000, directories: 20, samples }),
);
expect(samples.map((sample) => sample.discoveryCalls)).toEqual([
21, 21, 21, 21,
]);
} finally {
discovery.mockRestore();
}
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
5. Root cause
Synchronous matching on the request event loop
The search handler directly constructs its response from searchThreadsWithPendingInteractionState. The matching query performs FTS grouping and ranking via synchronous db.all; the search entry point calls it before hydration. An output limit constrains returned threads, not all the earlier matching work. The health route also lives on this server event loop. The observed health wait is therefore not explained solely by rendering or network transit. No query-plan optimization or operator-level CPU attribution was measured here.
Completed discovery is removed
The pending listing map coalesces concurrent callers, then deletes the promise in finally. Every path command asks for discovery again before applying the query-specific ranking. These four commands were serial and their filesystem/options identical; each reentered the actual directory walker. The experiment does not separately attribute fuzzy sorting cost and does not prescribe a cache lifetime.
Publication waits for all providers
The mention service starts provider tasks concurrently but returns only after await Promise.all(tasks). The completed group is not available at the aggregate route before the remaining task resolves. The composer query uses this aggregate endpoint; its placeholderData condition checks the trigger rather than query/context identity. The latter is source evidence of previous-query reuse, not a directly observed browser defect in this report.
6. Proposed fix and safety decision
Split remediation by owner. Preserve the existing aggregate mention API while adding independently publishable provider results and query-specific presentation. Give workspace discovery a bounded reuse policy with explicit invalidation and in-flight handling, preserving uncached browsing freshness. Isolate expensive search execution or reduce synchronous query work while maintaining visibility, archive membership, counts, ranking, and snippets; measure cold startup and consistency separately.
No automatic PR: the verified causes cross server/database, host discovery, and composer/plugin delivery. An end-to-end repair is outside the one-subsystem simple-fix rule and requires cache freshness, compatibility, and search lifecycle/consistency decisions. No partial production patch was attempted, so no fail-before/pass-after evidence or eligible changed-line count exists. No branch or fix was pushed. GitHub issue cross-reference metadata and an open-PR search found no open linked repair at investigation time.
7. Linked PR reference
GitHub metadata reports #4402 closed, with 26 changed files, 1,282 additions, and 76 deletions. Only metadata and the untrusted textual diff were inspected. The diff introduces per-provider queries, listing caching/invalidation, and a search worker; those directions correspond to the independently reproduced mechanisms. It also touches server build configuration and API selectors, so it is not a qualifying simple fix. No prototype checkout, test, binary, benchmark, or performance claim was trusted or executed. This is a scope review, not a code-safety endorsement; no new severity-ranked prototype finding is asserted.
8. Related issues and trust boundary
This report covers only #4413. No unrelated issue was modified. The issue body contains execution instructions and links; all were treated as untrusted evidence and ignored. Fixture code and paths were authored from trusted repository interfaces. The linked prototype was not used as implementation material.
9. Verification
The same agent repeated the probes in a second initially clean detached checkout at 0e7b518f135d43005dae201ef34ebb3001607eb4. The checkout had a fresh frozen dependency install, successful targeted build, fresh loopback listener, freshly migrated test database, new synthetic files, and a newly installed synthetic plugin. The identical probe source hashes are in the manifest. The second test commands used --force to avoid Turbo test-cache replay.
pnpm exec turbo run test --force --filter=@bb/server -- test/public/issue-4413-repro.test.ts --silent=false pnpm exec turbo run test --force --filter=@bb/host-daemon -- src/command-handlers/issue-4413-repro.test.ts --silent=false Result in each checkout: server 2/2 tests passed; file discovery 1/1 passed.
The second run supported all three backend mechanisms; no correction to those findings was needed. This is a second clean run by the same agent, not independent verification. The deliberately partial verdict retains the unverified frontend and packaged/cold-start claims.
10. Appendix and limitations
{
"trustedBase": "0e7b518f135d43005dae201ef34ebb3001607eb4",
"fixture": {
"threads": 24,
"archived": 12,
"messageSegments": 24000,
"messageText": "probe phrase alpha fixture <thread-index>-<segment-index>",
"files": 1000,
"directories": 20,
"providers": [
"fast",
"gated"
],
"providerGate": "Local file released after observing the ready provider and waiting 100 ms"
},
"sourceHashes": {
"server-repro.test.ts": "511e506aaa6be876f593fae066e545fc6297e9ca7ef804f9e0116c153b76c7d9",
"files-repro.test.ts": "848e9aadfd3adc8d1f212281d1bdf65f04ef18f82bb18589a5cd1ba300033b5f"
}
}
Both frozen installs and both targeted builds exited successfully. Each reproduction run passed two server tests and one file-discovery test. Detailed install/build/test logs remain with the local evidence backup rather than on this public site.
Logs preserve actual command output and samples, with checkout paths generalized. The manifest records source hashes and deterministic fixture recipe; generated IDs and ephemeral ports vary. No user database, CPU profile, historical artifact, screenshot, browser trace, or attached recording is republished. These are API/owner-boundary diagnostics using source test harnesses, not an enrolled remote daemon or a packaged product performance validation. Sorting costs, provider errors/timeouts, query cancellation, watcher invalidation, concurrency across persisted writes, maximum throughput, mobile behavior, and cache memory scaling remain follow-up coverage for a chosen repair.
> AGENT GENERATED