#6118 · Child execution defaults bypass parent history
Verdict: REPRODUCED · Root-cause confidence: high · Reproduction label: confirmed-repro
1. TL;DR
Creating a child without execution flags starts it with remembered project settings rather than its parent's last execution. This happens even when parent and project use the same provider: model and reasoning still come from the project. A real server creation test reproduces both variants, and the same test fails again in a second clean checkout. Provider selection has no parent input, and execution planning consults parent history only for permission mode. No real provider invocation or user runtime data is needed to demonstrate the wrong outbound start command.
2. Claims vs findings
| Claim | Finding | Evidence |
|---|---|---|
| Parent linkage does not imply provider inheritance. | Verified | A Claude Code parent produces a Codex child when project defaults select Codex. |
| Model and reasoning also bypass parent history. | Verified | Both same-provider and cross-provider cases send project-model / medium instead of parent-model / low. |
| The guide promises parent execution precedence. | Verified | Trusted guide lines 81–89; history shows the broad wording was added alongside permission inheritance. |
| Parent-self conversion causes the failure. | Ruled out by source and direct service reproduction | CLI forwards parentThreadId; the server receives a valid parent but does not use it for these defaults. |
| Every newly spawned CLI thread rewrites project defaults. | Refuted on trusted main | Remembering defaults is restricted to app-origin creation, not CLI-origin creation. This does not explain away the missing parent inheritance; the regression isolates an existing conflicting project preference directly. |
| A particular installed provider or model caused the reported live incident. | Unverified | No account-backed turns or original private runtime records were accessed. The server's wrong command is reproduced with deterministic fixture models. |
3. Environment
- Trusted public get-bb/bb main at the full base SHA above, version 0.46.1.
- Linux x86_64, kernel 4.19.0-gvisor; Node v22.19.0; pnpm 9.15.0; Vitest 4.1.1.
- Repository-provided first-party registrations for Codex and Claude Code; fixture model strings, no external provider binaries or credentials.
- Real migrated SQLite test database through createMigratedConnection and the normal server harness. Fresh temporary data directories in each run are removed by harness cleanup. No development server, bound port, production instance, or Connect registration.
- Frozen install succeeded. An initial broad build lacked the SDK runtime artifact; explicitly building the existing SDK then rerunning the normal build succeeded (69 tasks). The second checkout also completed the build.
4. Minimal reproduction
- Check out the trusted base in a scratch directory:
git clone https://github.com/get-bb/bb.git bb-6118-repro cd bb-6118-repro git checkout --detach cc292b975fc35e16d7480cd9b8d55eff0ed88d8f pnpm install --frozen-lockfile --prefer-offline pnpm exec turbo run build --filter=@get-bb/plugin-sdk pnpm exec turbo run build
- Save the complete reproduction test below at
apps/server/test/threads/thread-create-parent-execution.test.ts. It seeds a parent execution, changes remembered project defaults, creates a child without provider/model/reasoning, and inspects the actual queued thread.start command. - Run:
pnpm exec turbo run test --filter=@bb/server -- test/threads/thread-create-parent-execution.test.ts
Expected: the two assertions pass with each parent's provider, parent-model, and low reasoning. Actual: both assertions fail on the unchanged production code. Cross-provider assertion output:
- Expected
+ Received
{
- "model": "parent-model",
- "providerId": "claude-code",
- "reasoningLevel": "low",
+ "model": "project-model",
+ "providerId": "codex",
+ "reasoningLevel": "medium",
}
Test Files 1 failed (1)
Tests 2 failed (2)
The same-provider case differs only in model and reasoning. Permission mode is held at auto in both fixtures so the failure cannot be attributed to a permission conversion.
Complete reproduction test
import { upsertProjectExecutionDefaults } from "@bb/db";
import { describe, expect, it } from "vitest";
import { createThreadFromRequest } from "../../src/services/threads/thread-create.js";
import { waitForQueuedCommand } from "../helpers/commands.js";
import { textInput } from "../helpers/prompt-input.js";
import {
seedEnvironment,
seedHostSession,
seedProjectWithSource,
seedThread,
seedThreadRuntimeState,
} from "../helpers/seed.js";
import { withTestHarness } from "../helpers/test-app.js";
describe("child creation execution defaults", () => {
it.each(["codex", "claude-code"])(
"inherits %s parent execution ahead of remembered project defaults",
async (parentProviderId) => {
await withTestHarness(async (harness) => {
const { host } = seedHostSession(harness.deps);
const { project } = seedProjectWithSource(harness.deps, {
hostId: host.id,
});
const environment = seedEnvironment(harness.deps, {
hostId: host.id,
projectId: project.id,
});
const parent = seedThread(harness.deps, {
projectId: project.id,
environmentId: environment.id,
providerId: parentProviderId,
});
seedThreadRuntimeState(harness.deps, {
threadId: parent.id,
environmentId: environment.id,
providerThreadId: "parent-session",
model: "parent-model",
reasoningLevel: "low",
permissionMode: "auto",
});
upsertProjectExecutionDefaults(harness.db, {
projectId: project.id,
providerId: "codex",
model: "project-model",
reasoningLevel: "medium",
permissionMode: "auto",
serviceTier: "default",
});
const child = await createThreadFromRequest(harness.deps, {
projectId: project.id,
parentThreadId: parent.id,
environment: { type: "reuse", environmentId: environment.id },
input: textInput("Check execution defaults"),
origin: "cli",
startedOnBehalfOf: null,
});
const start = await waitForQueuedCommand(
harness,
({ command }) =>
command.type === "thread.start" && command.threadId === child.id,
);
expect(start.command.type).toBe("thread.start");
if (start.command.type !== "thread.start") {
throw new Error("Expected child start command");
}
expect({
providerId: child.providerId,
model: start.command.options.model,
reasoningLevel: start.command.options.reasoningLevel,
}).toEqual({
providerId: parentProviderId,
model: "parent-model",
reasoningLevel: "low",
});
});
},
);
});
5. Root cause
The CLI forwards omitted execution fields as omissions and keeps the parent link (spawn.ts lines 602–619). The server validates and loads the parent, but calls its create-default resolver without that parent (parent validation; default resolver call).
Project create defaults consult only caller-owned input and project defaults. Provider policy then chooses:
args.requestedProviderId ?? storedProviderId ?? requireDefaultProviderId(registry)
The provider becomes immutable at creation. Later, execution planning loads parent history but model/reasoning candidates contain only request, child override, child last execution, and project execution. Only permission policy uses parentExecution. Thus an empty child history cannot supply the parent's selections, even when provider IDs match.
The guide contract is broader than the implementation. The introducing history added the parent-default wording while implementing only permission-history inheritance; this report does not infer that model/provider inheritance ever worked. The issue's separate claim about every CLI spawn updating remembered preferences is not true at this base: the remember policy accepts only app-origin creation outside reused environments and source-derived forks.
6. Proposed fix
Resolve creation defaults on the server using the already validated parent and its last recorded execution. Prefer caller-owned provider flags; otherwise select the parent's execution provider before project defaults. For a matching provider, use the parent's model and reasoning ahead of remembered project values, while leaving explicit model/reasoning overrides intact. Do not carry model or reasoning across an explicitly different provider. Keep the existing fallback when the parent has no recorded execution. Do not change permission policy, host protocol, stored schema, service-tier policy, or the public CLI/SDK contracts.
A focused regression should also cover explicit matching/different providers, explicit model/reasoning, source-aware stale picker values, and parents without history. Existing creation, runtime configuration, default policy, and execution-plan suites are the relevant sibling checks.
7. Verification
The same agent repeated the test in a second newly created detached worktree at the exact trusted base. Its production tree was clean before copying only the authored regression file. Frozen install, existing SDK build, and full build completed there. The same Turbo test command failed with the same two assertion mismatches (Vitest: 2 failed, 9.23 seconds). Each harness created fresh SQLite and data directories; no ports were used. This is a second clean reproduction by the same agent, not independent verification. No report correction was needed.
The complete original two-case test and the exact assertion mismatch are included in this HTML. Raw first-run and second-run logs are retained only in the local evidence backup; the public report repository deliberately excludes raw logs and test-file attachments.
8. Related issues
GitHub metadata reviewed: #3463 concerns one project-default row across providers; #4014 concerns the UI's project selection; #2306 concerns product-provider fallback. The same-provider reproduction distinguishes this defect from cross-provider project-default storage. No open PR linking #6118 was present when checked.
9. Appendix and limits
Commands used for evidence: trusted main fetch and git rev-parse; repository-file searches; git log -S and git show for history; frozen pnpm installs; Turbo SDK/full builds; the focused Turbo reproduction test in both checkouts; GitHub read-only issue/PR metadata. The report's linked logs retain failure output with machine-specific checkout paths removed. No screenshots are necessary for this nonvisual server-defaults bug.
The issue and its commands/links were treated as untrusted claims, not executed or fetched. The reproduction is authored from trusted repository tests and production code. It does not certify a real CLI/provider session, the original user's named threads, or the exact remembered-default write sequence. It establishes the server selection error before any provider can execute.