> ## Documentation Index
> Fetch the complete documentation index at: https://braintrust.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshooting

> Diagnose and fix common issues when installing or using the Braintrust TypeScript SDK.

<AccordionGroup>
  <Accordion title="API URL conflicts in self-hosted deployments">
    **Issue:** Streaming logs fail with initialization or forbidden errors when `apiUrl`, `appUrl`, or `BRAINTRUST_API_URL` point to conflicting environments.

    **Cause:** The SDK sends logging requests to the API/data plane URL. If `apiUrl` is set, it takes precedence over `appUrl`.

    **Fix:** For Braintrust-hosted projects, remove custom URL settings unless you use a custom frontend. For self-hosted or hybrid projects, set only `apiUrl` or `BRAINTRUST_API_URL` to the data plane endpoint.

    ```typescript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    import { initLogger } from "braintrust";

    initLogger({
      projectName: process.env.BRAINTRUST_PROJECT_NAME,
      apiKey: process.env.BRAINTRUST_API_KEY,
      apiUrl: "https://your-data-plane.example.com",
    });
    ```
  </Accordion>

  <Accordion title="Evals fail from resource exhaustion or EMFILE errors">
    **Issue:** Evals fail, time out, produce incomplete results, or hit `EMFILE: too many open files`.

    **Cause:** `Eval()` runs tasks with unlimited concurrency unless `maxConcurrency` is set. That can exhaust file descriptors, memory, database pools, or model-provider rate limits.

    **Fix:** Set `maxConcurrency` to a small value such as `10`, then tune up or down based on the workload.

    ```typescript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    import { Eval } from "braintrust";

    await Eval("my-project", {
      data: myDataset,
      task: myTask,
      scores: [myScorer],
      maxConcurrency: 10,
    });
    ```
  </Accordion>

  <Accordion title="Eval process hangs after completion">
    **Issue:** `braintrust eval` prints results successfully, but the Node.js process does not exit.

    **Cause:** Open handles in your eval process can keep the Node.js event loop alive. Common sources include database pools, Redis clients, WebSockets, and HTTP keep-alive agents.

    **Fix:** Run the eval with the Node inspector to identify active handles, then explicitly close external resources after the eval finishes.

    ```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    node --inspect-brk ./node_modules/.bin/braintrust eval your-eval.ts
    ```

    ```typescript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    process.on("beforeExit", async () => {
      await mongoClient.close();
      await redis.disconnect();
    });
    ```
  </Accordion>

  <Accordion title="Traces are missing, empty, or stuck in progress">
    **Issue:** Traces don't appear, show empty fields, never leave "in progress", or don't include final values.

    **Cause:** A process can exit before logs flush, or an exception can bypass `end()` on a manually-created span.

    **Fix:** Prefer `traced()` — it ends the span for you, even when the wrapped function throws. If you use `startSpan()` directly, call `end()` in `finally`. Either way, call `flush()` before a short-lived process exits.

    ```typescript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    import { flush, startSpan, traced } from "braintrust";

    await traced(async (span) => {
      span.log({ input, output });
    });

    const span = startSpan({ name: "manual-operation" });
    try {
      span.log({ input });
    } finally {
      span.end();
      await flush();
    }
    ```
  </Accordion>

  <Accordion title="Trace continuity breaks after restarts">
    **Issue:** Follow-up turns in an existing conversation stop appearing under the original trace after a server restart or pod replacement.

    **Cause:** Exported span context from `span.export()` was kept only in memory. After restart, the SDK cannot continue the previous trace.

    **Fix:** Persist the exported span context. On the next request, wrap the work in `withParent()`, or pass the stored value as `parent` to `traced()` or `startSpan()`.

    ```typescript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    import { traced, withParent } from "braintrust";

    const parent = await traced((span) => span.export(), { name: "conversation" });
    await db.set(conversationId, parent);

    const storedParent = await db.get(conversationId);
    if (storedParent) {
      await withParent(storedParent, async () => {
        await traced(async (span) => {
          span.log({ input: nextMessage });
        });
      });
    }
    ```
  </Accordion>

  <Accordion title="Prompt version IDs do not match the UI">
    **Issue:** `prompt.version` returns a large decimal string, while the Braintrust UI shows a short hexadecimal version ID.

    **Cause:** The SDK returns the raw transaction ID. The UI displays a prettified, reversible version of the same ID.

    **Fix:** Convert SDK values to the UI form with `prettifyXact()`, or back with `loadPrettyXact()`. Both are exported from `braintrust/util`.

    ```typescript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    import { loadPrettyXact, prettifyXact } from "braintrust/util";

    const uiVersion = prettifyXact(prompt.version);
    const rawVersion = loadPrettyXact(uiVersion);
    ```
  </Accordion>

  <Accordion title="Prompt shows None in experiment view">
    **Issue:** The experiment view shows `Prompt: None`, even though your task uses a prompt.

    **Cause:** On LLM spans, the UI detects prompts from prompt metadata. Hard-coded prompts or prompts sent without the Braintrust prompt workflow do not attach that metadata.

    **Fix:** Fetch the prompt with `loadPrompt()`, call `prompt.build({ input })` to produce the messages and parameters, then send them through a wrapped client (`wrapOpenAI`, `wrapAnthropic`, etc.). The wrapper reads the metadata from `build()` and attaches it to the LLM span.

    ```typescript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    import { loadPrompt, wrapOpenAI } from "braintrust";
    import OpenAI from "openai";

    const client = wrapOpenAI(new OpenAI());
    const prompt = await loadPrompt({
      projectName: "my-project",
      slug: "summarizer",
    });

    const { messages, ...parameters } = prompt.build({ input });

    await client.responses.create({
      ...parameters,
      input: messages,
    });
    ```
  </Accordion>

  <Accordion title="GPT-5 prompts include unsupported temperature">
    **Issue:** A prompt configured for a GPT-5 model returns `temperature` from `prompt.build()`, and passing those parameters to OpenAI fails.

    **Cause:** Braintrust prompts can store `temperature` regardless of the selected model, but GPT-5 models reject `temperature` on the OpenAI API.

    **Fix:** Strip `temperature` from the parameters before sending the request, as shown below.

    ```typescript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    const { temperature: _temperature, messages, ...parameters } = prompt.build(variables);

    await client.responses.create({
      ...parameters,
      input: messages,
    });
    ```
  </Accordion>

  <Accordion title="No traces sent from a deployment on Vercel">
    **Issue:** You see only some traces, or none at all, from a deployment on Vercel.

    **Cause:** Vercel freezes the function as soon as it returns a response, so events still in Braintrust's buffer may never flush.

    **Fix:** Call `flush()` in your application code after your AI calls. On Vercel, `flush()` sends buffered data even after the response has been returned.

    ```typescript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    await client.responses.create();

    // Flushes buffered data, even on Vercel. flush() returns a Promise but does not need to be awaited.
    flush();
    ```
  </Accordion>
</AccordionGroup>
