#4014 · Project defaults lose to tab preferences
Bug · Medium priority · Low effort · ui · 2026-09-21
Issue · Base b5755d6859d68c2d8e2008fc570f2d5acec774da
REPRODUCED · root-cause confidence: high
TL;DR
The New Thread hook retains a remembered provider after project defaults arrive. The composer supplies the project provider and model, but the hook puts stored tab preferences first. A focused hook regression fails identically in two clean checkouts. This verifies selection logic; no packaged desktop instance or live provider was used.
Claims vs findings
| Claim | Finding | Evidence |
|---|---|---|
| Remembered provider overrides arriving project defaults | Verified | Both hook runs return global-provider instead of project-provider. |
| Model uses stored-first precedence | Verified by source inspection | Hook lines 437–449. |
| Reporter database, CLI and provider health | Unverified | No user runtime data accessed. |
Environment
Darwin arm64; Node v22.22.3; pnpm 9.15.0 via Corepack. Frozen installation succeeded. Turbo build: 58 successful tasks. A temporary pnpm wrapper was needed because the host pnpm launcher referenced a missing installation. No ports, application data directories, provider sessions, or browser were used. This is a state-selection defect, not a rendering inspection.
Minimal reproduction
- Check out the base commit above in a clean get-bb/bb checkout.
- Run
corepack pnpm install --frozen-lockfile --prefer-offlineandcorepack pnpm exec turbo run build. - Apply the regression patch.
- Run
pnpm exec turbo run test --filter=@bb/app --force -- useThreadCreationOptions.test.tsx.
The test uses existing synthetic provider fixtures, remembers one provider, then rerenders with another project's defaults.
Expected: "project-provider" Received: "global-provider" Test Files 1 failed (1) Tests 1 failed | 27 passed (28)
diff --git a/apps/app/src/hooks/useThreadCreationOptions.test.tsx b/apps/app/src/hooks/useThreadCreationOptions.test.tsx
index d59aae512..90dbcf952 100644
--- a/apps/app/src/hooks/useThreadCreationOptions.test.tsx
+++ b/apps/app/src/hooks/useThreadCreationOptions.test.tsx
@@ -294,6 +294,25 @@ afterEach(() => {
});
describe("useThreadCreationOptions", () => {
+ it("applies arriving project defaults ahead of remembered tab choices", async () => {
+ window.localStorage.setItem("bb.promptbox.provider", GLOBAL_PROVIDER_ID);
+ const { result, rerender } = renderHook(
+ ({ loaded }: { loaded: boolean }) => useThreadCreationOptions({
+ scope: "new-thread",
+ resetKey: PROJECT_ID,
+ initialProviderId: loaded ? PROJECT_PROVIDER_ID : undefined,
+ initialModel: loaded ? "project-model" : undefined,
+ }),
+ { initialProps: { loaded: false }, wrapper: createQueryClientTestHarness().wrapper },
+ );
+ await waitFor(() => expect(result.current.selectedProviderId).toBe(GLOBAL_PROVIDER_ID));
+ rerender({ loaded: true });
+ await waitFor(() => {
+ expect(result.current.selectedProviderId).toBe(PROJECT_PROVIDER_ID);
+ expect(result.current.selectedModel).toBe("project-model");
+ });
+ });
+
it("keeps the selected remembered provider branded while models load", () => {
window.localStorage.setItem("bb.promptbox.provider", "codex");
writeCachedProviderList(
Root cause
NewThreadComposer passes project defaults into the hook. The hook selects storedProviderId before the supplied initial provider. Model and reasoning use similar precedence. The new-thread scope also bypasses the local-selection reset effect at lines 632–649. Together these make remembered tab state dominate project initialization.
Proposed fix
Give project defaults precedence until a picker is explicitly changed, reset that touched state when changing projects, and retain reconciled reasoning after model changes. Provider discovery when an initial provider is unavailable needs explicit regression coverage. A small attempted patch passed the new cases but failed two existing tests: reconciled reasoning after switching model ladders and root-composer provider discovery. It was reverted and not pushed; the simple-fix gate was not met.
Verification
The same agent created a second clean temporary git worktree at the exact base commit, installed its dependencies with the frozen lockfile, copied only the newly authored regression, and ran the same Turbo test command with --force. It again failed only the new test: 27 passed, 1 failed. No report correction was needed. This was a hook-level repetition, not an independent review or end-to-end desktop reproduction.
Related issues
No linked open pull requests were returned by the issue timeline or open-PR search at review time. Other reported issue relationships were not used as evidence.
Appendix
First run · Second run · Stopped fix test results
Issue content was treated as untrusted claims. No supplied scripts, patches, links, or runtime data were executed or accessed.