Skip to content

fix(mcp): time-bound trace waterfall queries so they prune partitions - #3116

Open
brandon-pereira wants to merge 3 commits into
mainfrom
brandon/brandon-trace-waterfall-tool-improvements
Open

fix(mcp): time-bound trace waterfall queries so they prune partitions#3116
brandon-pereira wants to merge 3 commits into
mainfrom
brandon/brandon-trace-waterfall-tool-improvements

Conversation

@brandon-pereira

@brandon-pereira brandon-pereira commented Sep 11, 2026

Copy link
Copy Markdown
Member

Why

clickstack_trace_waterfall fetched a trace's spans and correlated logs with a WHERE TraceId = X predicate and no time bound. Because the trace tables partition by day, a TraceId-only query can't prune partitions — it scans the full retention window and hits max_execution_time, so the tool times out on any deployment with meaningful history.

Naively adding the tool's default 15-minute search window as the bound would fix the scan but break correctness: a trace older than 15 minutes (or picked by pickBy with a root that predates the window) would return zero or partial spans.

What

Thread a real time bound into the span and log queries so ClickHouse prunes partitions, without dropping valid traces:

  • Probe the trace's [min, max] span extent before fetching, and use that (padded) as the window. Cost scales with the one trace, not the retention window. This rescues an explicit traceId older than the default window and keeps a trace that ran longer than an hour from being truncated.
  • Probe in auto-pick mode too. A picked trace is only guaranteed one span inside the default window; its root/tail can lie outside. Without the probe, auto-pick returned a partial tree with a false root and understated duration.
  • Width-clamp the fetch window to the recent tail so a reused or sentinel traceId (e.g. an all-zero id from an uninstrumented emitter) whose min and max are days apart can't balloon the scan back toward the retention edge or stitch two unrelated occurrences into one nonsense tree. When the clamp drops early spans, the response carries a windowNote rather than silently promising the whole tree.
  • max_execution_time ceiling + HTTP request timeout shared with the rest of the MCP query tools (constants exported from query/helpers.ts rather than duplicated). The ceiling takes precedence over source.querySettings, so a source can't relax it.
  • Accurate empty-result hints. When the probe path finds nothing, the hint names the recoverable action ("pass an explicit startTime") instead of the misleading "widen the window" — the caller never set a window to widen. A probe that fails on infrastructure error is surfaced (probeNote), not silently swallowed.
  • Reclassify ClickHouse query timeouts (TIMEOUT_EXCEEDED) from user to server errors, with an actionable error hint. A query hitting the execution-time limit is a resource failure, not a user mistake, and the user default hid these from error views.

Testing

  • Unit (query.test.ts): TIMEOUT_EXCEEDED classification + error-hint text.
  • Integration (trace.int.test.ts): probe rescue of an old trace, explicit-window honoring, probe-miss hint, wide-spread sentinel clamp (with windowNote), >1h-spread trace, and auto-pick root predating the window.
  • End-to-end against a live ClickStack MCP + ClickHouse: verified the probe rescue, the >1h-spread window, auto-pick, and the empty-result hint on both seeded and organic traces.

The clickstack_trace_waterfall tool ran TraceId-only span/log queries that
scanned the full retention window and timed out. Thread the search window into
both queries, add a max_execution_time ceiling, and probe the trace's actual
[min, max] span extent so an explicit traceId older than the default window —
or one that ran longer than an hour — still resolves in full. The probe also
runs for auto-picked traces, so a picked trace whose root predates the window
is no longer truncated into a partial tree. The fetch window is width-clamped
to the recent tail so a reused/sentinel traceId can't widen the scan back
toward the retention edge or stitch unrelated occurrences into one tree.
Empty-result hints name the recoverable action (pass an explicit startTime),
and probe failures are surfaced rather than silently swallowed. ClickHouse
query timeouts are reclassified from user to server errors.
@changeset-bot

changeset-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: eb5b412

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@hyperdx/api Patch
@hyperdx/app Patch
@hyperdx/otel-collector Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Sep 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
hyperdx-oss Ready Ready Preview Sep 11, 2026 11:21pm UTC
hyperdx-storybook Ready Ready Preview Sep 11, 2026 11:21pm UTC

Request Review

@greptile-apps

greptile-apps Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR bounds trace-waterfall ClickHouse queries, probes trace extents, enforces shared query timeouts, and improves timeout classification and incomplete-result guidance.

  • Adds bounded span and correlated-log fetches with partition-pruning predicates.
  • Probes explicit and auto-picked trace extents, with a width clamp for reused trace IDs.
  • Adds unit and integration coverage for old traces, auto-pick, clamping, and timeout hints.
  • The latest revision fixes query-setting precedence and distinguishes setting constraints from actual execution timeouts.

Confidence Score: 3/5

The PR is not yet safe to merge because supported multi-column timestamp sources can return incorrect trace data, and auto-picked traces can still be silently truncated after an explicit end time.

The bounded-query approach is sound for single timestamp expressions, but selecting a date-only first timestamp token breaks event-time filtering and ordering for supported multi-column sources. The auto-pick probe also searches backward beyond the pick window without searching sufficiently beyond its end, so a later trace tail can be omitted without warning. The previous probe-failure warning was fully fixed; brandon-pereira accepted the bounded late-log risk because arbitrary duration padding would undermine scan limits; the fixed-bound finding was withdrawn after its tradeoff became explicit; and the comment-divider thread was resolved after the newly introduced divider was removed.

Files Needing Attention: packages/api/src/mcp/tools/trace/waterfall.ts

Important Files Changed

Filename Overview
packages/api/src/mcp/tools/trace/waterfall.ts Adds extent probing and bounded span/log fetches, but mishandles multi-column timestamps and can silently truncate an auto-picked trace after an explicit end time.
packages/api/src/mcp/tools/query/helpers.ts Exports shared ClickHouse limits and correctly classifies and explains execution-time failures.
packages/api/src/mcp/tests/trace.int.test.ts Adds broad integration coverage for bounded trace fetching, though it does not cover a trace tail after an explicit auto-pick end time.
packages/api/src/mcp/tests/query.test.ts Covers timeout classification and protects setting-constraint errors from the timeout hint.
.changeset/trace-waterfall-time-bound.md Records the user-facing trace-waterfall query behavior change as an API patch release.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  I[Trace waterfall input] --> P{Explicit trace ID?}
  P -->|No| A[Pick trace inside search window]
  P -->|Yes| E[Use supplied trace ID]
  A --> X[Probe bounded span extent]
  E --> X
  X --> C[Pad and width-clamp fetch window]
  C --> S[Fetch bounded spans]
  C --> L[Fetch bounded correlated logs]
  S --> R[Build trace tree]
  L --> R
  R --> O[Return waterfall and completeness notes]
Loading

Fix all with Greploop Fix All in Claude Code Fix All in Conductor Fix All in Cursor Fix All in Codex

Reviews (4): Last reviewed commit: "fix(mcp): address deep-review findings o..." | Re-trigger Greptile

Comment thread packages/api/src/mcp/tools/trace/waterfall.ts
Comment thread packages/api/src/mcp/tools/trace/waterfall.ts
Comment thread packages/api/src/mcp/tools/trace/waterfall.ts Outdated
Comment thread packages/api/src/mcp/tools/trace/waterfall.ts Outdated
@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

E2E Test Results

All tests passed • 357 passed • 1 skipped • 1503s

Status Count
✅ Passed 357
❌ Failed 0
⚠️ Flaky 1
⏭️ Skipped 1

Tests ran across 4 shards in parallel.

View full report →

- MCP ClickHouse settings now win over source.querySettings so a source can't
  relax the max_execution_time / readonly ceiling (span + log fetches).
- Surface a probeNote when the extent probe fails and the fallback window may
  return a partial tree, instead of presenting it as complete.
- Surface a windowNote when the fetch-window cap drops a wide-spread trace's
  earliest spans, so the tool no longer silently promises the whole tree.
- Drop the newly added "Safety limits" ASCII divider per AGENTS.md.
@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

🟡 Tier 3 — Standard

Introduces new logic, modifies core functionality, or touches areas with non-trivial risk.

Why this tier:

  • Diff size: 267 production lines changed (Tier 2 max: < 250)

Review process: Full human review — logic, architecture, edge cases.
SLA: First-pass feedback within 1 business day.

Stats
  • Production files changed: 2
  • Production lines changed: 267 (+ 363 in test files, excluded from tier calculation)
  • Branch: brandon/brandon-trace-waterfall-tool-improvements
  • Author: brandon-pereira

To override this classification, remove the review/tier-3 label and apply a different review/tier-* label. Manual overrides are preserved on subsequent pushes.

@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Deep Review

Scope: clickstack_trace_waterfall MCP tool — time-bound the span/log ClickHouse fetches via a [min, max] span-extent probe so partitions prune, plus a shared max_execution_time ceiling and TIMEOUT_EXCEEDED reclassification. 5 files, ~238 production lines.

Several previously-flagged P1 issues are confirmed resolved in the current tree: the multi-column timestampValueExpression split (tsExprFirst/logTsExpr), the auto-pick-with-explicit-startTime probe gate (isAutoPick || usedDefaultWindow), the probeNote on non-empty partial trees, the windowNote on clamp, and the timeout-hint regex no longer hijacking SETTING_CONSTRAINT_VIOLATION. The findings below are what remains.

✅ No critical (P0/P1) issues found.

🟡 P2 — recommended

  • packages/api/src/mcp/tools/trace/waterfall.ts:496 — For an explicit traceId with the default window, the extent probe runs WHERE TraceId = X AND ts >= now-90d AND ts <= now, a 90-day scan that cannot prune partitions and can itself hit the new max_execution_time: 30 ceiling on the large-history deployments this PR targets — partially reinstating the scan it aims to eliminate (mitigated: the probe is a min/max over only the timestamp+traceId columns and degrades to an accurate probeFailed hint rather than crashing).
    • Fix: Probe with a widening lookback ladder (e.g. 1h → 24h → 7d → 90d, stopping at the first hit) so the common recent-trace lookup touches one or two partitions.
  • packages/api/src/mcp/tools/trace/waterfall.ts:703 — The correlated-logs fetch is bounded by fetchEnd = max(span start) + 1h; a trace whose final/longest span starts near lastSeen and runs longer than the 1h trail pad silently drops logs emitted in its tail, and no windowNote/probeNote fires for this case (unlike the clamp case), so the incompleteness is invisible to the agent.
    • Fix: Emit a note when logs are time-bounded tighter than a span's start + duration, or extend the trail pad by the max span duration seen in the probe.
  • packages/api/src/mcp/tools/trace/waterfall.ts:498 — The probeFailed catch branch and its two downstream outputs (the empty-spans probeFailed hint and the non-empty partial-tree probeNote) have zero test coverage; the integration suite runs against real ClickHouse and never forces the probe query to throw, so the exact path prior review asked for cannot be verified.
    • Fix: Add a unit/spy test where clickhouseClient.query rejects on the probe call but succeeds on the fetch, asserting probeNote on a non-empty tree and the probeFailed hint on an empty fallback.
    • testing
  • packages/api/src/mcp/tools/trace/waterfall.ts:378tsExprFirst/getFirstTimestampValueExpression was added specifically for comma-separated timestampValueExpression (e.g. "EventDate, EventTime"), but every test source uses single-column 'Timestamp' where the split is a no-op, so the first-column extraction in the probe/fetch/log SQL is never exercised.
    • Fix: Add a trace source with a comma-separated timestampValueExpression and assert the waterfall and correlated-logs queries succeed.
    • testing
🔵 P3 nitpicks (3)
  • packages/api/src/mcp/tools/trace/waterfall.ts:560 — The { ...source.querySettings, ...MCP_CLICKHOUSE_SETTINGS } merge block is duplicated verbatim between the span fetch (560) and log fetch (722).
    • Fix: Extract a withMcpSettings(querySettings?) helper in query/helpers.ts and reuse in both fetches so override precedence lives in one place.
    • maintainability
  • packages/api/src/mcp/tools/trace/waterfall.ts:246probeTraceWindow passes clickhouse_settings: MCP_CLICKHOUSE_SETTINGS alone, while the span/log fetches merge source.querySettings first; a source that needs a per-query setting to read correctly would apply it to the fetch but not the probe that bounds it.
    • Fix: Route the probe through the same settings helper as the fetches.
    • maintainability
  • packages/api/src/mcp/tools/trace/waterfall.ts:117 — Pre-existing // ─── ... ─── ASCII section dividers remain in the edited files; AGENTS.md disallows them, but this diff introduces none.
    • Fix: No action for this PR; strip in a separate cleanup if desired.
    • project-standards

Reviewers (10): correctness, testing, maintainability, project-standards, performance, reliability, adversarial, kieran-typescript, api-contract, previous-comments.

Coverage note: testing, maintainability, and project-standards returned before synthesis; the remaining reviewers were still completing, so the P2 performance/probe and log-trail findings and the prior-thread dispositions are drawn from orchestrator analysis of the diff and the 8 prior-comment threads. The 90-day-probe-scan concern corroborates a prior automated review comment that was not clearly addressed.

Testing gaps: No test asserts a log outside the fetch window is excluded (only inclusion is tested). The 24h clamp-floor boundary is validated only indirectly via a 10-day gap, so an off-by-window regression near the edge would not be caught.

'The result row count is too large to serialize back to the agent.'
);
}
if (/TIMEOUT_EXCEEDED|Timeout exceeded|max_execution_time/i.test(msg)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 major — New timeout hint matches max_execution_time anywhere in the message, so it hijacks SETTING_CONSTRAINT_VIOLATION and READONLY errors

The regex /TIMEOUT_EXCEEDED|Timeout exceeded|max_execution_time/i runs before the SETTING_CONSTRAINT_VIOLATION branch at line 934. ClickHouse's constraint error reads Setting max_execution_time shouldn't be greater than 10. (SETTING_CONSTRAINT_VIOLATION) and the readonly error reads Cannot modify 'max_execution_time' setting in readonly mode — both now return "The query exceeded its execution-time limit. Narrow the time range…", sending the agent into a pointless narrow-and-retry loop instead of telling it the connection profile caps the setting. This is newly reachable precisely because this PR forces max_execution_time: 30 onto every waterfall query too. Drop max_execution_time from the alternation (keep TIMEOUT_EXCEEDED|Timeout exceeded), or move the branch below the SETTING_CONSTRAINT_VIOLATION check. The existing test at packages/api/src/mcp/__tests__/query.test.ts:43 only uses max_result_rows, so it doesn't catch this.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — dropped the bare max_execution_time from the timeout-hint alternation (kept TIMEOUT_EXCEEDED|Timeout exceeded), so a Setting max_execution_time shouldn't be greater than… error now falls through to the SETTING_CONSTRAINT_VIOLATION branch. Added a regression test asserting a max_execution_time constraint error gets the constraint hint, not the timeout hint.

const probeQuery = `
SELECT
min(${params.tsExpr}) AS firstSeen,
max(${params.tsExpr}) AS lastSeen

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 majortimestampValueExpression is interpolated unsplit into min()/max() and a comparison, breaking multi-column timestamp sources

timestampValueExpression may hold a comma-separated list (e.g. 'EventDate, EventTime' — see getFirstTimestampValueExpression in packages/common-utils/src/core/utils.ts:151, and the many configs in packages/common-utils/src/__tests__/renderChartConfig.test.ts:3931). The pre-PR query tolerated that because ${tsExpr} only appeared in SELECT … AS timestamp and ORDER BY; the new uses do not — min(EventDate, EventTime) fails arity validation and WHERE EventDate, EventTime >= fromUnixTimestamp64Milli(…) is a syntax error, so the probe throws and the tree query then returns a hard ClickHouse error for every call. Wrap both tsExpr (line 362) and logTsExpr (line 650) in getFirstTimestampValueExpression from @hyperdx/common-utils/dist/core/utils, which is already imported in the sibling packages/api/src/mcp/tools/query/helpers.ts:8.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed. Both tsExpr and logTsExpr are now wrapped in getFirstTimestampValueExpression for every raw-SQL use (probe min/max, tree SELECT/WHERE/ORDER BY, picker min/max orderBy, log SELECT/WHERE/ORDER BY). The full multi-column expression is kept only where queryChartConfig consumes it (it splits internally).

let fetchEnd = endDate;
let probeFailed = false;
let windowClamped = false;
const usedDefaultWindow = input.startTime == null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 major — Auto-pick with an explicit startTime skips the probe, so the fetch now silently truncates the tree

usedDefaultWindow = input.startTime == null gates the probe, but in auto-pick mode startTime is the pick window ("find me a slow trace from the last hour"), not a fetch bound. Before this PR the span query was unbounded so the whole tree came back; now a caller passing pickFilter + startTime: <1h ago> gets only the in-window spans of the picked trace — a false rootSpan, an understated totalDurationMs, and no probeNote/windowNote to signal it. That is the exact failure the PR description cites as the reason for probing in auto-pick mode. Gate on input.traceId == null || input.startTime == null so auto-pick always probes (the explicit-traceId + explicit-window honoring that trace.int.test.ts:488 pins is unaffected).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — the probe now runs whenever input.traceId == null (auto-pick) OR input.startTime == null, so auto-pick with an explicit pick-window startTime still probes the picked trace's real extent. The only path that honors the window verbatim is explicit traceId + explicit startTime (pinned by the existing test). Added an integration test for auto-pick + explicit startTime with a root predating the window.

const usedDefaultWindow = input.startTime == null;
if (usedDefaultWindow) {
const probeStart = input.traceId
? new Date(endDate.getTime() - TRACE_PROBE_MAX_LOOKBACK_MS)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — The probe re-runs the same unpruned 90-day TraceId scan the PR is trying to eliminate

For an explicit traceId the probe runs WHERE TraceId = X AND ts BETWEEN now-90d AND now — the same predicate shape, over a range that equals or exceeds the retention window on most deployments, so it cannot prune partitions either. On the deployments this PR targets the probe is what will now hit the new max_execution_time: 30 ceiling, leaving the caller with probeFailed plus an empty fallback window. Probe with a widening ladder instead (e.g. 1h → 24h → 7d → 90d, stopping at the first hit) so the common recent-trace case touches one or two partitions.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid observation, but deferring: a widening ladder (1h → 24h → 7d → 90d) is a meaningful redesign with its own tradeoffs (extra round-trips for genuinely old traces, tuning the rungs), and the probe now carries the same max_execution_time ceiling, so a probe timeout degrades gracefully to probeFailed + an accurate hint rather than a hang. The common recent-trace case already prunes to a narrow window once the extent is found. Worth a follow-up if probe latency shows up in practice; out of scope for this fix.

// first-seen — so a trace whose spans span more than an hour is returned
// whole, spans and correlated logs alike.
describe('long-running trace window', () => {
const LONG_TRACE_ID = 'aaaa1111bbbb2222cccc3333dddd4444';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minorLONG_TRACE_ID reuses the breakdown suite's TRACE_ID_1 in a table that is never truncated

'aaaa1111bbbb2222cccc3333dddd4444' is already used as TRACE_ID_1 at line 953, and clearClickhouseTables in packages/api/src/fixtures.ts:380 has the traces table commented out — the comment at line 951 warns about exactly this. The new test only passes because it runs first in declaration order; under --randomize, or if either describe is moved, the breakdown suite's three recent spans join the probe's [min, max], the 2-day spread trips the 24h clamp, and the test fails on spanCount/windowNote. Give it a unique id.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — gave LONG_TRACE_ID a unique value so it no longer collides with the breakdown suite's TRACE_ID_1 in the never-cleared traces table.

@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

PR Review

4 finding(s): 🔴 0 critical · 🟠 2 major · 🔵 2 minor

3 posted as inline comment(s) on the changed lines. 1 listed below.

Findings outside the changed lines

  • 🟠 packages/api/src/mcp/tools/trace/waterfall.ts:432Auto-pick query gets the new 32s HTTP request timeout but no max_execution_time, so a slow pick is aborted client-side instead of returning a clean TIMEOUT_EXCEEDED → The client built at line 349 now carries requestTimeout: MCP_REQUEST_TIMEOUT (32s), but this queryChartConfig call passes only querySettings: source.querySettings — unlike the equivalent call in packages/api/src/mcp/tools/query/helpers.ts:593-601, it never passes opts: { clickhouse_settings: MCP_CLICKHOUSE_SETTINGS }, and ClickhouseClient (packages/api/src/clickhouse.ts) sets no queryTimeout default, so processClickhouseSettings leaves max_execution_time unset. On the deployments this PR targets, a pick over a wide window now hits the 32s socket abort rather than the 30s server-side ceiling — the exact case the MCP_REQUEST_TIMEOUT doc comment says the ordering exists to avoid — producing an opaque client error that neither the new TIMEOUT_EXCEEDED errorHint nor isServerError classification recognizes. Pass opts: { clickhouse_settings: MCP_CLICKHOUSE_SETTINGS } on this call.

Severity is the reviewer's own estimate and is used for ordering, not filtering.

- errorHint: match only real timeouts (TIMEOUT_EXCEEDED / "Timeout exceeded")
  so a "Setting max_execution_time shouldn't be greater than…" constraint error
  no longer hijacks the timeout hint. Regression test added.
- Wrap tsExpr / logTsExpr in getFirstTimestampValueExpression for every raw-SQL
  use (probe min/max, tree + log SELECT/WHERE/ORDER BY, picker orderBy) so a
  multi-column timestampValueExpression (e.g. "EventDate, EventTime") no longer
  breaks the probe and span queries.
- Probe in auto-pick mode even with an explicit startTime: startTime is the
  pick window there, not a fetch bound, so the picked trace's full extent must
  still be probed. Only explicit traceId + explicit startTime honors the window
  verbatim. Integration test added.
- Give LONG_TRACE_ID a unique value so it no longer collides with the breakdown
  suite's TRACE_ID_1 in the never-cleared traces table.
@github-actions github-actions Bot added review/tier-3 Standard — full human review required and removed review/tier-2 Low risk — AI review + quick human skim labels Sep 11, 2026
// "EventDate, EventTime"). Raw-SQL uses that aggregate or compare on it —
// min()/max() and the WHERE bounds — need a single column; only the
// queryChartConfig picker (which splits it itself) gets the full form.
const tsExprFirst = getFirstTimestampValueExpression(tsExpr);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Multi-column timestamps lose precision

For supported timestamp expressions such as EventDate, EventTime, this uses the first, date-only partition column as the event timestamp. The probe and span fetch therefore calculate extents, filter, order, and return timestamps at day precision. This can exclude valid spans at window boundaries, misorder the tree, and report midnight instead of the actual span time. The log query and first_error ordering have the same problem. Use the precise DateTime expression for event semantics while retaining all configured timestamp columns for partition pruning.

Fix in Claude Code Fix in Conductor Fix in Cursor Fix in Codex

Comment on lines +506 to +507
startDate: probeStart,
endDate,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Probe silently drops trace tails

In auto-pick mode, an explicit endTime limits the search for a trace, not the full-trace fetch. However, the extent probe also stops at endTime. If the selected trace has descendant spans more than one hour after that boundary, they cannot contribute to lastSeen and are omitted from the fetch. None of the current notes warns that the returned tree is incomplete, even though the code recognizes that a trace tail may lie outside the search window.

Fix in Claude Code Fix in Conductor Fix in Cursor Fix in Codex

// "EventDate, EventTime"). Raw-SQL uses that aggregate or compare on it —
// min()/max() and the WHERE bounds — need a single column; only the
// queryChartConfig picker (which splits it itself) gets the full form.
const tsExprFirst = getFirstTimestampValueExpression(tsExpr);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 majorgetFirstTimestampValueExpression picks a day-precision column for composite timestamp sources, so the probe window is anchored at midnight and correlated logs come back empty

For a source whose timestampValueExpression is the supported composite "EventDate, EventTime" form, getFirstTimestampValueExpression returns EventDate — a Date column. The probe then computes min(EventDate)/max(EventDate), so firstSeen/lastSeen are midnight of the trace's day and the fetch window becomes [midnight-1h, midnight+1h]. The span query still matches (it compares the same EventDate), but (a) ${tsExprFirst} AS timestamp now returns midnight for every span, where the pre-diff ${tsExpr} AS timestamp rendered EventDate, EventTime AS timestamp and yielded the real EventTime — so buildPreOrderTree's timestamp sort and the emitted timestamp field silently degrade to day precision; and (b) the log query at line 703 compares the log source's own column against that midnight-anchored window, so a trace that ran at 14:00 returns logs: [] with no note. The mirror case (single-column trace source, composite log source) fails the same way: logTsExpr = EventDate is compared against a precise [13:00, 15:00] window and matches nothing. packages/app/src/utils/rowTimestamps.ts:44-48 spells out exactly this hazard. Use the canonical resolver pickBucketTimestampColumn (packages/common-utils/src/core/utils.ts:205) — it picks the highest-precision DateTime token and skips Date ones; metadata is already built at waterfall.ts:355, and the same fix applies to logTsExpr at line 664.

},
format: 'JSONEachRow',
connectionId: params.connectionId,
clickhouse_settings: MCP_CLICKHOUSE_SETTINGS,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — Probe query drops source.querySettings that every other query on this path applies

probeTraceWindow sends clickhouse_settings: MCP_CLICKHOUSE_SETTINGS alone, while the pick (line 435), span (line 560) and log (line 722) queries all merge source.querySettings. A source that needs a connection-specific setting to query its table at all will have the probe throw, silently setting probeFailed and falling back to the 15-minute default window — the very failure the probe exists to prevent. Merge source.querySettings under MCP_CLICKHOUSE_SETTINGS here too, matching the span fetch.

return {
start: new Date(fetchStart),
end: new Date(fetchEnd),
clamped: unclampedStart < clampFloor,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minorclamped is set for traces whose extent is 22–24h even though no span was actually dropped

clamped: unclampedStart < clampFloor is true whenever lastSeen - firstSeen > 22h (the two 1h pads), but spans are only lost when firstSeen < fetchStart, i.e. when the extent exceeds 23h. For a trace spanning 22.5h the response carries the windowNote claiming "the earliest spans may be missing" while the tree is complete, pushing the agent to re-run with an explicit startTime for nothing. Set clamped: firstSeen < fetchStart instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review/tier-3 Standard — full human review required

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant