fix(lens-ui): open traces on the top-level step and stop showing model names as input (#45254)

* fix(lens-ui): open a trace on its top-level step instead of the first failed one

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(lens-ui): pin that failed runs open on the root step

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(lens-ui): drop the unused firstErrorSpan helper

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(lens-ui): stop testing the removed firstErrorSpan helper

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(lens-ui): stop showing the model name as a run's input when none was recorded

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(lens-ui): cover the trace list row with no recorded input

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ishaan-berri 2026-10-07 19:26:50 -07:00 • committed by GitHub
parent 4f49f88172
commit e4d9a591dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 50 additions and 85 deletions

View file

@ -113,19 +113,12 @@ describe("RunView", () => {
expect(screen.getByRole("banner")).toHaveTextContent(`Step errors ${swarm.summary.error_count}`);
});
it("opens a failed run on its first failed span", async () => {
renderRun(swarm);
const pane = await screen.findByTestId("detail-pane");
const { selectedId } = initialRunSelection(swarm);
expect(selectedId).not.toBe(rootSpanId(swarm));
expect(pane).toHaveAttribute("data-row-id", selectedId);
expect(swarm.spans.find((s) => s.span_id === selectedId)?.status).toBe("error");
});
it("opens a healthy run on the root span", async () => {
renderRun(research);
expect(await screen.findByTestId("detail-pane")).toHaveAttribute("data-row-id", rootSpanId(research));
it.each([
["failed", swarm],
["healthy", research],
])("opens a %s run on the root span", async (_label, trace) => {
renderRun(trace);
expect(await screen.findByTestId("detail-pane")).toHaveAttribute("data-row-id", rootSpanId(trace));
});
it("inside the drawer moves spans with the arrow keys and leaves J / K to switch runs", async () => {
@ -191,7 +184,7 @@ describe("RunView", () => {
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent(traceDisplayName(swarm.summary)),
);
expect(screen.getByTestId("run-view")).toHaveAttribute("aria-busy", "false");
expect(screen.getByTestId("detail-pane")).toHaveAttribute("data-row-id", initialRunSelection(swarm).selectedId);
expect(screen.getByTestId("detail-pane")).toHaveAttribute("data-row-id", rootSpanId(swarm));
});
it("moves the selection with J / K and closes the detail pane with Esc", async () => {
@ -712,36 +705,19 @@ describe("initialRunSelection", () => {
return { ...base, ...defaults, ...over };
};
it("lands on a visible failure, never on a framework span the tree hides", () => {
const hiddenFields: Partial<Span> = { span_id: "mw", type: "framework", status: "error", start_offset_ms: 1 };
const toolFields: Partial<Span> = { span_id: "tool", type: "tool", status: "error", start_offset_ms: 5 };
const hiddenFailure = child(hiddenFields);
const toolFailure = child(toolFields);
const trace = { ...research, spans: [base, hiddenFailure, toolFailure] };
expect(initialRunSelection(trace).selectedId).toBe("tool");
});
it("folds other agent branches while revealing the failed step", () => {
const first = child({ span_id: "first", type: "agent" });
const second = child({ span_id: "second", type: "agent" });
const failureFields: Partial<Span> = { span_id: "failed", parent_span_id: "second", type: "tool", status: "error" };
const failure = child(failureFields);
const { selectedId, state } = initialRunSelection({ ...research, spans: [base, first, second, failure] });
expect(selectedId).toBe("failed");
expect(state.collapsedSpanIds.has("first")).toBe(true);
expect(state.collapsedSpanIds.has("second")).toBe(false);
});
it("falls back to the nearest visible ancestor when only a hidden span failed", () => {
const agent = child({ span_id: "agent", type: "agent", name: "researcher" });
const hiddenFields: Partial<Span> = { span_id: "mw", parent_span_id: "agent", type: "framework", status: "error" };
const hiddenFailure = child(hiddenFields);
const trace = { ...research, spans: [base, agent, hiddenFailure] };
expect(initialRunSelection(trace).selectedId).toBe("agent");
it("opens on the root and folds nested agent branches even when a step failed", () => {
const agentFields: Partial<Span> = { span_id: "agent", type: "agent" };
const failureFields: Partial<Span> = { span_id: "failed", parent_span_id: "agent", type: "tool", status: "error" };
const { selectedId, state } = initialRunSelection({
...research,
spans: [base, child(agentFields), child(failureFields)],
});
expect(selectedId).toBe(base.span_id);
expect(state.collapsedSpanIds.has("agent")).toBe(true);
});
});
it("opens a cited span instead of the default failed span", () => {
it("opens a cited span instead of the root", () => {
const cited = research.spans.find((span) => span.parent_span_id !== null)!;
expect(initialRunSelection(research, cited.span_id).selectedId).toBe(cited.span_id);
expect(initialRunSelection(research, "missing")).toEqual(initialRunSelection(research));

View file

@ -3,15 +3,7 @@ import { useCallback, useMemo, useState } from "react";
import type { RunSelection } from "../../routing";
import type { SpanTreeState, TreeRow } from "../../tree";
import type { Trace } from "../../types";
import {
buildTreeRows,
findTraceSteps,
firstErrorSpan,
GROUP_PAGE_SIZE,
isFrameworkSpan,
nearestVisibleSpanId,
revealSpanInState,
} from "../../utils";
import { buildTreeRows, findTraceSteps, GROUP_PAGE_SIZE, nearestVisibleSpanId, revealSpanInState } from "../../utils";
const INITIAL_STATE: SpanTreeState = {
hideFramework: true,
@ -20,7 +12,7 @@ const INITIAL_STATE: SpanTreeState = {
groupRevealCounts: {},
};
/** First failed span if the run has errors (with its tree path opened), otherwise the root agent. */
/** The linked step when one is given, otherwise the root agent with nested agent branches folded. */
export function initialRunSelection(
trace: Trace,
initialSpanId?: string,
@ -30,22 +22,14 @@ export function initialRunSelection(
const state = revealSpanInState(trace.spans, { ...INITIAL_STATE, hideFramework: false }, selectedId);
return { selectedId, state };
}
const initialState = {
const root = trace.spans.find((span) => span.parent_span_id === null);
const state = {
...INITIAL_STATE,
collapsedSpanIds: new Set(
trace.spans.filter((span) => span.type === "agent" && span.parent_span_id !== null).map((span) => span.span_id),
),
};
const failed = firstErrorSpan(trace.spans);
if (!failed || failed.parent_span_id === null) {
const root = trace.spans.find((s) => s.parent_span_id === null);
return { selectedId: root?.span_id ?? "", state: initialState };
}
const visibleFailure = trace.spans
.filter((s) => s.status === "error" && s.parent_span_id !== null && !isFrameworkSpan(s))
.sort((a, b) => a.start_offset_ms - b.start_offset_ms)[0];
const selectedId = visibleFailure?.span_id ?? nearestVisibleSpanId(trace.spans, failed.span_id, true);
return { selectedId, state: revealSpanInState(trace.spans, initialState, selectedId) };
return { selectedId: root?.span_id ?? "", state };
}
const toggle = (set: ReadonlySet<string>, id: string): Set<string> => {

View file

@ -183,6 +183,26 @@ describe("AgentTracesTable input cell", () => {
);
expect(screen.getByText("CURRENT USER REQUEST: - add feedback to Lens")).toBeInTheDocument();
});
it("says no input was recorded instead of passing the run's model name off as its input", () => {
const template = (traceList as TracePage).data[0] as TraceSummary;
const run = { ...template, name: "chat openai/gpt-6-astra", input_preview: "" };
renderWithProviders(
inList(
<AgentTracesTable
traces={[run]}
findings={new Map()}
isLoading={false}
error={null}
hasMore={false}
onLoadMore={vi.fn()}
onSetUpTracing={vi.fn()}
/>,
),
);
expect(screen.getByText("No input recorded")).toBeInTheDocument();
expect(screen.queryByText(run.name)).not.toBeInTheDocument();
});
});
describe("AgentTracesTable column picker", () => {

View file

@ -20,7 +20,7 @@ import { SignalPills } from "../ui/SignalPills";
import { FrameworkLogo, traceFramework } from "../ui/TraceFramework";
import type { TraceSummary } from "../types";
import { traceRefOf } from "../routing";
import { fmtMs, previewText, traceDisplayName, traceAgentNames } from "../utils";
import { fmtMs, previewText, traceAgentNames } from "../utils";
interface AgentTracesTableProps {
traces: TraceSummary[];
@ -103,11 +103,14 @@ function AgentCell({ run }: { run: TraceSummary }) {
}
function InputCell({ run }: { run: TraceSummary }) {
const input = singleLine(previewText(run.input_preview));
return (
<div className="flex min-w-0 items-center gap-2">
<span className="truncate text-foreground">
{singleLine(previewText(run.input_preview)) || traceDisplayName(run)}
</span>
{input ? (
<span className="truncate text-foreground">{input}</span>
) : (
<span className="truncate text-muted-foreground">No input recorded</span>
)}
{run.resolution_limited && (
<span
className="shrink-0 text-xs text-muted-foreground"

View file

@ -9,7 +9,6 @@ import {
buildTreeRows,
buildVisibleTree,
errorSource,
firstErrorSpan,
findTraceSteps,
fmtMs,
GROUP_PAGE_SIZE,
@ -317,7 +316,7 @@ describe("buildTreeRows", () => {
describe("revealSpanInState", () => {
it("opens the path to a span nested in a folded group so the view can land on it", () => {
const failed = firstErrorSpan(swarm.spans) as Span;
const failed = swarm.spans.find((s) => s.status === "error" && s.parent_span_id !== null) as Span;
const state = revealSpanInState(swarm.spans, STATE, failed.span_id);
const rows = buildTreeRows(swarm.spans, state);
expect(rows.some((r) => r.id === failed.span_id)).toBe(true);
@ -345,13 +344,6 @@ describe("errorSource", () => {
});
describe("payload helpers", () => {
it("finds the earliest failing non-root span", () => {
const failed = firstErrorSpan(swarm.spans);
expect(failed?.status).toBe("error");
expect(failed?.parent_span_id).not.toBeNull();
expect(firstErrorSpan(deepAgent.spans)).toBeNull();
});
it("parses llm message payloads and rejects non-message JSON", () => {
expect(parseMessages('[{"role":"user","content":"hi"}]')).toEqual([{ role: "user", content: "hi" }]);
expect(parseMessages('{"file_path":"/tmp/x"}')).toBeNull();

View file

@ -254,16 +254,6 @@ export function nearestVisibleSpanId(spans: readonly Span[], spanId: string, hid
return current?.span_id ?? spanId;
}
/* ------------------------------------------------------------------ */
/* Trace-level rollups */
/* ------------------------------------------------------------------ */
/** Earliest failing non-root span (the root just echoes its children), else the root. */
export function firstErrorSpan(spans: readonly Span[]): Span | null {
const failed = spans.filter((s) => s.status === "error").sort(byStart);
return failed.find((s) => s.parent_span_id !== null) ?? failed[0] ?? null;
}
/* ------------------------------------------------------------------ */
/* Span detail payloads */
/* ------------------------------------------------------------------ */