+ {instancesError !== null ? (
+
+ {instancesError}
+
+ ) : null}
@@ -487,7 +553,16 @@ export function AgentsPage({
{definitions.map((definition) => {
const isSelected = selection.isSelected(definition.id);
- const status = agentRosterStatus(definition, instances);
+ const runs = agentRunsSettledContent(
+ definition.id,
+ instances,
+ now,
+ instancesError,
+ );
+ const status =
+ runs.kind === "error"
+ ? null
+ : agentRosterStatus(definition, instances);
return (
-
- {status === "running" ? (
-
- ) : null}
-
- {AGENT_ROSTER_STATUS_LABEL[status]}
-
-
+ {status === null ? (
+ —
+ ) : (
+
+ {status === "running" ? (
+
+ ) : null}
+
+ {AGENT_ROSTER_STATUS_LABEL[status]}
+
+
+ )}
{tenantId !== null ? (
@@ -581,7 +660,11 @@ export function AgentsPage({
)}
- {runsInLast7Days(definition.id, instances, now)}
+ {runs.kind === "error" ? (
+ —
+ ) : (
+ runs.value
+ )}
);
@@ -645,9 +728,9 @@ export function AgentsRoute({
const queryClient = useQueryClient();
const directory = useAgentDirectory(selectedTenantId ?? undefined);
const activity = useBenchActivity(selectedTenantId);
- // Powers the roster's Status and "Runs · 7d" columns; a failed fetch here
- // degrades those two columns to Idle/0 rather than blocking the page —
- // the definitions listing above is what makes the page usable at all.
+ // Powers the roster's Status and "Runs · 7d" columns. A failed fetch must
+ // not degrade those columns to Idle/0 (CL-6842) — the definitions listing
+ // still makes the page usable, but Status/Runs admit the load failed.
const runsQuery = useQuery({
queryKey: ["agent-top-level-runs", selectedTenantId],
queryFn: () => listTopLevelRuns(selectedTenantId as string),
@@ -695,6 +778,11 @@ export function AgentsRoute({
definitions={definitions}
workbenches={workbenches}
instances={runsQuery.data ?? []}
+ instancesError={
+ runsQuery.isError
+ ? describeApiError(runsQuery.error, "loading run history")
+ : null
+ }
selectedId={selectedId}
onSelect={(id) =>
navigate(
diff --git a/apps/web/test/agents-page.test.tsx b/apps/web/test/agents-page.test.tsx
index 3281dc420..d53f83d30 100644
--- a/apps/web/test/agents-page.test.tsx
+++ b/apps/web/test/agents-page.test.tsx
@@ -9,7 +9,10 @@ import { renderToStaticMarkup } from "react-dom/server";
import {
AgentsPage,
+ AgentModelCellView,
+ agentModelSettledContent,
agentRosterStatus,
+ agentRunsSettledContent,
archiveDefinitions,
archiveResultToast,
runsInLast7Days,
@@ -102,6 +105,98 @@ describe("runsInLast7Days", () => {
});
});
+describe("agentModelSettledContent (CL-6848)", () => {
+ test("a fetch failure is an error, never the same label as an unset model", () => {
+ expect(
+ agentModelSettledContent({
+ status: "error",
+ message: "Something went wrong loading this agent's model. Try again.",
+ }),
+ ).toEqual({
+ kind: "error",
+ message: "Something went wrong loading this agent's model. Try again.",
+ });
+ expect(
+ agentModelSettledContent({
+ status: "ready",
+ data: { name: "triage-bot" },
+ }),
+ ).toEqual({ kind: "model", label: "Default" });
+ });
+
+ test("a ready model name passes through", () => {
+ expect(
+ agentModelSettledContent({
+ status: "ready",
+ data: { name: "triage-bot", model: "claude-sonnet-4" },
+ }),
+ ).toEqual({ kind: "model", label: "claude-sonnet-4" });
+ });
+});
+
+describe("AgentModelCellView (CL-6848)", () => {
+ test("a capabilities fetch error is visually distinct from an unset model", () => {
+ const errorMarkup = renderToStaticMarkup(
+ ,
+ );
+ const unsetMarkup = renderToStaticMarkup(
+ ,
+ );
+ expect(errorMarkup).toContain("text-destructive");
+ expect(errorMarkup).toContain('role="alert"');
+ expect(errorMarkup).toContain("loading this agent's model");
+ expect(errorMarkup).not.toContain("text-muted-foreground");
+ expect(unsetMarkup).toContain("Default");
+ expect(unsetMarkup).toContain("text-muted-foreground");
+ expect(unsetMarkup).not.toContain("text-destructive");
+ // The muted em-dash is the absent-value glyph elsewhere on the page —
+ // a fetch failure must not reuse it.
+ expect(errorMarkup).not.toContain(">—<");
+ });
+});
+
+describe("agentRunsSettledContent (CL-6842)", () => {
+ test("a runs fetch failure is not the same as an honest empty history", () => {
+ expect(
+ agentRunsSettledContent("wfd_1", [], NOW, "Couldn't load run history"),
+ ).toEqual({
+ kind: "error",
+ message: "Couldn't load run history",
+ });
+ expect(agentRunsSettledContent("wfd_1", [], NOW, null)).toEqual({
+ kind: "count",
+ value: 0,
+ });
+ });
+
+ test("a successful fetch still reports the trailing-week count", () => {
+ expect(
+ agentRunsSettledContent(
+ "wfd_1",
+ [
+ instance({
+ definitionId: "wfd_1",
+ createdAt: "2026-08-19T00:00:00.000Z",
+ }),
+ ],
+ NOW,
+ null,
+ ),
+ ).toEqual({ kind: "count", value: 1 });
+ });
+});
+
describe("archiveDefinitions", () => {
test("one id failing does not roll back or hide the ids that succeeded", async () => {
const result = await archiveDefinitions(
@@ -393,4 +488,53 @@ describe("AgentsPage", () => {
);
expect(markup).not.toContain("Could not load agent skills");
});
+
+ test("an honest empty run history shows 0, not a load-failure marker (CL-6842)", () => {
+ const markup = renderToStaticMarkup(
+ ,
+ );
+ expect(markup).toContain("Idle");
+ expect(markup).toContain(">0<");
+ expect(markup).not.toContain("Couldn't load run history");
+ });
+
+ test("a runs fetch failure is distinguishable from zero instances (CL-6842)", () => {
+ const markup = renderToStaticMarkup(
+ ,
+ );
+ expect(markup).toContain("text-destructive");
+ expect(markup).toContain('role="alert"');
+ expect(markup).toContain(
+ "Something went wrong loading run history. Try again.",
+ );
+ // Must not claim an Idle status or a literal zero off fabricated empty
+ // instances — that is the dishonest path `runsQuery.data ?? []` produced.
+ expect(markup).not.toContain("Idle");
+ expect(markup).not.toContain(">0<");
+ });
});