Skip to content

fix(errors): surface user search-query 400s as ValidationError, keep CLI-built 400s reported (CLI-FA)#1154

Open
BYK wants to merge 2 commits into
mainfrom
byk/fix/search-query-validation-error
Open

fix(errors): surface user search-query 400s as ValidationError, keep CLI-built 400s reported (CLI-FA)#1154
BYK wants to merge 2 commits into
mainfrom
byk/fix/search-query-validation-error

Conversation

@BYK

@BYK BYK commented Jun 27, 2026

Copy link
Copy Markdown
Member

Why

Follow-up to #1151 based on review feedback: silencing every search-query 400 papers over a real bug class.

HTTP 400 means the server rejected our request as malformed. That has two distinct causes, only distinguishable at the command boundary (where flags.query is in scope):

  1. The user typed an invalid --query → a user input mistake.
  2. The CLI built an invalid query itself (no user --query) → a genuine CLI defect.

PR #1151 special-cased isSearchQueryParseError() in all three classifiers (classifySilenced, isUserError, isUserApiError), which silenced both — so CLI-authored bad queries became invisible.

What changed

  • New toSearchQueryError(error, userQuery, extraSuggestions?) in src/lib/errors.ts: converts a parse-400 into a clean, actionable ValidationError only when the user supplied --query. When there's no user --query, the original ApiError(400) is returned untouched so it stays reported to Sentry as the CLI bug it is.
  • Wired in at the three command boundaries that accept --query: issue list (both the single- and multi-project 400 paths), explore, and trace list.
  • Removed the isSearchQueryParseError special-case from classifySilenced (+ dropped the api_query_error SilenceReason), isUserError, and isUserApiError. A raw ApiError(400) is now always treated as a reported CLI bug.

Result

  • User query typos → clean ValidationError with the server detail + search-syntax help (correct exit code, no "CLI bug" upgrade nudge).
  • CLI-constructed bad queries → still reported, so we can actually find and fix them.

Note

A user-query ValidationError is still surfaced to the user and (like other ValidationErrors) reaches Sentry — that volume is now a useful signal for client-side query-linting UX rather than noise mislabeled as a 400 crash. Happy to silence query ValidationErrors separately if preferred.

Tests

  • toSearchQueryError unit tests (convert vs passthrough, suggestions, non-ApiError).
  • issue list integration tests: user --queryValidationError; no --query → reported ApiError(400).
  • Updated classifySilenced / isUserError / isUserApiError tests to assert search-query 400s are no longer silenced.
  • Full unit suite for touched files green; lint + typecheck clean.

…LI-built 400s reported (CLI-FA)

Silencing every "Error parsing search query" 400 in the error classifiers
papered over a real bug class: a 400 means the server rejected our request as
malformed, and that has two distinct causes only distinguishable at the command
boundary (where flags.query is in scope):

- The user typed an invalid --query — a user input mistake.
- The CLI built an invalid query itself — a genuine CLI defect.

The blanket silence hid both. Now:

- toSearchQueryError() converts a parse-400 into a clean, actionable
  ValidationError ONLY when the user supplied --query (issue/explore/trace
  list). With no user --query, the original ApiError(400) propagates and stays
  reported to Sentry as the bug it is.
- Removed the isSearchQueryParseError special-case from all three classifiers
  (classifySilenced, isUserError, isUserApiError); a raw 400 is now always a
  reported CLI bug.
@github-actions github-actions Bot added the risk: medium PR risk score: medium label Jun 27, 2026
@github-actions

github-actions Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://cli.sentry.dev/_preview/pr-1154/

Built to branch gh-pages at 2026-06-27 15:00 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@github-actions

github-actions Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Codecov Results 📊

❌ Patch coverage is 60.00%. Project has 5142 uncovered lines.
❌ Project coverage is 81.45%. Comparing base (base) to head (head).

Files with missing lines (9)
File Patch % Lines
src/commands/issue/list.ts 50.00% ⚠️ 3 Missing
src/commands/event/list.ts 0.00% ⚠️ 1 Missing
src/commands/explore.ts 50.00% ⚠️ 1 Missing
src/commands/issue/events.ts 0.00% ⚠️ 1 Missing
src/commands/log/list.ts 0.00% ⚠️ 1 Missing
src/commands/span/list.ts 0.00% ⚠️ 1 Missing
src/commands/trace/list.ts 0.00% ⚠️ 1 Missing
src/commands/trace/logs.ts 0.00% ⚠️ 1 Missing
src/lib/errors.ts 100.00% ⚠️ 1 partials
Coverage diff
@@            Coverage Diff             @@
##          main       #PR       +/-##
==========================================
- Coverage    81.47%    81.45%    -0.02%
==========================================
  Files          397       397         —
  Lines        27702     27721       +19
  Branches     17991     17996        +5
==========================================
+ Hits         22570     22579        +9
- Misses        5132      5142       +10
- Partials      1862      1863        +1

Generated by Codecov Action

hasPreviousPage,
resolveCursor,
} from "../../lib/db/pagination.js";
import { toSearchQueryError } from "../../lib/errors.js";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

User-query 400s in log/list, span/list, event/list, and issue/events now falsely reported to Sentry as CLI bugs

Removing isSearchQueryParseError from classifySilenced, isUserError, and isUserApiError requires every command with a user --query flag to call toSearchQueryError before re-throwing API errors. log/list.ts (passes flags.query to listLogs), span/list.ts (passes translateSpanQuery(flags.query) to listSpans), event/list.ts, and issue/events.ts were not updated, so a user typing an invalid --query in those commands will produce an ApiError(400) that is now captured by Sentry as a CLI defect instead of being surfaced as a ValidationError.

Evidence
  • classifySilenced in error-reporting.ts now has no special case for 400: only error.status > 400 && error.status < 500 is silenced, so ApiError(400) with detail 'Error parsing search query' reaches captureException.
  • isUserError in errors.ts similarly excludes 400 (error.status > 400 && error.status < 500), so the upgrade nudge fires.
  • log/list.ts:178 passes query: flags.query to listLogs with no surrounding toSearchQueryError call confirmed by grep returning no matches.
  • span/list.ts:451 passes the translated user query to listSpans with no toSearchQueryError call.
  • The three commands that were updated (issue/list.ts, explore.ts, trace/list.ts) all wrap their API .catch with throw toSearchQueryError(error, flags.query), confirming the required pattern.
Also found at 2 additional locations
  • src/lib/errors.ts:882-887
  • src/lib/telemetry.ts:363-365

Identified by Warden find-bugs · DS5-SH4

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 in a44094d. Wired toSearchQueryError(error, flags.query) into the remaining commands that hit the events/logs/spans search backend (which emits the Error parsing search query marker): log list, span list, event list, issue events, and trace logs. Other query-forwarding commands (replay/alert/dashboard) use different backends that don't emit that marker, so they were never silenced and toSearchQueryError is a no-op there.

Warden flagged that removing the search-query special-case from the classifiers
requires every command forwarding a user --query to call toSearchQueryError, or
its parse-400s get reported as CLI bugs. Wire it into the remaining commands that
hit the events/logs/spans search backend (which emits "Error parsing search
query"): log list, span list, event list, issue events, and trace logs.
@github-actions github-actions Bot added risk: high PR risk score: high and removed risk: medium PR risk score: medium labels Jun 27, 2026

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a44094d. Configure here.

Comment thread src/commands/span/list.ts
allProjects: true,
}).catch((error: unknown): never => {
// An unparseable user --query is a user input mistake, not a CLI bug.
throw toSearchQueryError(error, flags.query);

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.

Span project mode missing handler

Medium Severity

handleTraceMode wraps listSpans with toSearchQueryError, but handleProjectMode does not. With --query in project mode, a server search-parse 400 stays a raw ApiError(400), so it is reported as a CLI defect and skips the ValidationError UX the PR adds elsewhere.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a44094d. Configure here.

Comment thread src/commands/log/list.ts
extraFields: flags.fields,
}).catch((error: unknown): never => {
// An unparseable user --query is a user input mistake, not a CLI bug.
throw toSearchQueryError(error, flags.query);

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.

Log follow mode missing handler

Medium Severity

Non-follow fetches use toSearchQueryError on listLogs failures, but the --follow path calls listLogs with no conversion. A bad --query while streaming logs still surfaces as a reported ApiError(400) instead of a fielded ValidationError.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a44094d. Configure here.

Comment thread src/commands/span/list.ts
Comment on lines 391 to 399
...timeRangeToApiParams(timeRange),
extraFields: extraApiFields,
allProjects: true,
}).catch((error: unknown): never => {
// An unparseable user --query is a user input mistake, not a CLI bug.
throw toSearchQueryError(error, flags.query);
})
);

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.

Bug: The span list command in project mode is missing the toSearchQueryError() error handler, causing invalid user queries to be reported as CLI bugs instead of user errors.
Severity: MEDIUM

Suggested Fix

Wrap the listSpans() API call within handleProjectMode in src/commands/span/list.ts with a .catch() block that uses toSearchQueryError, similar to the implementation in handleTraceMode. This will ensure invalid search queries from users are correctly handled as input errors.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/commands/span/list.ts#L391-L399

Potential issue: The `span list` command has two modes: trace mode and project mode.
While an error handler using `toSearchQueryError` was added to `handleTraceMode` to
correctly handle invalid user search queries, the same handler is missing for the
`listSpans()` call in `handleProjectMode`. As a result, if a user runs `sentry span list
-q "invalid_query"`, the API's 400 error is not converted into a user-friendly
`ValidationError`. Instead, it's treated as an unhandled CLI bug and reported to Sentry,
contradicting the PR's goal of gracefully handling user query typos.

Did we get this right? 👍 / 👎 to inform future reviews.

Comment thread src/commands/log/list.ts
import {
AuthError,
stringifyUnknown,
toSearchQueryError,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

executeTraceSingleFetch skips toSearchQueryError, leaving user-query 400s as raw ApiError in trace mode

In log/list.ts, executeSingleFetch wraps its listLogs call with .catch((error) => { throw toSearchQueryError(error, flags.query); }) so an invalid user --query surfaces as an actionable ValidationError instead of being reported to Sentry as a CLI bug. The parallel executeTraceSingleFetch (used by sentry log list <trace>) calls listTraceLogs at line 487 without this guard. A user-supplied --query rejected by the server with a 400 parse error therefore surfaces as a raw ApiError(400) and is misclassified as a CLI defect, getting reported to Sentry. The sibling command trace/logs.ts (line 213) wraps the identical listTraceLogs call with toSearchQueryError(error, flags.query), confirming the intended pattern. Fix: wrap the listTraceLogs call in executeTraceSingleFetch with .catch((error) => { throw toSearchQueryError(error, flags.query); }).

Evidence
  • executeSingleFetch (log/list.ts:183-188) catches listLogs failures and rethrows via toSearchQueryError(error, flags.query).
  • executeTraceSingleFetch (log/list.ts:487) awaits listTraceLogs(org, traceId, { query, ... }) with no .catch, so a parse-400 propagates unchanged.
  • query is built from the user value: buildProjectQuery(flags.query, projectFilter) (line 486), so an invalid user --query reaches the endpoint.
  • trace/logs.ts:206-214 wraps the same listTraceLogs call with toSearchQueryError(error, flags.query), showing the guard was intended for trace-logs queries.
Also found at 2 additional locations
  • src/commands/log/list.ts:184
  • src/lib/telemetry.ts:364-366

Identified by Warden find-bugs · SWM-6YD

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

Labels

risk: high PR risk score: high

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant