Reports

#4418 · Claude context launch investigation

BugPriority: MediumEffort: Mediumprovidersprovider-claude-codeGitHub issue

September 30, 2026 · trusted main 0e7b518f135d43005dae201ef34ebb3001607eb4

Verdict: NOT REPRODUCED · Root-cause confidence: low · reproduction label: no-repro

1. TL;DR

The reported symptom is intermittent loss of personal instructions, memory, skills, and identity context in Claude Code sessions. This investigation did not reproduce that loss. Two fresh trusted-main checkouts consistently preserved the requested context inputs at the bb-to-SDK boundary, including all three settings sources, the synthetic home directory, default-enabled memory, and supplied instructions. The SDK call was intercepted rather than running an authenticated Claude session, so these checks do not prove that Claude reads those inputs or receives the expected context. No root cause or already-fixed commit is established, and the negative result does not invalidate the reporter's observations.

2. Claims vs findings

ClaimStatusEvidence
Fresh ordinary sessions sometimes lose multiple context sources together.UnverifiedNo authenticated live session or affected session transcript was inspected. No deterministic failure obtained.
The settings-source request is identical for every SDK session.Verified at boundarySdkSession.start sets user, project, and local unconditionally; eight diagnostic cases pass in each clean checkout.
The effective environment is necessarily the same process environment for all sessions.Not establishedThe bridge merges validated per-session envVars into a filtered process environment. The fallback in SdkSession does not establish the actual effective environment of the reported sessions.
Project and personal workspace sessions can exhibit the same symptom.UnverifiedThe diagnostic passes two synthetic cwd strings; it does not instantiate either environment plugin or test their real filesystem contents.
Resume ancestry and CLI version do not predict affected sessions.UnverifiedFresh/resume inputs preserve the launch options here, but the historical transcript and version comparisons could not be repeated.
Three later sessions loaded context after an update.Reporter observationThe follow-up reports success across three environment types, but this is not proof of a fix or a reproduced outcome.

3. Environment

4. Minimal diagnostic and results

This is the closest obtained offline diagnostic, not a faithful end-to-end reproduction of missing context. It combines the real options builder with the real session launcher and intercepts the external SDK query boundary. A synthetic completed SDK stream permits cleanup without provider access. It protects the composition of these two production functions rather than claiming to test SDK file loading.

  1. Use a fresh clone of trusted main and pin the recorded revision:
    git clone --single-branch --branch main https://github.com/get-bb/bb.git bb-4418-qa
    cd bb-4418-qa
    git checkout --detach 0e7b518f135d43005dae201ef34ebb3001607eb4
    pnpm install --frozen-lockfile --prefer-offline
    pnpm exec turbo run build
  2. Download the diagnostic test from this report and copy it to plugins/provider-claude-code/src/bridge/__tests__/issue-4418-launch.test.ts. The file was authored from trusted repository evidence, not copied from issue data.
  3. Run:
    pnpm exec turbo run test --filter=bb-plugin-provider-claude-code -- src/bridge/__tests__/issue-4418-launch.test.ts src/bridge/__tests__/sdk-session.test.ts
  4. Expected: all settings sources, default-enabled auto-memory, synthetic HOME, and supplied context remain present in the SDK options for both paths, both instruction modes, and fresh/resumed sessions.
    Actual in both checkouts:
    Test Files  2 passed (2)
    Tests       23 passed (23)
    The eight added cases and the existing fifteen SDK-session tests all passed.
  5. Broader bridge check:
    pnpm exec turbo run test --filter=bb-plugin-provider-claude-code -- src/bridge/__tests__/issue-4418-launch.test.ts src/bridge/__tests__/sdk-session.test.ts src/bridge/__tests__/bridge.test.ts
    Actual in both checkouts:
    Test Files  1 failed | 2 passed (3)
    Tests       1 failed | 101 passed (102)
    The unrelated existing test falls back to well-known install locations when PATH discovery fails expects a home-directory executable. Production deliberately returns no well-known candidates when UID is zero. The observed value was undefined; this failure is not evidence of intermittent context loss and was not fixed.

The added file is a disposable report diagnostic, not a proposed production regression test. It never failed for the reported bug; therefore it cannot justify a repair or a pull request.

Diagnostic source

import { afterEach, describe, expect, it, vi } from "vitest";
import type { Options, SDKMessage } from "@anthropic-ai/claude-agent-sdk";

const { queryMock } = vi.hoisted(() => ({ queryMock: vi.fn() }));

vi.mock("@anthropic-ai/claude-agent-sdk", () => ({ query: queryMock }));

import { buildSessionOptions } from "../session-options.js";
import { SdkSession } from "../sdk-session.js";

afterEach(() => {
  vi.clearAllMocks();
});

describe("issue 4418 offline launch-boundary diagnostic", () => {
  const cases = ["/tmp/qa-project", "/tmp/qa-personal"].flatMap((cwd) =>
    ["append", "replace"].flatMap((instructionMode) =>
      [false, true].map((resume) => ({ cwd, instructionMode, resume })),
    ),
  );

  it.each(cases)(
    "preserves context inputs for cwd=$cwd mode=$instructionMode resume=$resume",
    async ({ cwd, instructionMode, resume }) => {
      const captured: Options[] = [];
      queryMock.mockImplementation(({ options }: { options: Options }) => {
        captured.push(options);
        return {
          [Symbol.asyncIterator]() {
            return {
              async next(): Promise<IteratorResult<SDKMessage>> {
                return { done: true, value: undefined };
              },
            };
          },
        };
      });
      const mode = instructionMode === "append" ? "append" : "replace";
      const sessionOptions = buildSessionOptions(
        {
          cwd,
          instructionMode: mode,
          baseInstructions: "Synthetic identity and skills context.",
          permissionMode: "auto",
          permissionScope: "workspace",
          serviceTier: "default",
          workflowsEnabled: false,
          chromeEnabled: false,
          disable1MContext: false,
        },
        { HOME: "/tmp/qa-home", PATH: "/tmp/qa-empty-path" },
      );
      let finish: (() => void) | undefined;
      const done = new Promise<void>((resolve) => {
        finish = resolve;
      });
      const session = new SdkSession(sessionOptions, () => {}, () => finish?.());
      session.start(resume ? "qa-session" : undefined);
      await done;
      expect(captured).toHaveLength(1);
      expect(captured[0]).toMatchObject({
        cwd,
        settingSources: ["user", "project", "local"],
        env: { HOME: "/tmp/qa-home", PATH: "/tmp/qa-empty-path" },
        settings: { autoMemoryEnabled: true },
        persistSession: true,
        permissionMode: "auto",
        systemPrompt:
          mode === "append"
            ? {
                type: "preset",
                preset: "claude_code",
                append: "Synthetic identity and skills context.",
              }
            : "Synthetic identity and skills context.",
      });
      if (resume) {
        expect(captured[0]).toHaveProperty("resume", "qa-session");
      } else {
        expect(captured[0]).not.toHaveProperty("resume");
      }
    },
  );
});

5. Root cause analysis

Root cause of the reported loss: not established. The available evidence narrows the question to the distinction between requested launch context and context actually loaded downstream; it does not locate a failing SDK or CLI branch.

The launcher at the pinned base always requests the full settings cascade and forwards a supplied environment in preference to its fallback:

      settingSources: ["user", "project", "local"],
      persistSession: true,
      env: this.options.env ?? process.env,
      stderr: onStderr,
      ...(this.options.mcpServers
        ? { mcpServers: this.options.mcpServers }
        : {}),
      ...(this.options.allowedTools
        ? { allowedTools: this.options.allowedTools }
        : {}),
      ...(this.options.disallowedTools

The options builder selects a native preset for append mode and an explicit custom prompt for replace mode; both diagnostic modes retain the supplied instructions. Replace mode is intentional and is not a demonstrated cause of this issue:

  const systemPrompt: Exclude<Options["systemPrompt"], undefined> =
    params.instructionMode === "replace"
      ? (params.baseInstructions ?? "You are a helpful coding assistant.")
      : {
          type: "preset",
          preset: "claude_code",
          ...(params.baseInstructions && params.baseInstructions.length > 0
            ? { append: params.baseInstructions }
            : {}),
        };

Memory defaults enable auto-memory unless a caller explicitly disables it:

function buildFlagSettings(params: BuildSessionOptionsArgs): Settings {
  return {
    autoMemoryEnabled: params.memoryEnabled ?? true,
    enableWorkflows: params.workflowsEnabled,
    ultracode: params.reasoningLevel === "ultracode",
    fastMode: params.serviceTier === "fast",
  };

The actual bridge environment construction accepts validated envVars. Thus inspecting only the SDK launcher's fallback does not rule out different HOME, configuration-directory, or executable-related inputs in historical sessions:

function buildSessionEnv(
  envOverrides: Record<string, string>,
): NodeJS.ProcessEnv {
  const sessionEnv: NodeJS.ProcessEnv = {
    ...withoutBridgeRuntimeEnv(process.env),
    ...envOverrides,
    CLAUDE_CODE_ENTRYPOINT: "cli",
  };
  delete sessionEnv.CLAUDE_AGENT_SDK_CLIENT_APP;
  return sessionEnv;
}

const sessionConfigEnvVarsSchema = z.record(z.string(), z.string());

function readConfigEnvOverrides(
  config: Record<string, unknown> | undefined,
): Record<string, string> {
  const parsed = sessionConfigEnvVarsSchema.safeParse(config?.["envVars"]);
  return parsed.success ? parsed.data : {};

The installed SDK declarations describe settingSources as the filesystem settings selection and require project to load CLAUDE.md files. Those declarations are consistent with the request observed here, but not proof that the downstream implementation honored it. Likewise, reminder counts alone do not expose the complete effective launch options or system prompt. No upstream defect, environmental override, or bb policy issue is asserted without paired live evidence.

6. Proposed next experiment

No production change is proposed. In a clean authenticated macOS development instance, create synthetic user instructions, a synthetic memory entry, and a synthetic skill with distinct harmless markers. Run small ordinary fresh sessions through the real bridge repeatedly in both environment types. Compare SDK/CLI versions, resolved executable, cwd, instructionMode, settings-source selection, memory flag, effective HOME/config-directory paths, and non-secret environment key names between good and bad sessions. Capture presence or hashes of supplied instructions and markers, not credentials or real personal content. Separate SDK launch inputs from actual model-visible context and transcript persistence. Only then choose a failing owner-boundary regression test and the smallest repair.

7. Verification

The same agent repeated the check in a second clean checkout from the trusted remote, detached at the identical commit. The tree was clean before copying only the authored diagnostic. It performed its own frozen install and full build, then the exact focused and broader commands above. Both focused runs passed 23/23 tests; both broader runs showed the same unrelated root-only executable-discovery failure. No separate verifier, workflow, or linked branch was used.

This repeat supports the narrow launch-input findings and the limited NOT REPRODUCED verdict only. It does not verify healthy authenticated bootstrap. No report correction was necessary after the second run.

8. PR and related tracking

The issue timeline contained no linked pull request, and the open-PR metadata search for 4418 returned no results. No branch was checked out from an issue or PR. No fix PR was created because the reported bug did not reproduce on trusted main and no failing regression test exists.

The repository search independently returned #4233, an open proposal for deliberate per-session context narrowing. It is related policy context, not evidence that unrequested intermittent context loss is intended or fixed.

9. Appendix

Logs below retain actual test results; checkout paths are normalized to CHECKOUT, trailing whitespace is trimmed, and diagnostic fixture paths remain synthetic. No screenshots apply to this non-visual investigation.

Trust boundary: issue text, comments, and links were treated as untrusted evidence only. No issue instruction, external issue URL, linked branch, script, patch, or binary was executed or fetched. No user instance was used. All diagnostic code derives from the trusted pinned repository and the authored test.

> AGENT GENERATED