Repository navigation
fix(skills): ns-analyze-tracing builds the waterfall with tracing-detail - #76
Conversation
The skill said tracing-detail isn't exposed over MCP and told agents to look a trace up with tracing's span_traceId filter. Both are wrong for current consoles: tracing-detail is an MCP tool that returns every span of a trace with pagination metadata, and tracing ignores span_traceId and returns the default page. Agents following the skill matched span_parentId values by hand, or gave up on full traces. - A trace ID alone goes to tracing-detail, paged while more remain. - Step 6 builds the parent/child waterfall from tracing-detail, and falls back to list evidence only when the tool isn't there (older consoles). - Parent/child claims are allowed when the hierarchy came from tracing-detail, not only from a supplied tree. - tracing rounds start down to the hour: check metadata.start, and for a sub-hour window pass an explicit start and filter rows by span_start. Fixes #72 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WalkthroughRe-review of the synchronized head
AssessmentVerified the new claims against
The two other non-blocking notes from the human review on No new correctness, security, or performance findings — the change is Markdown-only and the console behavior it cites matches the code. Verdict: APPROVE — the synchronized head resolves the last open note, matches the console code, and introduces no new issues. |
Cesar-M-Diaz
left a comment
There was a problem hiding this comment.
Reviewed against nsolid-console-ng master (a4af2501a).
Confirmed correct in this PR:
tracingdoes ignorespan_traceId: it is listed in the schema (schemas/http-v4.toml,[GET.tracing]) butGETTracinginlib/db/elastic/reader.jsnever adds it as a filter. The new guardrail is right.tracing-detailis MCP-exposed (since7ddf52ee19, first inv6.5.2) and MCP forcesincludeMetadata=true(lib/mcp-server.js), sohasMoreis always present.metadata.startreflects the rounded start (GET-tracing.js), so checking it is useful.
Main concern: the sub-hour window guidance in Step 3 can't work as written (see inline comments). Two related points that fall outside the diff hunks:
- Step 5 (lines 33–36) trusts
metadata.total/ empty results.metadata.totalis thecardinalityofspan_traceIdover the hour-rounded window (trace_id_countagg inreader.js). For a 10:40–11:00 window, an app whose only matching traces are at 10:10 still getsmetadata.total > 0, so Step 5 would report it "has matching traces". Step 3 and Step 5 should agree on what window the evidence actually covers. - Default window when no
startis passed is ~23–24h, not "recent":GET-tracing.jsusesnow - 23h, rounded down to the hour. Worth stating next to the rounding note so the agent doesn't present a day of data as current.
Suggested rewrite for the Step 3 bullet: drop "pass an explicit ISO start and keep only rows…"; instead say the effective minimum granularity is the full hour, that the summary aggregates and metadata.total cover that whole hour, and that the report must state the actual window from metadata.start/metadata.end.
| ### 3. Find Slow Requests Only If No Trace Was Supplied | ||
| - Call `tracing`. Use pipe duration syntax (e.g., `durations="1000|5000"` for 1–5s spans); do not use dash-range syntax. | ||
| - Use `functionName` only when the exact server-side function name is known. | ||
| - `tracing` rounds `start` down to the hour. Check `metadata.start` in the response. For a window shorter than an hour, pass an explicit ISO `start` and keep only rows whose `span_start` falls inside the window. |
There was a problem hiding this comment.
An explicit ISO start is also rounded down to the hour. GET-tracing.js does qData.start = dayjs(qData.start).minute(0).second(0) regardless of input format, and reader.js repeats the same rounding in both GETTracing (legacy) and _GETTracingOTel. Passing start=2026-10-14T10:40:00 still returns everything from 10:00, while the agent believes the ISO value fixed the window.
There was a problem hiding this comment.
Right, confirmed in GET-tracing.js and both reader paths. Fixed in 1f2c83c. Step 3 now says tracing works in whole hours: it rounds start down even for an exact ISO time, and the report states the window from metadata.start and metadata.end. The "pass an explicit ISO start" advice is gone.
| ### 3. Find Slow Requests Only If No Trace Was Supplied | ||
| - Call `tracing`. Use pipe duration syntax (e.g., `durations="1000|5000"` for 1–5s spans); do not use dash-range syntax. | ||
| - Use `functionName` only when the exact server-side function name is known. | ||
| - `tracing` rounds `start` down to the hour. Check `metadata.start` in the response. For a window shorter than an hour, pass an explicit ISO `start` and keep only rows whose `span_start` falls inside the window. |
There was a problem hiding this comment.
There are no rows to filter by span_start. SUMMARIZE_CONFIG.tracing in lib/mcp-server.js has no optIn, so every MCP tracing response is always summarized into per-app aggregates (min/max/median/latest, uniqueValues). Only the min/max/latest context snapshots carry span_start (formatRecordContext in summarize-data.js); the median, counts, uniqueValues of span_traceId and metadata.total all include the out-of-window traces and can't be trimmed. The agent can't do "keep only rows whose span_start falls inside the window" and may report earlier latency as current.
There was a problem hiding this comment.
Confirmed: tracing has no optIn in SUMMARIZE_CONFIG, so shouldSummarize is always true for it. Fixed in 1f2c83c with your suggested rewrite. Step 3 says the MCP response is always summarized per app, so it can't be narrowed to a shorter window afterwards, and that metadata.total counts the whole rounded window. The "keep only rows whose span_start" instruction is gone.
| - Treat the prompt as primary evidence. Parse any provided trace ID, span list, app name, endpoint name, status code, duration, or error stack before calling tools. | ||
| - If the user supplied a trace tree/export or any other host-provided trace data, treat it as authoritative: map that hierarchy directly and **do not fall through to live enrichment** (`information-dashboard`, `tracing`). The connected-services discovery (step 2) and live `tracing` queries (steps 3–4) are only for when no sufficient host data is present. | ||
| - If the user supplied only a trace ID (not a full tree), call `tracing` with `span_traceId` for list-level rows only; the MCP tool does not expose full waterfall details. | ||
| - If the user supplied only a trace ID (not a full tree), call `tracing-detail` with `span_traceId` to get every span of that trace. For a large trace, page with `page` and `showLimit` while the pagination metadata says more remain (`hasMore`). Do not call `tracing` with `span_traceId`: it ignores that filter and returns the default page. |
There was a problem hiding this comment.
No fallback for consoles < v6.5.2 on the trace-ID-only path. tracing-detail was first MCP-exposed in v6.5.2. On older consoles this step requires a tool that doesn't exist, and the new guardrail (line 73) forbids the previous tracing + span_traceId path. The Step 6 fallback (line 42) doesn't cover Step 1, so the agent has no allowed route. Suggest adding the same "if tracing-detail is not available, say so and stop / ask for more data" fallback here.
There was a problem hiding this comment.
Fixed in 1f2c83c. Step 1's trace-ID path now has its own fallback: if tracing-detail isn't available (consoles before 6.5.2), errors, or returns no spans, the agent says the trace couldn't be read and asks for the trace data or another ID. The guardrail against tracing with span_traceId stays, so there's no silent wrong route.
| - Treat the prompt as primary evidence. Parse any provided trace ID, span list, app name, endpoint name, status code, duration, or error stack before calling tools. | ||
| - If the user supplied a trace tree/export or any other host-provided trace data, treat it as authoritative: map that hierarchy directly and **do not fall through to live enrichment** (`information-dashboard`, `tracing`). The connected-services discovery (step 2) and live `tracing` queries (steps 3–4) are only for when no sufficient host data is present. | ||
| - If the user supplied only a trace ID (not a full tree), call `tracing` with `span_traceId` for list-level rows only; the MCP tool does not expose full waterfall details. | ||
| - If the user supplied only a trace ID (not a full tree), call `tracing-detail` with `span_traceId` to get every span of that trace. For a large trace, page with `page` and `showLimit` while the pagination metadata says more remain (`hasMore`). Do not call `tracing` with `span_traceId`: it ignores that filter and returns the default page. |
There was a problem hiding this comment.
Paging without a sort can duplicate or skip spans. GETTracingBySpanTraceId and _GETTracingBySpanTraceIdOTel paginate with from/size and no sort, so pages aren't stable (especially while a live trace is still being ingested). Default showLimit is 25, so a 200-span trace takes 8 sequential calls. normalizePositiveInteger has no upper cap, so a cheaper/safer approach is one call with a large showLimit (or re-request once with showLimit >= totalSpans from the first page's metadata), bounded by ES's 10k window. Minor.
There was a problem hiding this comment.
Agreed, and confirmed: neither GETTracingBySpanTraceId nor the OTel variant sorts, and normalizePositiveInteger has no cap. Fixed in 1f2c83c. The skill asks for the whole trace in one call (showLimit: 1000). If totalSpans is larger, it calls once more with showLimit at least totalSpans, at most 10000 for ES's result window. It says not to page with page, since unsorted pages can repeat or skip spans.
| ### 6. Triage the Trace | ||
| - Use `tracing` results as collapsed trace-list evidence: slow/failing service, endpoint, status, duration, and `span_traceId`. | ||
| - Do not claim parent/child waterfall analysis from `tracing` alone; `tracing-detail` is not exposed as MCP. | ||
| - For the slow or failing trace, call `tracing-detail` with its `span_traceId` and build the parent/child waterfall from `span_id` and `span_parentId`: which child spans take the time, which fail, and which run one after another when they could run in parallel. |
There was a problem hiding this comment.
tracing-detail returns spans in no defined order. Neither GETTracingBySpanTraceId nor its OTel variant adds a sort to the ES query, so hits come back in score/doc order. To reason about "which run one after another when they could run in parallel", the skill should tell the agent to order spans by span_start/span_end first; otherwise it may read response order as execution order and misclassify Promise.all children.
There was a problem hiding this comment.
Fixed in 1f2c83c. Step 6 has the agent sort the spans by span_start before building the waterfall, and says response order is not execution order. "One after another" is now defined as one span starting after the previous ends. A guardrail repeats it: read tracing-detail spans in span_start order.
| ### 6. Triage the Trace | ||
| - Use `tracing` results as collapsed trace-list evidence: slow/failing service, endpoint, status, duration, and `span_traceId`. | ||
| - Do not claim parent/child waterfall analysis from `tracing` alone; `tracing-detail` is not exposed as MCP. | ||
| - For the slow or failing trace, call `tracing-detail` with its `span_traceId` and build the parent/child waterfall from `span_id` and `span_parentId`: which child spans take the time, which fail, and which run one after another when they could run in parallel. |
There was a problem hiding this comment.
This can override a user-supplied trace tree. This bullet calls tracing-detail unconditionally for "the slow or failing trace", and the Step 1 "do not fall through to live enrichment" list only names information-dashboard and tracing. If the user supplied an exported tree that includes its trace ID, the agent may fetch live (possibly paged/partial) spans and analyze those instead of the authoritative export. Suggest gating this bullet on "no trace tree was supplied" and adding tracing-detail to the Step 1 exclusion list.
There was a problem hiding this comment.
Fixed in 1f2c83c. Step 6's tracing-detail call is gated on "no trace tree was supplied", and Step 1's exclusion list now names tracing-detail too. It applies "even when the export includes its trace ID".
| - Use `tracing` results as collapsed trace-list evidence: slow/failing service, endpoint, status, duration, and `span_traceId`. | ||
| - Do not claim parent/child waterfall analysis from `tracing` alone; `tracing-detail` is not exposed as MCP. | ||
| - For the slow or failing trace, call `tracing-detail` with its `span_traceId` and build the parent/child waterfall from `span_id` and `span_parentId`: which child spans take the time, which fail, and which run one after another when they could run in parallel. | ||
| - If `tracing-detail` is not among the available tools (an older console), stay with the collapsed trace-list evidence and say that the waterfall was not available. |
There was a problem hiding this comment.
Fallback only covers the tool being absent, not failing or empty. When Elastic isn't configured, GET-tracing-detail.js returns elasticReader is not available (surfaced as an MCP error), and an aged-out or wrong trace ID returns totalSpans: 0. In both cases the tool is listed, so this fallback doesn't trigger and there's no guidance. Suggest: "If tracing-detail is unavailable, errors, or returns no spans, stay with the collapsed trace-list evidence and say the waterfall was not available."
There was a problem hiding this comment.
Fixed in 1f2c83c. The fallback now covers all three cases: not among the tools (before 6.5.2), an error (such as elasticReader is not available), or no spans (totalSpans: 0). In triage it stays with the collapsed trace-list evidence and says the waterfall wasn't available. On the trace-ID-only path it asks the user for the data instead.
| name: ns-analyze-tracing | ||
| description: >- | ||
| Analyzes N|Solid/OpenTelemetry tracing evidence for request latency, HTTP errors, and service dependencies. Use when the user asks about slow endpoints, API timeouts, distributed traces/spans, N+1 queries, await chains, microservice latency, cascading failures, or trace IDs. Query N|Solid only when no trace data was provided. Do not assume full waterfall details unless supplied. | ||
| Analyzes N|Solid/OpenTelemetry tracing evidence for request latency, HTTP errors, and service dependencies. Use when the user asks about slow endpoints, API timeouts, distributed traces/spans, N+1 queries, await chains, microservice latency, cascading failures, or trace IDs. Query N|Solid only when no trace data was provided. Builds the span waterfall with `tracing-detail` once a trace ID is known. |
There was a problem hiding this comment.
Nit: the description now says the skill always builds the waterfall with tracing-detail, which isn't true on consoles < v6.5.2 (and the body still supports that case). Maybe "Builds the span waterfall with tracing-detail when available".
There was a problem hiding this comment.
Done in 1f2c83c: "Builds the span waterfall with tracing-detail when the console offers it."
…and sorts tracing-detail spans The console rounds a tracing start down to the hour, even an exact ISO time, and without one it covers the last 23 to 24 hours. Over MCP the tracing response is always summarized per app, so the skill's advice to keep only rows inside a shorter window could not be followed, and metadata.total counts the whole rounded window. The skill now says the window is whole hours and has the report state it from metadata.start and metadata.end. tracing-detail returns spans in no fixed order and pages without a sort, so the skill asks for the whole trace in one call (showLimit covering totalSpans) and sorts the spans by span_start before reading what ran in sequence. Its fallback now covers the tool erroring or returning no spans, as well as consoles before 6.5.2 that lack it, on both the trace-ID path and in triage. A supplied trace tree stays authoritative even when it includes its trace ID. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for checking this against
The rest are answered inline. |
Cesar-M-Diaz
left a comment
There was a problem hiding this comment.
Re-reviewed at 1f2c83c against nsolid-console-ng master (74adf6a32). All eight inline notes and both out-of-diff points (Step 5's metadata.total, the 23–24 h default window) are addressed, and the new claims check out: totalSpans and span_start are in the tracing-detail response, elasticReader is not available is the literal error, normalizePagination has no cap, and tracing-detail isn't summarized over MCP. Approving.
Not blocking. The inline note on showLimit: 1000 matters most; two more outside the diff:
- Step 10 (Validate) mixes pre- and post-deploy traces. It's a direct consequence of the hour rounding this PR documents. Deploy at 10:40, re-run
tracing, andstartrounds to 10:00: the summary's min/max/median blend traces from before and after the fix, so "p95 dropped from X to Y" is contaminated. Measuring from the first full hour after the deploy, and saying so, keeps the comparison clean. - Nit, Step 3: "pass
startat the current hour" gives 5 minutes of data at 10:05. It could suggest the previous full hour, or mention thatendisn't rounded (GET-tracing.js:28-32), so it can bound the window's end.
| - If the user supplied a trace tree/export or any other host-provided trace data, treat it as authoritative: map that hierarchy directly and **do not fall through to live enrichment** (`information-dashboard`, `tracing`). The connected-services discovery (step 2) and live `tracing` queries (steps 3–4) are only for when no sufficient host data is present. | ||
| - If the user supplied only a trace ID (not a full tree), call `tracing` with `span_traceId` for list-level rows only; the MCP tool does not expose full waterfall details. | ||
| - If the user supplied a trace tree/export or any other host-provided trace data, treat it as authoritative: map that hierarchy directly and **do not fall through to live enrichment** (`information-dashboard`, `tracing`, `tracing-detail`), even when the export includes its trace ID. The connected-services discovery (step 2) and live `tracing` queries (steps 3–4) are only for when no sufficient host data is present. | ||
| - If the user supplied only a trace ID (not a full tree), call `tracing-detail` with `span_traceId` and `showLimit: 1000` to get the whole trace in one call. If the metadata's `totalSpans` is larger, call once more with `showLimit` at least `totalSpans` (at most 10000). Don't page with `page`: the spans come back in no fixed order, so pages can repeat or skip spans. Do not call `tracing` with `span_traceId`: it ignores that filter and returns the default page. |
There was a problem hiding this comment.
showLimit: 1000 can blow past the client's MCP output limit. This follows my earlier suggestion of one large call, which fixes the unstable paging but doesn't account for size. tracing-detail isn't in SUMMARIZE_CONFIG and GETTracingBySpanTraceId returns each hit's full _source, so every span reaches the agent raw. The span in web/test/acceptance/fixtures/tracing/tracing-1h is ~514 bytes with only 17 fields; real ones with HTTP/DB attributes (or OTel resource attributes) are larger. By rough estimate, somewhere around 100–150 spans already exceeds Claude Code's default MCP output cap (MAX_MCP_OUTPUT_TOKENS, 25k tokens), and a 1000-span trace is well over 150k tokens. The large traces are exactly the N+1 cases this skill is for.
Suggestion: ask for showLimit: 150 first. If totalSpans fits, that's the whole trace. If not, don't pull up to 10000: say the waterfall covers an unordered subset of returnedSpans out of totalSpans. That is still enough to show repetition by span_name (N+1), but not to claim the critical path or totals.
Longer term, a sort on span_start (with span_id as a tiebreak) in GETTracingBySpanTraceId and its OTel variant would make page stable again. That's a console-ng change, not for this PR.
There was a problem hiding this comment.
Good catch. Fixed in 6b782e5, with your suggestion:
- Step 1 asks
tracing-detailforshowLimit: 150, and says why: every span comes back whole. A trace whosetotalSpansis at most 150 is whole. - For a bigger trace it does not ask for more. It says the waterfall covers
returnedSpansoftotalSpansspans, in no particular order: enough to show repeated span names (an N+1), not the critical path or totals. Step 6 points back to it, and a guardrail repeats it. - No more 10000. The
sortin the console'sGETTracingBySpanTraceIdwould be its own change, as you say.
…d says when a trace is bigger tracing-detail returns every span whole, so one call with showLimit 1000 can pass the client's limit on tool output. Claude Code caps MCP output at 25,000 tokens by default, and a trace of 100 to 150 real spans already fills that. The big traces are the N+1 cases this skill is for. The skill now asks for 150 spans. A trace that fits comes back whole. For a bigger one it says the waterfall covers returnedSpans of totalSpans, in no particular order: enough to show repeated span names, not the critical path or totals. A guardrail repeats it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The
ns-analyze-tracingskill saidtracing-detailisn't exposed over MCP, and told agents to look a trace up withtracing'sspan_traceIdfilter. Both are wrong for current consoles.tracing-detailis an MCP tool that returns every span of a trace, with pagination metadata.tracingignoresspan_traceIdand returns the default page (nodesource/nsolid-console-ng#5518). Agents following the skill matchedspan_parentIdvalues by hand, or reported that they couldn't pull full traces.Changes, all in
skills/ns-analyze-tracing/SKILL.md:tracing-detail { span_traceId }, paged withpage/showLimitwhile more remain. Nevertracingwithspan_traceId.tracing-detail(span_id,span_parentId). It falls back to list-level evidence, and says so, only when the tool isn't available (older consoles).tracing-detail, not only from a supplied tree. A new guardrail says not to passspan_traceIdtotracing.tracingroundsstartdown to the hour (nodesource/nsolid-console-ng#5519). Agents checkmetadata.start, and for a sub-hour window pass an explicit ISOstartand filter rows byspan_start.Testing:
pnpm lintandpnpm typecheckpass. The commit skipped the pre-commit hook, with the maintainer's OK. Its test suite can't run on the Windows machine this was made on: tests from fcaa1fc create file symlinks, which Windows refuses without Developer Mode, so they fail withEPERMon main too. This PR changes only Markdown; CI runs the full suite.Fixes #72
🤖 Generated with Claude Code