From 3202993563a7f496ff549e490fd1dc94b4015234 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Mon, 5 Oct 2026 10:55:16 -0400 Subject: [PATCH 1/9] feat(search-events): Teach the logs query agent regex message search Add logs text-matching guidance to the embedded query-translation agent prompt so it uses `key://pattern//` regex filters when wildcards cannot express the request, and add regex few-shot examples for the logs dataset. Refs LOGS-1014 Co-Authored-By: Claude Code --- .../search-events-api-patterns.md | 1 + .../src/tools/support/search-events/config.ts | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/docs/contributing/search-events-api-patterns.md b/docs/contributing/search-events-api-patterns.md index b6299c234..b4e159e43 100644 --- a/docs/contributing/search-events-api-patterns.md +++ b/docs/contributing/search-events-api-patterns.md @@ -114,6 +114,7 @@ https://us.sentry.io/api/0/organizations/sentry/events/?dataset=spans&field=ai.m - Does NOT support timestamp filters in query (use `statsPeriod` instead) - Severity levels: fatal, error, warning, info, debug, trace - Common aggregate functions: `count()`, `epm()` +- Regex filters on string attributes: `message://timeout after \d+ms//` (RE2, unquoted, max 64 characters); prefer wildcards for plain substrings #### Metrics Dataset - Represents newer span metrics, including counters, gauges, and distributions diff --git a/packages/mcp-core/src/tools/support/search-events/config.ts b/packages/mcp-core/src/tools/support/search-events/config.ts index 27d4a42a4..38228b4e8 100644 --- a/packages/mcp-core/src/tools/support/search-events/config.ts +++ b/packages/mcp-core/src/tools/support/search-events/config.ts @@ -122,6 +122,17 @@ REPLAY SEARCH RULES: - If the user asks about replays they have viewed, prefer viewed_by_me:true - If the user asks about replay users and says "me", use whoami and translate to user.email: +LOGS TEXT MATCHING (LOGS DATASET ONLY): +- Plain word or phrase: use wildcards, e.g. message:"*database*". Do NOT use a regex when a substring match is enough +- Use a regex filter key://pattern// when wildcards cannot express the request: number shapes (\\d+), alternation (a|b), anchoring (^ starts with, $ ends with), character classes ([0-9a-f]), or structured values like IPs, UUIDs, and status codes + - Example: message://timeout after \\d+ms// + - Negate with a leading !: !message://job \\d+ completed// + - There is no list form; use alternation: message://(ConnectionReset|ReadTimeout)Error// +- NEVER quote a regex: message:"//...//" is a literal string match, not a regex. Spaces and parentheses inside the pattern are fine unquoted +- Regex uses RE2 syntax (no lookarounds or backreferences), matches anywhere unless anchored, and is case sensitive; prefix the pattern with (?i) to ignore case +- Patterns are limited to 64 characters (an escape like \\d counts as one). Write \\/\\/ to match a literal // +- Regex only works on string attributes, and only in the logs dataset; other datasets treat //...// as a literal value + MATHEMATICAL QUERY PATTERNS: When user asks mathematical questions like "how many X", "total Y used", "sum of Z": - Identify the appropriate dataset based on context @@ -720,6 +731,33 @@ export const DATASET_EXAMPLES: Record< sort: "-timestamp", }, }, + { + description: + "logs whose message reports a retry count like 'retry 3 of 5'", + output: { + query: "message://retry \\d+ of \\d+//", + fields: ["timestamp", "message", "severity", "trace"], + sort: "-timestamp", + }, + }, + { + description: + "error logs whose message starts with ConnectionReset or ReadTimeout", + output: { + query: "severity:error AND message://^(ConnectionReset|ReadTimeout)//", + fields: ["timestamp", "message", "severity", "trace"], + sort: "-timestamp", + }, + }, + { + description: + "logs excluding cache hit messages like 'cache hit for key user:42'", + output: { + query: "!message://^cache hit for key \\S+//", + fields: ["timestamp", "message", "severity", "trace"], + sort: "-timestamp", + }, + }, ], tracemetrics: [ { From 5623a6580a8bb48f2ce95aa1d9abfb2f2cf1f092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Mon, 5 Oct 2026 12:41:22 -0400 Subject: [PATCH 2/9] fix(search-events): Keep the regex prompt from favoring regex over wildcards Teach prefix, suffix, and wildcard-list matching so plain alternatives and anchors don't reach for a regex, keep exact value sets as key:[a,b], and swap examples that overlapped the regex evals or needed no regex. Refs LOGS-1014 Co-Authored-By: Claude Opus 5.5 --- .../search-events-api-patterns.md | 2 +- .../src/tools/support/search-events/config.ts | 25 +++++++++++-------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/docs/contributing/search-events-api-patterns.md b/docs/contributing/search-events-api-patterns.md index b4e159e43..5fe627927 100644 --- a/docs/contributing/search-events-api-patterns.md +++ b/docs/contributing/search-events-api-patterns.md @@ -114,7 +114,7 @@ https://us.sentry.io/api/0/organizations/sentry/events/?dataset=spans&field=ai.m - Does NOT support timestamp filters in query (use `statsPeriod` instead) - Severity levels: fatal, error, warning, info, debug, trace - Common aggregate functions: `count()`, `epm()` -- Regex filters on string attributes: `message://timeout after \d+ms//` (RE2, unquoted, max 64 characters); prefer wildcards for plain substrings +- Regex filters on string attributes: `message://timeout after \d+ms//` (RE2, unquoted, max 64 characters); prefer wildcards for plain substrings, prefixes, and suffixes #### Metrics Dataset - Represents newer span metrics, including counters, gauges, and distributions diff --git a/packages/mcp-core/src/tools/support/search-events/config.ts b/packages/mcp-core/src/tools/support/search-events/config.ts index 38228b4e8..659fc24f3 100644 --- a/packages/mcp-core/src/tools/support/search-events/config.ts +++ b/packages/mcp-core/src/tools/support/search-events/config.ts @@ -123,14 +123,17 @@ REPLAY SEARCH RULES: - If the user asks about replay users and says "me", use whoami and translate to user.email: LOGS TEXT MATCHING (LOGS DATASET ONLY): -- Plain word or phrase: use wildcards, e.g. message:"*database*". Do NOT use a regex when a substring match is enough -- Use a regex filter key://pattern// when wildcards cannot express the request: number shapes (\\d+), alternation (a|b), anchoring (^ starts with, $ ends with), character classes ([0-9a-f]), or structured values like IPs, UUIDs, and status codes - - Example: message://timeout after \\d+ms// - - Negate with a leading !: !message://job \\d+ completed// - - There is no list form; use alternation: message://(ConnectionReset|ReadTimeout)Error// -- NEVER quote a regex: message:"//...//" is a literal string match, not a regex. Spaces and parentheses inside the pattern are fine unquoted +- Plain word, phrase, prefix, or suffix: use wildcards. message:"*database*" contains, message:"database*" starts with, message:"*database" ends with. Do NOT use a regex when a wildcard is enough +- Any of several plain words or phrases: use a wildcard list, e.g. message:["*ConnectionReset*","*ReadTimeout*"] +- Use a regex filter key://pattern// when wildcards cannot express the request: number shapes (\\d+), character classes ([0-9a-f]), or structured values like IPs, UUIDs, and status codes. Use alternation (a|b) and anchors (^ or $) only alongside one of these + - Example: message://request took \\d+ms// + - Negate with a leading !: !message://worker \\d+ ready// + - There is no regex list form (key:[//a//,//b//] is a literal list); put alternatives inside one pattern: message://(upload|download) of \\d+ bytes failed// + - For an exact set of values, keep the list: severity:[error,fatal], not severity://error|fatal// +- NEVER quote a regex: message:"//...//" is a literal string match, not a regex +- The pattern ends at the first // followed by a space, ) or the end of the query. Spaces, parentheses, and a // followed by anything else (https?://host) are fine unquoted; write \\/\\/ for a literal // followed by a space or ) - Regex uses RE2 syntax (no lookarounds or backreferences), matches anywhere unless anchored, and is case sensitive; prefix the pattern with (?i) to ignore case -- Patterns are limited to 64 characters (an escape like \\d counts as one). Write \\/\\/ to match a literal // +- Patterns are limited to 64 characters (an escape like \\d counts as one) - Regex only works on string attributes, and only in the logs dataset; other datasets treat //...// as a literal value MATHEMATICAL QUERY PATTERNS: @@ -742,18 +745,18 @@ export const DATASET_EXAMPLES: Record< }, { description: - "error logs whose message starts with ConnectionReset or ReadTimeout", + "error logs whose message contains a hex trace id like 'trace=4bf92f3577b34da6a3ce929d0e0e4736'", output: { - query: "severity:error AND message://^(ConnectionReset|ReadTimeout)//", + query: "severity:error AND message://trace=[0-9a-f]{32}//", fields: ["timestamp", "message", "severity", "trace"], sort: "-timestamp", }, }, { description: - "logs excluding cache hit messages like 'cache hit for key user:42'", + "logs excluding heartbeat messages like 'heartbeat seq=1042'", output: { - query: "!message://^cache hit for key \\S+//", + query: "!message://^heartbeat seq=\\d+$//", fields: ["timestamp", "message", "severity", "trace"], sort: "-timestamp", }, From c6fc093c89ff829b3132d56cad5132219efeb9e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Mon, 5 Oct 2026 15:01:33 -0400 Subject: [PATCH 3/9] fix(search-events): Point the logs agent at attributes before message regex A value with its own attribute, like a response status, should filter that attribute rather than a regex over message text. Refs LOGS-1014 Co-Authored-By: Claude Opus 5.5 --- packages/mcp-core/src/tools/support/search-events/config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/mcp-core/src/tools/support/search-events/config.ts b/packages/mcp-core/src/tools/support/search-events/config.ts index 659fc24f3..c06f04d93 100644 --- a/packages/mcp-core/src/tools/support/search-events/config.ts +++ b/packages/mcp-core/src/tools/support/search-events/config.ts @@ -130,6 +130,7 @@ LOGS TEXT MATCHING (LOGS DATASET ONLY): - Negate with a leading !: !message://worker \\d+ ready// - There is no regex list form (key:[//a//,//b//] is a literal list); put alternatives inside one pattern: message://(upload|download) of \\d+ bytes failed// - For an exact set of values, keep the list: severity:[error,fatal], not severity://error|fatal// +- Regex on message is for text inside the message. When the user asks about a value that has its own attribute (e.g. response status), filter that attribute instead - NEVER quote a regex: message:"//...//" is a literal string match, not a regex - The pattern ends at the first // followed by a space, ) or the end of the query. Spaces, parentheses, and a // followed by anything else (https?://host) are fine unquoted; write \\/\\/ for a literal // followed by a space or ) - Regex uses RE2 syntax (no lookarounds or backreferences), matches anywhere unless anchored, and is case sensitive; prefix the pattern with (?i) to ignore case From 37ffbe21a96dbff06abc5b9b67f460882dd4981d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Mon, 5 Oct 2026 15:22:36 -0400 Subject: [PATCH 4/9] fix(search-events): Teach regex for ignoring case and drop the trace-id example Wildcards are case sensitive, so ignoring case is a regex trigger; the trace id few-shot is replaced since trace has its own attribute, and array attributes are noted as regex-capable. Refs LOGS-1014 Co-Authored-By: Claude Opus 5.5 --- docs/contributing/search-events-api-patterns.md | 2 +- .../mcp-core/src/tools/support/search-events/config.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/contributing/search-events-api-patterns.md b/docs/contributing/search-events-api-patterns.md index 5fe627927..ab98803c5 100644 --- a/docs/contributing/search-events-api-patterns.md +++ b/docs/contributing/search-events-api-patterns.md @@ -114,7 +114,7 @@ https://us.sentry.io/api/0/organizations/sentry/events/?dataset=spans&field=ai.m - Does NOT support timestamp filters in query (use `statsPeriod` instead) - Severity levels: fatal, error, warning, info, debug, trace - Common aggregate functions: `count()`, `epm()` -- Regex filters on string attributes: `message://timeout after \d+ms//` (RE2, unquoted, max 64 characters); prefer wildcards for plain substrings, prefixes, and suffixes +- Regex filters on string and array attributes: `message://timeout after \d+ms//` (RE2, unquoted, max 64 characters); prefer wildcards for plain substrings, prefixes, and suffixes #### Metrics Dataset - Represents newer span metrics, including counters, gauges, and distributions diff --git a/packages/mcp-core/src/tools/support/search-events/config.ts b/packages/mcp-core/src/tools/support/search-events/config.ts index c06f04d93..6615c242d 100644 --- a/packages/mcp-core/src/tools/support/search-events/config.ts +++ b/packages/mcp-core/src/tools/support/search-events/config.ts @@ -125,7 +125,7 @@ REPLAY SEARCH RULES: LOGS TEXT MATCHING (LOGS DATASET ONLY): - Plain word, phrase, prefix, or suffix: use wildcards. message:"*database*" contains, message:"database*" starts with, message:"*database" ends with. Do NOT use a regex when a wildcard is enough - Any of several plain words or phrases: use a wildcard list, e.g. message:["*ConnectionReset*","*ReadTimeout*"] -- Use a regex filter key://pattern// when wildcards cannot express the request: number shapes (\\d+), character classes ([0-9a-f]), or structured values like IPs, UUIDs, and status codes. Use alternation (a|b) and anchors (^ or $) only alongside one of these +- Use a regex filter key://pattern// when wildcards cannot express the request: number shapes (\\d+), character classes ([0-9a-f]), structured values like IPs, UUIDs, and status codes, or ignoring case (wildcards are case sensitive). Use alternation (a|b) and anchors (^ or $) only alongside one of these - Example: message://request took \\d+ms// - Negate with a leading !: !message://worker \\d+ ready// - There is no regex list form (key:[//a//,//b//] is a literal list); put alternatives inside one pattern: message://(upload|download) of \\d+ bytes failed// @@ -135,7 +135,7 @@ LOGS TEXT MATCHING (LOGS DATASET ONLY): - The pattern ends at the first // followed by a space, ) or the end of the query. Spaces, parentheses, and a // followed by anything else (https?://host) are fine unquoted; write \\/\\/ for a literal // followed by a space or ) - Regex uses RE2 syntax (no lookarounds or backreferences), matches anywhere unless anchored, and is case sensitive; prefix the pattern with (?i) to ignore case - Patterns are limited to 64 characters (an escape like \\d counts as one) -- Regex only works on string attributes, and only in the logs dataset; other datasets treat //...// as a literal value +- Regex only works on string and array attributes, and only in the logs dataset; other datasets treat //...// as a literal value MATHEMATICAL QUERY PATTERNS: When user asks mathematical questions like "how many X", "total Y used", "sum of Z": @@ -746,9 +746,9 @@ export const DATASET_EXAMPLES: Record< }, { description: - "error logs whose message contains a hex trace id like 'trace=4bf92f3577b34da6a3ce929d0e0e4736'", + "error logs whose message contains a hex error code like 'err=0x8007000e'", output: { - query: "severity:error AND message://trace=[0-9a-f]{32}//", + query: "severity:error AND message://err=0x[0-9a-f]+//", fields: ["timestamp", "message", "severity", "trace"], sort: "-timestamp", }, From ef38959f6b93c857072138658433cf5cf11d1f4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Mon, 5 Oct 2026 15:35:13 -0400 Subject: [PATCH 5/9] fix(search-events): Say regex works on string attributes only Array regex needs the tags[name,array][*] key form, so naming arrays without it invites a plain key regex that silently matches nothing. Refs LOGS-1014 Co-Authored-By: Claude Opus 5.5 --- docs/contributing/search-events-api-patterns.md | 2 +- packages/mcp-core/src/tools/support/search-events/config.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/contributing/search-events-api-patterns.md b/docs/contributing/search-events-api-patterns.md index ab98803c5..5fe627927 100644 --- a/docs/contributing/search-events-api-patterns.md +++ b/docs/contributing/search-events-api-patterns.md @@ -114,7 +114,7 @@ https://us.sentry.io/api/0/organizations/sentry/events/?dataset=spans&field=ai.m - Does NOT support timestamp filters in query (use `statsPeriod` instead) - Severity levels: fatal, error, warning, info, debug, trace - Common aggregate functions: `count()`, `epm()` -- Regex filters on string and array attributes: `message://timeout after \d+ms//` (RE2, unquoted, max 64 characters); prefer wildcards for plain substrings, prefixes, and suffixes +- Regex filters on string attributes: `message://timeout after \d+ms//` (RE2, unquoted, max 64 characters); prefer wildcards for plain substrings, prefixes, and suffixes #### Metrics Dataset - Represents newer span metrics, including counters, gauges, and distributions diff --git a/packages/mcp-core/src/tools/support/search-events/config.ts b/packages/mcp-core/src/tools/support/search-events/config.ts index 6615c242d..e67923bf7 100644 --- a/packages/mcp-core/src/tools/support/search-events/config.ts +++ b/packages/mcp-core/src/tools/support/search-events/config.ts @@ -135,7 +135,7 @@ LOGS TEXT MATCHING (LOGS DATASET ONLY): - The pattern ends at the first // followed by a space, ) or the end of the query. Spaces, parentheses, and a // followed by anything else (https?://host) are fine unquoted; write \\/\\/ for a literal // followed by a space or ) - Regex uses RE2 syntax (no lookarounds or backreferences), matches anywhere unless anchored, and is case sensitive; prefix the pattern with (?i) to ignore case - Patterns are limited to 64 characters (an escape like \\d counts as one) -- Regex only works on string and array attributes, and only in the logs dataset; other datasets treat //...// as a literal value +- Regex only works on string attributes, and only in the logs dataset; other datasets treat //...// as a literal value MATHEMATICAL QUERY PATTERNS: When user asks mathematical questions like "how many X", "total Y used", "sum of Z": From 5138386af0c4de5f76d00abbd0341d3107b1b255 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Mon, 5 Oct 2026 15:38:22 -0400 Subject: [PATCH 6/9] fix(search-events): Ask for short regex patterns near the 64-character limit An unneeded \b or (?i) pushed a UUID pattern past Sentry's limit in an eval run, so the length rule now says to leave them out unless needed. Refs LOGS-1014 Co-Authored-By: Claude Opus 5.5 --- packages/mcp-core/src/tools/support/search-events/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mcp-core/src/tools/support/search-events/config.ts b/packages/mcp-core/src/tools/support/search-events/config.ts index e67923bf7..be809795f 100644 --- a/packages/mcp-core/src/tools/support/search-events/config.ts +++ b/packages/mcp-core/src/tools/support/search-events/config.ts @@ -134,7 +134,7 @@ LOGS TEXT MATCHING (LOGS DATASET ONLY): - NEVER quote a regex: message:"//...//" is a literal string match, not a regex - The pattern ends at the first // followed by a space, ) or the end of the query. Spaces, parentheses, and a // followed by anything else (https?://host) are fine unquoted; write \\/\\/ for a literal // followed by a space or ) - Regex uses RE2 syntax (no lookarounds or backreferences), matches anywhere unless anchored, and is case sensitive; prefix the pattern with (?i) to ignore case -- Patterns are limited to 64 characters (an escape like \\d counts as one) +- Patterns are limited to 64 characters (an escape like \\d counts as one), so leave out \\b and (?i) unless the request needs them - Regex only works on string attributes, and only in the logs dataset; other datasets treat //...// as a literal value MATHEMATICAL QUERY PATTERNS: From b3ce40205dbeac6c2e6b5fbed96eb03fe7eca6f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Mon, 5 Oct 2026 16:36:32 -0400 Subject: [PATCH 7/9] fix(search-events): Forbid regex filters outside the logs dataset The agent still wrote key://pattern// for errors in some eval runs, so the rule now names the other datasets and says to use wildcards there. Refs LOGS-1014 Co-Authored-By: Claude Opus 5.5 --- packages/mcp-core/src/tools/support/search-events/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mcp-core/src/tools/support/search-events/config.ts b/packages/mcp-core/src/tools/support/search-events/config.ts index be809795f..b411aa0bb 100644 --- a/packages/mcp-core/src/tools/support/search-events/config.ts +++ b/packages/mcp-core/src/tools/support/search-events/config.ts @@ -135,7 +135,7 @@ LOGS TEXT MATCHING (LOGS DATASET ONLY): - The pattern ends at the first // followed by a space, ) or the end of the query. Spaces, parentheses, and a // followed by anything else (https?://host) are fine unquoted; write \\/\\/ for a literal // followed by a space or ) - Regex uses RE2 syntax (no lookarounds or backreferences), matches anywhere unless anchored, and is case sensitive; prefix the pattern with (?i) to ignore case - Patterns are limited to 64 characters (an escape like \\d counts as one), so leave out \\b and (?i) unless the request needs them -- Regex only works on string attributes, and only in the logs dataset; other datasets treat //...// as a literal value +- Regex only works on string attributes in the logs dataset. NEVER write key://pattern// for errors, spans, metrics, or any other dataset: they match //...// literally and return nothing. Approximate the shape with wildcards there, e.g. message:"*retry*failed*" MATHEMATICAL QUERY PATTERNS: When user asks mathematical questions like "how many X", "total Y used", "sum of Z": From ea918c5be75799c4dcda91e81168596bdbee8965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Mon, 5 Oct 2026 17:03:56 -0400 Subject: [PATCH 8/9] fix(search-events): Prefer (?i) over mixed-case classes to fit the regex limit An eval run spelled a UUID with [0-9a-fA-F], landing at 75 characters, so the length rule now says to match either case with (?i) and a lowercase class, which fits a UUID in 64. Refs LOGS-1014 Co-Authored-By: Claude Opus 5.5 --- packages/mcp-core/src/tools/support/search-events/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mcp-core/src/tools/support/search-events/config.ts b/packages/mcp-core/src/tools/support/search-events/config.ts index b411aa0bb..3c344263c 100644 --- a/packages/mcp-core/src/tools/support/search-events/config.ts +++ b/packages/mcp-core/src/tools/support/search-events/config.ts @@ -134,7 +134,7 @@ LOGS TEXT MATCHING (LOGS DATASET ONLY): - NEVER quote a regex: message:"//...//" is a literal string match, not a regex - The pattern ends at the first // followed by a space, ) or the end of the query. Spaces, parentheses, and a // followed by anything else (https?://host) are fine unquoted; write \\/\\/ for a literal // followed by a space or ) - Regex uses RE2 syntax (no lookarounds or backreferences), matches anywhere unless anchored, and is case sensitive; prefix the pattern with (?i) to ignore case -- Patterns are limited to 64 characters (an escape like \\d counts as one), so leave out \\b and (?i) unless the request needs them +- Patterns are limited to 64 characters (an escape like \\d counts as one), so keep them short: leave out \\b unless the request needs it, and match either case with (?i)[0-9a-f] rather than [0-9a-fA-F] - Regex only works on string attributes in the logs dataset. NEVER write key://pattern// for errors, spans, metrics, or any other dataset: they match //...// literally and return nothing. Approximate the shape with wildcards there, e.g. message:"*retry*failed*" MATHEMATICAL QUERY PATTERNS: From 3a464ea4ba4b184e6be5a60a9cc0b1a1c012e817 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Tue, 13 Oct 2026 11:12:54 -0400 Subject: [PATCH 9/9] fix(search-events): Recognize regex filters in search syntax detection Co-Authored-By: Claude Opus 5.5 --- .../tools/support/search-events/utils.test.ts | 52 +++++++++++++++++++ .../src/tools/support/search-events/utils.ts | 41 ++++++++++++--- 2 files changed, 87 insertions(+), 6 deletions(-) diff --git a/packages/mcp-core/src/tools/support/search-events/utils.test.ts b/packages/mcp-core/src/tools/support/search-events/utils.test.ts index e7e84857b..b156e46f9 100644 --- a/packages/mcp-core/src/tools/support/search-events/utils.test.ts +++ b/packages/mcp-core/src/tools/support/search-events/utils.test.ts @@ -290,8 +290,25 @@ describe("search query helpers", () => { ); }); + it("should detect regex filters as structured Sentry search syntax", () => { + expect( + looksLikeSentrySearchSyntax(String.raw`message://request took \d+ms//`), + ).toBe(true); + expect( + looksLikeSentrySearchSyntax(String.raw`!message://worker \d+ ready//`), + ).toBe(true); + expect( + looksLikeSentrySearchSyntax( + String.raw`message://https?://api\.example\.com//`, + ), + ).toBe(true); + }); + it("should ignore common natural language colon patterns", () => { expect(looksLikeSentrySearchSyntax("open http://example.com")).toBe(false); + expect(looksLikeSentrySearchSyntax("see http://example.com/a/")).toBe( + false, + ); expect(looksLikeSentrySearchSyntax("started at 10:30")).toBe(false); expect(looksLikeSentrySearchSyntax("Note: show slow spans")).toBe(false); expect(looksLikeSentrySearchSyntax("ERROR: service is down")).toBe(false); @@ -392,6 +409,41 @@ describe("search query helpers", () => { ); expect(isSemanticFilterDowngrade("id:1", 'message:"error 1"')).toBe(true); }); + + it("detects message regex downgrades of structured filters", () => { + expect( + isSemanticFilterDowngrade( + "http.status_code:500", + "message://status 500//", + ), + ).toBe(true); + expect( + isSemanticFilterDowngrade( + "user.id:42 severity:error", + String.raw`severity:error message://(?i)user 42 failed//`, + ), + ).toBe(true); + + expect( + isSemanticFilterDowngrade( + "http.status_code:500", + String.raw`http.status_code:500 message://took \d+ms//`, + ), + ).toBe(false); + + // A key:value inside a regex is pattern text, not a kept structured filter. + expect( + isSemanticFilterDowngrade("custom:foo", "message://custom:foo//"), + ).toBe(true); + + // A quote inside a regex must not hide the filters after it. + expect( + isSemanticFilterDowngrade( + "custom:foo", + String.raw`message://it's \d+// message:"*foo*"`, + ), + ).toBe(true); + }); }); describe("fetchCustomAttributes", () => { diff --git a/packages/mcp-core/src/tools/support/search-events/utils.ts b/packages/mcp-core/src/tools/support/search-events/utils.ts index a8d699d7c..3e6b67daf 100644 --- a/packages/mcp-core/src/tools/support/search-events/utils.ts +++ b/packages/mcp-core/src/tools/support/search-events/utils.ts @@ -30,8 +30,14 @@ export type FlexibleEventData = Record; const DEFAULT_MAX_VALUE_LENGTH = 200; const DEFAULT_MAX_ARRAY_ITEMS = 20; -const SENTRY_SEARCH_TOKEN_PATTERN = - /(^|\s)!?([A-Za-z_][A-Za-z0-9_.[\],-]*):(?=\S)(?!\/\/)/g; +// Mirrors regex_value in Sentry's event_search.py grammar, which is what +// separates a key://pattern// filter from prose like http://example.com. +const REGEX_FILTER_VALUE_SOURCE = String.raw`\/\/(?!\/\/(?:[\t\n )]|$))[^\n]{1,1024}?\/\/(?=[\t\n )]|$)`; +const REGEX_FILTER_VALUE_PATTERN = new RegExp(`^${REGEX_FILTER_VALUE_SOURCE}`); +const SENTRY_SEARCH_TOKEN_PATTERN = new RegExp( + String.raw`(^|\s)!?([A-Za-z_][A-Za-z0-9_.[\],-]*):(?=\S)(?:(?!\/\/)|(?=${REGEX_FILTER_VALUE_SOURCE}))`, + "g", +); const KNOWN_SENTRY_SEARCH_KEYS = new Set([ "browser", "device", @@ -122,8 +128,9 @@ export function looksLikeSentrySearchSyntax(query?: string): boolean { const FULL_TEXT_SEARCH_KEYS = new Set(["message", "log.body"]); /** - * Replace quoted regions with same-length placeholders so colons inside quotes - * are not treated as filter-key separators (e.g. transaction:"handle message:hello"). + * Replace quoted regions and regex filter values with same-length placeholders + * so colons inside them are not treated as filter-key separators + * (e.g. transaction:"handle message:hello" or message://key:\d+//). * Length is preserved so offsets still map back to the original query. */ function maskQuotedRegions(query: string): string { @@ -131,7 +138,17 @@ function maskQuotedRegions(query: string): string { let quote: '"' | "'" | null = null; let escaped = false; - for (const char of query) { + for (let i = 0; i < query.length; i += 1) { + const char = query[i] ?? ""; + if (!quote && !escaped && char === "/" && query[i - 1] === ":") { + const regexValue = query.slice(i).match(REGEX_FILTER_VALUE_PATTERN)?.[0]; + if (regexValue) { + // Keep the // delimiters so the token pattern still sees a regex value. + out += `//${regexValue.slice(2, -2).replace(/[\s:"']/g, "x")}//`; + i += regexValue.length - 1; + continue; + } + } if (escaped) { escaped = false; out += quote ? "x" : char; @@ -169,6 +186,10 @@ type SearchFilterOccurrence = { }; function normalizeFilterValue(rawValue: string): string { + if (REGEX_FILTER_VALUE_PATTERN.test(rawValue)) { + return rawValue.slice(2, -2).trim().toLowerCase(); + } + return rawValue .replace(/^['"]|['"]$/g, "") .replace(/^\*+|\*+$/g, "") @@ -178,7 +199,8 @@ function normalizeFilterValue(rawValue: string): string { /** * Read one filter value starting at `valueStart` in the original query. - * Quoted values keep interior whitespace; unquoted values stop at whitespace. + * Quoted and regex values keep interior whitespace; other values stop at + * whitespace. */ function readRawFilterValue( query: string, @@ -188,6 +210,13 @@ function readRawFilterValue( return undefined; } + const regexValue = query + .slice(valueStart) + .match(REGEX_FILTER_VALUE_PATTERN)?.[0]; + if (regexValue) { + return regexValue; + } + const first = query[valueStart]; if (first === '"' || first === "'") { let escaped = false;