Skip to main content
This page is organized by symptom. Most issues are trace-shape problems: the data arrives, but it does not match the trace contract. For a visual reference of malformed vs conformant traces, see Good trace vs bad trace.
When behavior is unclear, first enable Debug mode in the same runtime that serves traffic. It shows whether the SDK is running, which trace it tries to send, how many child spans were included, and whether the ingest request succeeds.

No traces in Lemma

Cause: credentials, project ID, base URL, or outbound network access is wrong. Fix: create one Lemma client with your API key and project ID, then send a tiny test trace.
import { Lemma } from "@uselemma/tracing";

const lemma = new Lemma({
  apiKey: process.env.LEMMA_API_KEY,
  projectId: process.env.LEMMA_PROJECT_ID,
});

await lemma.trace({ name: "smoke-test", input: "hello" }, async () => "ok");
  • Confirm LEMMA_API_KEY and LEMMA_PROJECT_ID come from the same project.
  • Confirm your runtime can reach https://api.uselemma.ai.
  • Pass baseUrl / base_url only when using a self-hosted or staging endpoint.
Debug with Debug mode: enable LEMMA_DEBUG=true and run the smoke test again. If you do not see SDK logs, the traced code path is not running in that process. If you see an ingest failure, check the status code, credentials, project ID, and baseUrl.

Every span appears as a separate trace

Cause: child work is being recorded outside the lemma.trace() callback, so the SDK cannot attach it to the root trace. Fix: make lemma.trace() the boundary around the whole agent run, and record tools, generations, and spans inside that callback.
const answer = await lemma.trace(
  { name: "support-agent", input: userMessage, threadId, userId },
  async (trace) => {
    const docs = await searchDocs(userMessage);
    trace.recordTool({ name: "search_docs", input: { query: userMessage }, output: docs });

    const response = await callModel(userMessage, docs);
    trace.recordGeneration({
      name: "answer",
      model: response.model,
      input: response.messages,
      output: response.text,
      usage: response.usage,
    });

    return response.text;
  },
);
Across queues, workers, or separate services, start a new top-level trace and pass the same threadId / userId so turns remain connected. If one logical agent execution spans multiple processes, prefer recording the final trace in the coordinator that has the complete input, output, tools, and model calls. Debug with Debug mode: compare logs from the request handler and any background worker. If the child work runs after the lemma.trace() callback has returned, it will not attach to the active trace. Move the recording into the callback, or pass IDs and use the detached record* helpers from the coordinator.

Tool calls are missing

Cause: provider instrumentation captures model calls but not your application tools, or the tool result is never recorded. Fix: call trace.recordTool() after each tool completes.
const result = await lookupOrder(orderId);
trace.recordTool({
  name: "lookup_order",
  input: { orderId },
  output: result,
});
For failures, record the error before re-raising:
try {
  return await chargeCard(payload);
} catch (error) {
  trace.recordTool({ name: "charge_card", input: payload, error });
  throw error;
}
Debug with Debug mode: check the spanCount (span_count in Python) on the sending trace log. If the count does not increase after you add recordTool(...), the tool recording code path is not running or is running outside the trace.

Inputs or outputs are blank

Cause: the trace was created without input, and the callback did not return or set the final output. Fix: pass input to lemma.trace() and return the final answer. Use trace.output() only when the callback return value is not the user-visible output.
await lemma.trace({ name: "support-agent", input: userMessage }, async (trace) => {
  const answer = await runAgent(userMessage);
  trace.output(answer);
  return answer;
});
Debug with Debug mode: confirm the trace logs trace sent, then open that trace in Lemma. If root output is still blank, the callback likely returned undefined, streamed the answer elsewhere, or exited before setting trace.output(...).

Model or tokens are missing

Cause: the LLM call is recorded as a generic span, so Lemma has no model name or token usage to read. Fix: record model calls with trace.recordGeneration().
trace.recordGeneration({
  name: "draft-reply",
  model: "gpt-4o",
  input: messages,
  output: response.text,
  usage: {
    inputTokens: response.usage.inputTokens,
    outputTokens: response.usage.outputTokens,
  },
});
See Generations. Debug with Debug mode: check whether spanCount (span_count in Python) increases when the model call runs. If it does but the dashboard still lacks model metadata, switch that call site from generic span recording to recordGeneration() / record_generation() and include model and usage.

Agent name is blank

Cause: no stable name is attached to the trace, so traces cannot be grouped or filtered by workflow. Fix: set name on every lemma.trace() call.
await lemma.trace({ name: "support-agent", input: userMessage }, async (trace) => {
  return runAgent(userMessage, trace);
});
Debug with Debug mode: check the name on trace started or sending trace. It should be the stable workflow name you expect, not a generated fallback.

Trace Nesting Is Flat

Cause: long-running subtasks are recorded as sibling observations when they should have their own parent span. Fix: use trace.startSpan() when a subtask has nested work, then record children from that span handle. In Python, use trace.start_span() to measure the subtask and record child observations on the active trace.
const retrieve = trace.startSpan({ name: "retrieve-context", input: { query } });
try {
  const docs = await searchDocs(query);
  retrieve.recordTool({ name: "search_docs", input: { query }, output: docs });
  retrieve.end({ output: { count: docs.length } });
  return docs;
} catch (error) {
  retrieve.end({ error });
  throw error;
}
support-agent
├─ retrieve-context
│  └─ search_docs
└─ answer
Debug with Debug mode: confirm spanCount (span_count in Python) includes both the parent span and the child tool, then inspect the trace in Lemma. If they appear as siblings, record the child from the span handle, for example retrieve.recordTool(...), before ending the parent span.

Serverless traces are delayed or missing

Cause: the request exits before the SDK request finishes, or a background task records work after the trace callback has returned. Fix: await lemma.trace(...) inside the handler and keep trace recording inside the callback. The SDK sends the completed payload before lemma.trace() resolves. Debug with Debug mode: check whether the ingest log appears before the function returns. If it appears after the response is sent, or never appears, await the trace promise and avoid detached background recording unless you explicitly flush or end the trace handle.

Traces render but there are no issues or incidents

Cause: the trace renders, but it is not in a shape Lemma recognizes for automated issue detection. Fix: match the contract: one root trace, input/output, typed generation children, typed tool children, error states, and thread/user context where available.
  • Use lemma.trace() for the root.
  • Use trace.recordGeneration() for model calls.
  • Use trace.recordTool() for tool calls.
  • Use trace.recordSpan() for completed app work or trace.startSpan() for live app work.
  • Pass threadId and userId for multi-turn analysis.
Debug with Debug mode: confirm the SDK logs trace sent with the expected trace name and child spanCount (span_count in Python), then compare the dashboard trace against the contract.