Skip to content

fix(mcp): make event_patterns drill-down snippets executable - #3426

Merged
kodiakhq[bot] merged 12 commits into
mainfrom
brandon/brandon-mcp-event-pattern-tweaks
Nov 4, 2026
Merged

kodiakhq[bot] merged 12 commits into
mainfrom
brandon/brandon-mcp-event-pattern-tweaks

Conversation

@brandon-pereira

@brandon-pereira brandon-pereira commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

Summary

clickstack_event_patterns returned a whereSnippet per pattern that agents were told to pass to clickstack_search, but it often matched nothing. A template like Sending <*> rows became Body:"Sending rows" (a phrase that never occurs), and map-field body expressions produced Lucene that failed to parse. Agents hit dead ends on the main drill-down path.

Changes:

  • Executable drill-down. whereSnippet is now a SQL match() predicate (whereLanguage: "sql") built from the template: literal tokens in order, one non-whitespace token per <*>, whitespace matching Drain's JS \s tokenization. It already ANDs in the caller's where (rendered to SQL, dashboard variables expanded) and the source's table filter. If the caller filter can't be rendered as SQL, summary.drilldown.includesFilters is false with a warning. Templates over 4000 chars get drilldownUnavailable instead of an empty snippet that would match everything.
  • Source table filter. Pattern mining (event_patterns, emerging_signals, dashboard pattern tiles) now applies the log source's tableFilterExpression. Counts can drop for sources that set one.
  • bodyExpression also accepts JSONExtractString(<column or map ref>, '<key>', ...). It is rebuilt from validated args, so the injection allowlist still holds.
  • Sampling transparency. Response reports sampleCoverage, and summary.trend gives the real bucket interval and boundaries (granularity rounding means fewer or wider buckets than requested). Tool description and usage text explain that counts are extrapolated and a 0 bucket doesn't mean no events.
  • minePatterns template bug. Each cluster now reports its own template. Before, it re-matched the first member's body and could pick up a different same-length cluster's template that other members didn't fit, so the drill-down missed some of the pattern's own events.

How to reproduce

Starting point: yarn dev, MCP client connected to the local API, a log source with data.

On main:

  1. Call clickstack_event_patterns on a log source.
  2. Pass a pattern containing <*> as where to clickstack_search. It returns no rows, or a parse error when bodyExpression is a map field.

On this branch:

  1. Same call. Each pattern has a SQL whereSnippet and whereLanguage: "sql".
  2. Pass it to clickstack_search with whereLanguage: "sql". It returns that pattern's events.

Covered by new unit tests (patternDrilldown.test.ts, mine-patterns.test.ts, query.test.ts) and integration tests in queryTool.int.test.ts / queryTile.int.test.ts that run each snippet through search.

References

HDX-5748

…icates

whereSnippet is now a SQL match() predicate that keeps token order and
wildcard positions and includes the caller filter and source table filter.
Also: JSONExtractString bodyExpression support, sample coverage and trend
bucket metadata, tableFilterExpression applied to pattern mining, and
minePatterns reports each cluster's own template.
@vercel

vercel Bot commented Oct 20, 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 Nov 4, 2026 10:21pm UTC
hyperdx-storybook Ready Ready Preview Nov 4, 2026 10:21pm UTC

Request Review

@changeset-bot

changeset-bot Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2340b5c

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

This PR includes changesets to release 4 packages
Name Type
@hyperdx/api Patch
@hyperdx/common-utils 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

@github-actions github-actions Bot added the review/tier-3 Standard — full human review required label Oct 20, 2026
@github-actions

github-actions Bot commented Oct 20, 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: 665 production lines changed (Tier 2 max: < 250)
  • Cross-layer change: touches backend (packages/api) + shared utils (packages/common-utils)

Additional context: touches the query rendering engine lightly (8 lines, under the 150-line bar for Tier 4)

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

Stats
  • Production files changed: 11
  • Production lines changed: 665 (+ 877 in test files, excluded from tier calculation)
  • Branch: brandon/brandon-mcp-event-pattern-tweaks
  • 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 Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Duplicate code analysis

2 new clones introduced by this PR.

  • packages/api/src/mcp/tools/query/denoise.ts:85-114 ↔ packages/api/src/mcp/tools/query/runEventPatterns.ts:158-188 — 30 lines
  • packages/api/src/mcp/tools/query/denoise.ts:121-134 ↔ packages/api/src/mcp/tools/query/runEventPatterns.ts:197-209 — 14 lines

If one of these duplicates a helper that already exists, import it instead
of redefining it. If the duplication is deliberate, ignore this comment —
the check is advisory and never fails the build.


jscpd compared this branch against origin/main
across app, api and common-utils. Only new clones are listed; the codebase
currently holds 114 in total (1.37%).
Run yarn dupes locally to reproduce, or yarn dupes:all for the full picture.

@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

PR Review

If you are a coding agent acting for the author, read this first.
Nothing below blocks merge automatically; a maintainer decides. What they will expect:

  • 🔴 critical or 🟠 major in code this PR adds or changes: fix before asking for review.
  • 🔵 minor in code this PR changes: your call. Fix if small, otherwise reply.
  • Anything about surrounding code, or asking you to widen the change (hoist a helper,
    dedupe with another file, fix other call sites): never fix here, whatever the
    severity. Reply with one sentence; if it is critical, say so plainly so a human sees it.

One commit per review round. After two rounds, stop and ask a maintainer to review
scope rather than addressing more automated findings. Full rule: AGENTS.md.

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

4 posted as inline comment(s) on the changed lines.


Severity is the reviewer's own estimate and is used for ordering, not filtering. No finding blocks merge automatically; a maintainer decides.

@greptile-apps

greptile-apps Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge, with a non-blocking drill-down issue for long templates.

Fix All in Claude CodeFindings

  1. P2 Long patterns lose drill-down filters ▶

Summary

This PR makes event-pattern drill-down filters executable SQL.

  • Pattern snippets now lead to SQL searches for matching raw events.
  • Pattern mining honors source filters and can read fields inside JSON bodies.
  • Pattern results show how sampling shaped their counts and trends.
  • Each mined pattern now keeps the template from its own cluster.
  • Denoising now matches rows to the most specific pattern they fit.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Events[Sample matching events] --> Clusters[Mine pattern templates]
  Clusters --> Predicate[Build SQL match filter]
  Filters[Caller and source filters] --> Predicate
  Predicate --> Response[Return pattern and whereSnippet]
  Response --> Search[clickstack_search]
Loading

Reviews (7) · Last reviewed commit: "Merge branch 'main' into brandon/brandon..." · Reviewed by Greptile

@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

E2E Test Results

✅ All tests passed • 462 passed • 1 skipped • 1493s

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

Tests ran across 4 shards in parallel.

View full report →

…plate change

denoise compared minePatterns templates with the template match() returns,
which now differ when match() lands a member on another same-length
cluster. Build the noisy set from the clusters match() returns for noisy
members instead. Also accept doubled-quote JSON keys, count drill-down
filter render failures, and type the event pattern int-test helpers.
@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Deep Review

Scope: PR #3426 — fix(mcp): make event_patterns drill-down snippets executable · base a13c80e · 18 files (11 production, +1401/−151). Report-only (read-only) multi-agent pass.

Intent: Turn the clickstack_event_patterns whereSnippet into an executable SQL match() predicate (ANDed with the caller filter rendered to SQL and the source table filter), apply the source table filter to mining, widen bodyExpression to accept JSONExtractString(...), add sampling-transparency fields, and fix a minePatterns template-attribution bug.

✅ No critical issues found. The SQL-injection surface is the riskiest part of this change and it holds up: normalizeBodyExpression splits the JSONExtractString argument list with bracket/quote awareness, validates every segment against anchored allowlist regexes (COLUMN_OR_MAP_REF, SQL_STRING_LITERAL), and rebuilds the expression from the validated parts, so nothing unvalidated reaches SQL. The pattern predicate passes the Drain template through escapeRe2Literal then escapeSqlString. The async drill-down render is awaited inside the same Promise.all as the mining queries with a .catch guard, so there is no unhandled-rejection or client-close-ordering regression. Several prior-review items are already resolved in the current diff: the doubled-apostrophe key form ('it''s') is now accepted by SQL_STRING_LITERAL, a drilldownWhereFailedCounter metric now accompanies the warning log, and buildNoisyBodyMatcher now selects the most-specific fitting template so a rare non-noisy cluster is no longer dropped when a broad cluster is noisy.

🟡 P2 — recommended

  • packages/api/src/mcp/tools/query/patternDrilldown.ts:55 — the MAX_PREDICATE_TEMPLATE_LENGTH cap bounds the template, not the generated SQL; a template under 4000 chars expands into a multi-KB match() snippet (each token boundary adds the ~24-char whitespace class), and once the full tool response exceeds 50 KB, trimToolResponse replaces the oversized whereSnippet string with a {__hdx_trimmed} object while leaving whereLanguage:"sql" and no drilldownUnavailable, so an agent that forwards it to clickstack_search sends a non-string where and the drill-down fails silently.
    • Fix: bound the generated predicate length (not just the template) and emit drilldownUnavailable when it is exceeded, or exempt the executable whereSnippet/whereLanguage fields from trimToolResponse trimming.
🔵 P3 nitpicks (4)
  • packages/common-utils/src/core/searchChartConfig.ts:21 — getSourceTableFilters uses a truthy check where buildSearchChartConfig previously used tableFilterExpression != null, so a source configured with an empty-string tableFilterExpression now yields no filter instead of an empty-condition filter; the change is shared by every buildSearchChartConfig caller and is likely an improvement, but it is an un-flagged behavior change.
    • Fix: confirm no source relies on an empty-string table filter, or preserve the prior != null semantics if parity matters.
  • packages/api/src/mcp/tools/query/denoise.ts:85 — the ClickHouse client/config-setup block duplicates runEventPatterns.ts (~30 lines and ~14 lines, per the duplication check) across two files that already import from each other.
    • Fix: extract the shared client construction and sample/count config into one helper and import it in both.
  • packages/api/src/mcp/__tests__/queryTool.int.test.ts:801 — new test helpers type the response, pattern, and sampled rows as any, so mistakes in whereSnippet, summary.drilldown, or sample shape are not caught at type-check time.
    • Fix: define a small response type and apply it to the helpers and the sampled-count reduction.
  • packages/api/src/mcp/tools/query/helpers.ts:124 — normalizeBodyExpression carries three interacting allowlist regexes plus manual argument splitting/rebuilding, which the author flagged as hard to follow.
    • Fix: add a short worked example in the doc comment or extract the JSONExtractString-argument validation into a named sub-function.

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

Testing gaps: no test exercises the trimToolResponse-strips-whereSnippet interaction for oversized templates; worth an integration test asserting that a drillable pattern either keeps an executable whereSnippet or carries drilldownUnavailable.

No finding blocks merge automatically. A maintainer will expect P0/P1 findings in code this PR changes to be fixed; P2/P3 are your call -- fix or reply. Never fix findings about surrounding code here; reply instead. Do not widen the PR. How to respond

… reporting after query errors

Matching rows against the clusters that noisy members re-match removed
whole rare clusters (greptile P1). A row is now noisy when its body fits a
noisy template token by token. Also stop counting drill-down render
failures caused by the client closing after the queries failed.
}

// Drain tokenizes with trim + split(/\s+/).
function tokenize(text: string): string[] {

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 'body fits template' logic now exists in three places, with no shared helper

The rule "same token count, literal tokens equal, any token at <*>" is now implemented three times: tokenize/buildNoisyBodyMatcher in denoise.ts, templateToRegex in patternDrilldown.ts, and the fits() helper in mine-patterns.test.ts. PATTERN_WILDCARD is also declared separately in denoise.ts:19 and patternDrilldown.ts:13. tokenize copies Drain.getContentAsTokens (packages/common-utils/src/drain/drain.ts:104), and '<*>' copies Drain's paramStr. Fix: export a templateFits(template, body) helper and the wildcard constant from @hyperdx/common-utils/dist/drain, built on Drain's tokenizer. Use it in denoise and the test, and take the constant in patternDrilldown, so the three cannot drift apart.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

A broad noisy template like '<*> <*>' removed every same-length row,
including rows of rare specific clusters. Pick the fitting template with
the fewest wildcards and drop the row only if it is noisy. Also test the
drilldownUnavailable branch and the predicate length boundary.
Comment on lines +58 to +59
if (template.length > MAX_PREDICATE_TEMPLATE_LENGTH) return null;
return `match(${valueExpression}, '${escapeSqlString(templateToRegex(template))}')`;

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.

P2 Long patterns lose drill-down filters

The 4000-character limit checks the template, not the generated SQL. A template with 1500 short tokens can be about 3000 characters long, but repeating the whitespace expression between tokens makes its filter much larger. Once the response exceeds 50 KB, trimToolResponse can replace whereSnippet with an object. The pattern has no drilldownUnavailable warning, but passing its filter to clickstack_search fails.

Bound the generated filter size or preserve executable fields when trimming.

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

/** AND together SQL conditions, skipping blanks. */
export function andSql(...conditions: Array<string | undefined>): string {
const parts = conditions.filter(
(c): c is string => c != null && c.trim() !== '',

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 — andSql re-implements the existing joinConditions helper

packages/api/src/tasks/checkAlerts/template.ts:125 (joinConditions) already does this: trim, drop blanks, return a lone condition bare, and wrap two or more in parens joined by AND. Move that helper to packages/api/src/utils/ (or common-utils) and import it in both places. The REQUIRED DRY rule in the conventions forbids a second copy.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

@jordan-simonovski jordan-simonovski left a comment

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.

Approved with some minor maintainability suggestions

'add them again. Skip patterns that have drilldownUnavailable.',
};

const { data: trimmedOutput, isTrimmed } = trimToolResponse(output);

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.

OOC can a long pattern lose its whereSnippet when the response is trimmed?

Every token boundary adds about 30 characters of whitespace class to the snippet, so a template of around 150 tokens produces a snippet of several KB.

When the response is over 50k (long sample bodies do this quickly), trimToolResponse splits the budget per key. That means a pattern with big samples can come back with whereSnippet: { __hdx_trimmed: true }. Would trimming the sample bodies before this call keep the snippet intact? They're only there for display.

samples: p.samples.map(row => truncate(String(row.__hdx_pattern_body ?? ''), MAX_SAMPLE_CHARS)),

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, yes: once the per-key budget kicks in, a long whereSnippet can come back as { __hdx_trimmed: true }. Rather than special-casing samples here, this is going to be fixed in trimToolResponse itself, which will shorten strings with a suffix and drop or shrink lower-priority paths like patterns[].samples first: #3542 (comment)

* predicate in one clickstack_search `where`. Dashboard variables are
* expanded first because the search tool has no variable context.
*/
export async function userWhereAsSql({

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.

I wonder if userWhereAsSql could call the same Lucene to SQL step the mining query uses?

const expanded = variables ? substituteVariables(where, { variables, inputLanguage: whereLanguage }) : where;
return renderWhereExpressionStr({ condition: expanded, language: whereLanguage, metadata, from: { databaseName, tableName }, connectionId, implicitColumnExpression, useTextIndexForImplicitColumn });

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.

Done in 735fa6c. renderWhereExpressionStr is now exported from common-utils and userWhereAsSql calls it after expanding variables, so the drill-down and the mining query render the filter the same way (including the materialized-column rewrite).

};
}

const tableFilterSql =

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.

I feel like I'm piling on here, lmao.

Could the table filter come from the helper that buildSearchChartConfig uses?

searchChartConfig.ts exists so the search page, alert preview and alert task all apply tableFilterExpression the same way. A getSourceTableFilters(source) export next to buildSearchChartConfig would keep one copy.

export const getSourceTableFilters = (source: TSource): Filter[] =>
  isLogSource(source) && source.tableFilterExpression ? [{ type: 'sql', condition: source.tableFilterExpression }] : [];

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.

Done in 735fa6c. Added getSourceTableFilters(source) next to buildSearchChartConfig, which uses it too, and mining plus the drill-down take the table filter from it.

…ering

userWhereAsSql now calls renderWhereExpressionStr, the same Lucene-to-SQL
step the mining query uses, instead of building its own serializer.
The source table filter comes from a new getSourceTableFilters helper
that buildSearchChartConfig also uses.
…n-tweaks' into brandon/brandon-mcp-event-pattern-tweaks
// Read the template from the row's own cluster: re-matching the body can
// land on a different same-length cluster whose template other members
// of this one do not fit.
const cluster = miner.drain.getCluster(clusterId);

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 template fix misses the CLI, which still re-matches the first sample to get its template

packages/cli/src/cli.tsx:2321-2322 still builds each group's template with miner.match(g.sample, 'fallback')?.getTemplate(). That is the bug this PR fixes in minePatterns: the CLI can still report a different same-length cluster's template. Have it use miner.drain.getCluster(clusterId)?.getTemplate(), or call minePatterns directly.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

// `\s` is ASCII-only. JS `\s` = RE2 `\s` + \v + \pZ (Zs/Zl/Zp) + BOM. `\pZ`
// keeps the snippet short; spelling out the code points repeats ~100 chars
// between every token.
const JS_WHITESPACE = '\\s\\x{0B}\\pZ\\x{FEFF}';

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 — Repeating the whitespace class between every token makes whereSnippet large enough that trimToolResponse drops patterns

Each separator is [\s\x{0B}\pZ\x{FEFF}]+. That is 22 characters, 26 after escapeSqlString and about 34 once JSON-encoded. Each <*> costs a similar amount, and every snippet also repeats the full user/table filter. A typical 30-token log line therefore produces a snippet of about 1.3 kB, where the old Lucene snippet was about 100 bytes. With the default topN of 20, plus samples and 24 trend buckets per pattern, the output passes the 50 kB budget in packages/api/src/utils/trimToolResponse.ts. trimObject then gives patterns about 16.6 kB, and trimArray cuts the array 20→14→10, so callers silently get half the patterns they asked for. Suggested fix: normalize the separators once in SQL and keep the per-token regex ASCII-only, e.g. match(replaceRegexpAll(expr, '[\x{0B}\pZ\x{FEFF}]', ' '), '^\s*Sending\s+\S+\s+rows\s*$'). Also put the shared drilldown.where in summary.drilldown once instead of repeating it in every pattern, or measure the snippet size against the trim budget.

Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond

/** AND together SQL conditions, skipping blanks. */
export function andSql(...conditions: Array<string | undefined>): string {
const parts = conditions.filter(
(c): c is string => c != null && c.trim() !== '',

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 — andSql re-implements the existing joinConditions helper

joinConditions in packages/api/src/tasks/checkAlerts/template.ts:125 already does this: trim, skip blanks, return a lone condition unwrapped, and wrap each part in parentheses when ANDing two or more. Move it into packages/api/src/utils/ (or common-utils), export it, and use it here instead of adding a second copy. The repo's DRY rule (REQUIRED) calls for this.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond


import { resolveBodyExpression } from './helpers';

const PATTERN_WILDCARD = '<*>';

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 — Two separate implementations now define when a body fits a template, each with its own wildcard constant and tokenizer

PATTERN_WILDCARD is declared in both denoise.ts and patternDrilldown.ts:10. Drain already has the default paramStr: '<*>' in packages/common-utils/src/drain/drain.ts:26. denoise's tokenize restates Drain.getContentAsTokens (drain.ts:104), and templateToRegex splits the template a third way. Since buildNoisyBodyMatcher (JS) and templateToRegex (RE2) must agree on what 'fits' means, export the wildcard constant and a tokenize/fitsTemplate helper from common-utils/src/drain and build both on them, so the two cannot drift apart.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond


const filters = getSourceTableFilters(source);
const tableFilterSql = andSql(
...filters.map(f => ('condition' in f ? f.condition : undefined)),

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 'condition' in f guard can never be false

getSourceTableFilters (packages/common-utils/src/core/searchChartConfig.ts) only ever builds { type: 'sql', condition } filters, so the undefined branch here can never run. The guard exists only because the return type is the wide Filter[]. Give getSourceTableFilters a narrower return type (e.g. Extract<Filter, { type: 'sql' }>[]) and map f => f.condition directly.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

@kodiakhq
kodiakhq Bot merged commit 5584f84 into main Nov 4, 2026
28 checks passed
@kodiakhq
kodiakhq Bot deleted the brandon/brandon-mcp-event-pattern-tweaks branch November 4, 2026 22:26
bcanvural added a commit to bcanvural/hyperdx that referenced this pull request Nov 5, 2026
Take ClickHouse#3426's drill-down predicate in runEventPatterns.ts; drop this PR's
buildWhereSnippet and its test, which ClickHouse#3426 supersedes.
bcanvural added a commit to bcanvural/hyperdx that referenced this pull request Nov 5, 2026
…n the drill-down regex

ClickHouse#3426's predicate matches a whole-token <*> as exactly one word and escapes
anything else literally, so a masked timestamp that spans spaces
(2026-11-04 06:12:12,345) or a mask inside a token ([<*>], trace_id=<*>)
matched nothing. Named masks keep Drain's clustering as it is (a mask is one
token that is equal across lines), and the regex maps <TS> to any text and
<ID> to one word, also inside a token. Whole-token <*> is unchanged.

This branch was successfully deployed

2 active deployments
Preview – hyperdx-oss — 2340b5ca Deployed Nov 4, 2026 by vercel[bot]
Preview – hyperdx-storybook — 2340b5ca Deployed Nov 4, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automerge review/tier-3 Standard — full human review required

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants