diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cdd34e..deaf021 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- A failing `GET /api/v1/bash-commands` no longer renders as the "no command detail in this data" explainer. The Bash section branched on row count alone, so a request that errored looked identical to one that legitimately returned nothing, blaming Claude Code's telemetry for what was actually a server fault. Fetch failures now show the error + +### Changed +- Tools page is flat: the tools table sits directly on the page instead of inside an "All Tools" card. `DataTable` already draws its own bordered surface, so the card put a box inside a box for a heading the page title already gave. Matches the Users page +- The Bash commands section no longer vanishes when it has no rows — it always renders and says why it is empty. Claude Code's tool spans carry `tool_name`, `tool_use_id` and `duration_ms` only: which tool ran, not what it ran. No `command` attribute is ever sent (verified against a captured live session, including the enhanced-telemetry beta), so this breakdown stays empty against Claude Code telemetry no matter what — the DuckDB filter-pushdown fix in 0.3.0 was a real bug fix but could not, on its own, put rows in this table. The endpoint is kept for OTLP producers that do send `command` + ## [0.3.0] - 2026-08-09 ### Fixed diff --git a/docs/operations/api-reference.md b/docs/operations/api-reference.md index b9f70be..4a23501 100644 --- a/docs/operations/api-reference.md +++ b/docs/operations/api-reference.md @@ -82,6 +82,12 @@ Per-command breakdown of `Bash` tool calls. The command text is read from the `command` span attribute, falling back to `command` inside a JSON-encoded `tool_input`. +**Claude Code does not send either attribute.** Its tool spans carry `tool_name`, +`tool_use_id` and `duration_ms` only — which tool ran, not what it ran — so +against Claude Code telemetry this endpoint returns an empty `items` list and the +dashboard's Bash commands section stays empty. The endpoint is kept for OTLP +producers that do send a `command` attribute; cotel's ingest is generic. + | Sort key | Orders by | |---|---| | `command` | Command text | diff --git a/frontend/src/pages/Tools.module.css b/frontend/src/pages/Tools.module.css index 39f5f61..1651ee6 100644 --- a/frontend/src/pages/Tools.module.css +++ b/frontend/src/pages/Tools.module.css @@ -27,10 +27,21 @@ white-space: nowrap; } +.section { + margin-top: var(--space-6); +} + +.sectionTitle { + font-size: var(--text-lg); + font-weight: 600; + color: var(--color-text-1); + margin: 0 0 var(--space-1); +} + .sectionNote { font-size: var(--text-sm); color: var(--color-text-3); - margin-bottom: var(--space-3); + margin: 0 0 var(--space-3); } .commandCell { diff --git a/frontend/src/pages/Tools.tsx b/frontend/src/pages/Tools.tsx index 835d90e..24b0ce7 100644 --- a/frontend/src/pages/Tools.tsx +++ b/frontend/src/pages/Tools.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react' import { useSearchParams } from 'react-router-dom' import { Search, ChevronLeft, ChevronRight } from 'lucide-react' import { useTools, useBashCommands } from '../api' -import { Card, DataTable, EmptyState, ErrorState, LoadingSkeleton, SegmentedControl, failRateBadge } from '../components' +import { DataTable, EmptyState, ErrorState, LoadingSkeleton, SegmentedControl, failRateBadge } from '../components' import type { ToolItem, BashCommandItem } from '../api' import type { Column, SortState } from '../components' import { RANGE_OPTIONS, useRangeCookie } from '../lib/range' @@ -78,7 +78,7 @@ export default function Tools() { user_id: userId, }) - const { data: bashData, isLoading: bashLoading } = useBashCommands({ + const { data: bashData, error: bashError, isLoading: bashLoading } = useBashCommands({ range, sort: bashSort, order: bashOrder, @@ -174,62 +174,67 @@ export default function Tools() { ) : error ? ( + ) : total === 0 ? ( + q ? ( +
No tools match "{q}"
+ ) : ( + + ) ) : ( - - {total === 0 ? ( - q ? ( -
No tools match "{q}"
- ) : ( - - ) - ) : ( - <> - - columns={toolColumns} - rows={data!.items} - sort={{ key: sort as keyof ToolItem, dir: order }} - onSortChange={handleSortChange} - /> - {data?.duration_stats_since && ( -

- Duration and error figures start from {formatDay(data.duration_stats_since)} — earlier - days were rolled up before those totals were recorded, so they count toward Calls only. -

- )} - - + <> + + columns={toolColumns} + rows={data!.items} + sort={{ key: sort as keyof ToolItem, dir: order }} + onSortChange={handleSortChange} + /> + {data?.duration_stats_since && ( +

+ Duration and error figures start from {formatDay(data.duration_stats_since)} — earlier + days were rolled up before those totals were recorded, so they count toward Calls only. +

)} -
+ + )} - {bashLoading && !bashData ? ( - +
+

Bash commands

+

+ Each distinct command passed to the Bash tool, read from the command span + attribute. +

+ + {bashLoading && !bashData ? ( - - ) : bashTotal > 0 ? ( - -

- Each distinct command passed to the Bash tool. Commands are extracted from the{' '} - command span attribute. -

- - columns={bashColumns} - rows={bashData!.items} - sort={{ key: bashSort as keyof BashCommandItem, dir: bashOrder }} - onSortChange={handleBashSortChange} + ) : bashError ? ( + + ) : bashTotal === 0 ? ( + - {bashData?.covered_since && ( -

- This breakdown starts from {formatDay(bashData.covered_since)} — older activity is kept - only as daily totals, which carry no command detail. -

- )} - -
- ) : null} + ) : ( + <> + + columns={bashColumns} + rows={bashData!.items} + sort={{ key: bashSort as keyof BashCommandItem, dir: bashOrder }} + onSortChange={handleBashSortChange} + /> + {bashData?.covered_since && ( +

+ This breakdown starts from {formatDay(bashData.covered_since)} — older activity is kept + only as daily totals, which carry no command detail. +

+ )} + + + )} +
) } diff --git a/internal/dashboard/static/index.html b/internal/dashboard/static/index.html index b5eb3d3..8d11231 100644 --- a/internal/dashboard/static/index.html +++ b/internal/dashboard/static/index.html @@ -5,8 +5,8 @@ cotel - - + +