Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions packages/mcp-core/src/tools/catalog/search-events.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3419,6 +3419,68 @@ describe("search_events", () => {
expect(validatedQueries.at(-1)).not.toContain("message:");
});

it.each([
"environment:production is:unresolved",
// Parentheses inside a quoted value are not grouping.
'environment:production message:"err (timeout)"',
])(
"keeps explicit timestamp bounds the agent moved into an overridden time range: %s",
async (filters) => {
// The agent moves the bounds into an absolute timeRange, but the explicit
// period wins over it, so dropping them would search the whole period.
mockGenerateText.mockResolvedValueOnce(
mockAIResponse(
"errors",
filters,
["count()", "count_unique(issue)"],
undefined,
"-count()",
{ start: "2025-01-01T00:00:00Z", end: "2025-01-02T00:00:00Z" },
),
);
const bounds =
"timestamp:>=2025-01-01T00:00:00Z timestamp:<2025-01-02T00:00:00Z";
const requests: URLSearchParams[] = [];
mswServer.use(
http.get(
"https://sentry.io/api/0/organizations/test-org/events/",
({ request }) => {
requests.push(new URL(request.url).searchParams);
return HttpResponse.json({ data: [] });
},
),
);

await runSearchEvents(
{
organizationSlug: "test-org",
regionUrl: null,
projectSlug: null,
dataset: "errors",
query: `${filters} ${bounds}`,
fields: ["count()", "count_unique(issue)"],
period: "7d",
limit: 10,
includeExplanation: false,
},
{
constraints: {
organizationSlug: null,
regionUrl: null,
projectSlug: null,
},
accessToken: "test-token",
userId: "1",
},
{ lockDataset: true },
);

expect(requests).toHaveLength(1);
expect(requests[0]!.get("statsPeriod")).toBe("7d");
expect(requests[0]!.get("query")).toBe(`${filters} ${bounds}`);
},
);

it("rejects partial full-text downgrades when a duplicate structured key remains", async () => {
// Set-based key comparison would miss this: custom remains present after
// custom:foo is dropped into message full-text.
Expand Down
47 changes: 44 additions & 3 deletions packages/mcp-core/src/tools/support/search-events/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,46 @@ function preservesSearchTokens(originalQuery: string, repairedQuery: string) {
);
}

const TIME_BOUND_FILTER_PATTERN = /^timestamp:\S/;
const QUOTED_VALUE_PATTERN = /(["'])(?:\\.|(?!\1).)*\1/g;

/**
* Put back explicit `timestamp:` bounds that the agent dropped from a
* structured query.
*
* The agent often moves them into an absolute `timeRange`, but an explicit
* `period` wins over that range, so dropping them silently widens the search to
* the whole period. Kept bounds only narrow the period, so they are always
* safe to send. Grouped or boolean queries are left as they are, because a
* bound appended outside its group would change what it means.
*/
function restoreDroppedTimeBounds(
originalQuery: string,
repairedQuery: string,
): string {
const originalTokens = tokenizeSearchQuery(originalQuery);
if (
originalTokens.some(
(token) =>
token === "OR" ||
token === "AND" ||
// Parentheses inside a quoted value, e.g. message:"err (timeout)",
// are not grouping.
/[()]/.test(token.replace(QUOTED_VALUE_PATTERN, "")),
)
) {
return repairedQuery;
}

return originalTokens
.filter(
(token) =>
TIME_BOUND_FILTER_PATTERN.test(token) &&
!containsSearchToken(repairedQuery, token),
)
.reduce((query, token) => appendSearchFilter(query, token), repairedQuery);
}

function choosePreservingRepairedQuery(params: {
originalQuery: string;
repairedQuery?: string | null;
Expand Down Expand Up @@ -917,13 +957,14 @@ async function executeSearchEvents(
filter: environmentFilter,
dataset,
})
: looksLikeSentrySearchSyntax(params.query, dataset) &&
isSemanticFilterDowngrade(
: looksLikeSentrySearchSyntax(params.query, dataset)
? isSemanticFilterDowngrade(
params.query ?? "",
parsed.query || "",
dataset,
)
? (params.query ?? "")
? (params.query ?? "")
: restoreDroppedTimeBounds(params.query ?? "", parsed.query || "")
Comment thread
sentry-junior[bot] marked this conversation as resolved.
Resolved
: parsed.query || "";
sortParam =
shouldTrustExplicitSearchParams && explicitSort
Expand Down
Loading