Dashboard › calm-forest › Session ses_286097fb
ses_286097fbeffeqTLNIcCCPZJykmLet's fix CLI-12E <system-reminder> Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits (with the exception of the plan file mentioned below), run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system. This supersedes any other instructions you have received.
No plan file exists yet. You should create your plan at /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/.opencode/plans/1775867691073-hidden-orchid.md using the write tool. You should build your plan incrementally by writing to or editing this file. NOTE that this is the only file you are allowed to edit - other than this you are only allowed to take READ-ONLY actions.
Goal: Gain a comprehensive understanding of the user's request by reading through code and asking them questions. Critical: In this phase you should only use the explore subagent type.
Focus on understanding the user's request and the code associated with their request
Launch up to 3 explore agents IN PARALLEL (single message, multiple tool calls) to efficiently explore the codebase.
...
The user wants to fix "CLI-12E". This looks like a Sentry issue ID (short ID format). Let me first try to understand what this issue is about by looking it up via the Sentry CLI skill, and also explore the codebase to understand the project structure. I'll start by looking up the Sentry issue CLI-12E and exploring the codebase to understand the context.
<skill_content name="sentry-cli"> # Skill: sentry-cli # Sentry CLI Usage Guide Help users interact with Sentry from the command line using the `sentry` CLI. ## Agent Guidance Best practices and operational guidance for AI coding agents using the Sentry CLI. ### Key Principles - **Just run the command** — the CLI handles authentication and org/project detection automatically. Don't pre-authenticate or look up org/project before running commands. If auth is needed, the CLI prompts interactively. - **Prefer CLI commands over raw API calls** — the CLI has dedicated commands for most tasks. Reach for `sentry issue view`, `sentry issue list`, `sentry trace view`, etc. before constructing API calls manually or fetching external documentation. - **Use `sentry schema` to explore the API** — if you need to discover API endpoints, run `sentry schema` to browse interactively or `sentry schema <resource>` to search. This is faster than fetching OpenAPI specs externally. ...
{
"id": "7403105956",
"shareId": null,
"shortId": "CLI-12E",
"title": "ValidationError: The \"issue\" dataset supports: table, area, line, bar. Got: \"big_number\".",
"culprit": "H1(bin)",
"permalink": "https://sentry.sentry.io/issues/7403105956/",
"logger": null,
"level": "error",
"status": "unresolved",
"statusDetails": {},
"substatus": "new",
"isPublic": false,
"platform": "node",
"project": {
"id": "4510776311808000",
"name": "cli",
"slug": "cli",
"platform": "node"
},
"type": "error",
"metadata": {
"value": "The \"issue\" dataset supports: table, area, line, bar. Got: \"big_number\".",
"type": "ValidationError",
"filename": "/dist-bin/bin.js",
"function": "H1",
"in_app_frame_mix": "system-only",
"sdk": {
"name": "sentry.javascript.node-light",
"name_normalized": "other"
},
"initial_priority": 75,
"title": null
},
"numComments": 0,
"assignedTo": null,
"isBookmarked": false,
...
The issue is about a ValidationError: The "issue" dataset supports: table, area, line, bar. Got: "big_number". This is related to dashboard widget creation validation. Let me explore the relevant code.
Now I understand the issue. It's a ValidationError about the "issue" dataset not supporting big_number display type in dashboard widgets. Let me explore the relevant code.
task_id: ses_2860927e5ffeznzca7JTF8jtzJ (for resuming to continue this task if needed)
<task_result>
I now have a complete picture. Here is the full analysis:
---
## Findings: Dashboard Widget Display Type Validation
### 1. Where the dataset-to-display-type validation is defined
**The mapping (source of truth):**
- **File:** `/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/types/dashboard.ts`
- **Lines 328-384** -- the `DATASET_SUPPORTED_DISPLAY_TYPES` constant
The relevant entry for the `issue` dataset is on **line 329**:
```typescript
issue: ["table", "area", "line", "bar"],
```
**The validation function that throws the error:**
- **File:** `/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/commands/dashboard/resolve.ts`
- **Lines 620-655** -- the `validateWidgetEnums()` function
The exact throw is on **line 648-650**:
```typescript
throw new ValidationError(
`The "${dataset}" ...
Interesting. The explore agent says the validation is correct and the error is expected. But the issue is filed as a Sentry error (it's being tracked in Sentry as an unresolved issue), which means users are hitting this error. The question is: what should the fix be?
Looking at the issue more carefully:
The error is ValidationError: The "issue" dataset supports: table, area, line, bar. Got: "big_number".
It's a handled error (mechanism: handled: true)
But it's still being reported to Sentry as an error
The real problem is that user-facing validation errors are being captured and sent to Sentry as if they were bugs, creating telemetry noise. The fix could involve either filtering out these input validation errors from Sentry telemetry, improving the user experience with better error messaging, or confirming that the issue dataset genuinely shouldn't support big_number (which the explore agent already validated). user input errors shouldn't generate Sentry issues. ...
task_id: ses_286078f10ffeuMUi4lpdTQAHoh (for resuming to continue this task if needed)
<task_result>
Here is my complete analysis of how errors flow through the telemetry system, and why `ValidationError` is being captured.
---
## Findings: Error Telemetry Flow
### There are TWO separate `Sentry.captureException` call sites for command errors, and `ValidationError` is NOT filtered at either one.
---
### 1. `src/app.ts` -- Lines 297-349: `exceptionWhileRunningCommand` (THE PRIMARY PROBLEM)
**File:** `/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/app.ts`
This is the Stricli application-level error handler. When a command's `func()` throws, Stricli catches it and calls this function to format the error message. **This is the call site that captures `ValidationError` to Sentry.**
**Lines 297-349** -- The critical flow:
```typescript
exceptionWhileRunningCommand: (exc: unknown, ansiColor: boolean): string => {
// ...
Now I have a clear picture. The issue is that ValidationError (and other user-facing CliError subclasses) are being captured as Sentry error events when they shouldn't be. These are expected user input errors, not CLI bugs.
Let me now design the fix. The key location is src/app.ts:334 in exceptionWhileRunningCommand. I need to:
Sentry.captureException()User errors (should NOT be captured):
ValidationError - user input is malformed
ContextError - user omitted required value
ResolutionError - user provided a value that wasn't found
ConfigError - configuration issue (could be user error or bug)
SeerError - Seer not enabled/no budget (user's org state)
TimeoutError - unclear, could be either
CLI bugs (SHOULD be captured):
ApiError (5xx) - server errorsDeviceFlowError - OAuth flow issues (could be bugs)UpgradeError - upgrade issues ...<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/app.ts</path>
<type>file</type>
<content>
290: ? `\n\x1B[39m\x1B[22m${warning(`Tip: ${hint}`)}`
291: : `\nTip: ${hint}`;
292: return `${base}${formatted}`;
293: }
294:
295: return base;
296: },
297: exceptionWhileRunningCommand: (exc: unknown, ansiColor: boolean): string => {
298: // OutputError: data was already rendered to stdout — just re-throw
299: // so the exit code propagates without Stricli printing an error message.
300: if (exc instanceof OutputError) {
301: throw exc;
302: }
303:
304: // Re-throw AuthError for auto-login flow in bin.ts
305: // Don't capture to Sentry - it's an expected state (user not logged in or token expired), not an error
306: // Note: skipAutoAuth is checked in bin.ts, not here — all auth errors must escape Sentry capture
307: if (
308: exc instanceof AuthError &&
309: ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/lib/errors.ts</path>
<type>file</type>
<content>
1: /**
2: * CLI Error Hierarchy
3: *
4: * Unified error classes for consistent error handling across the CLI.
5: */
6:
7: import {
8: buildBillingUrl,
9: buildOrgSettingsUrl,
10: buildSeerSettingsUrl,
11: } from "./sentry-urls.js";
12:
13: /**
14: * Base class for all CLI errors.
15: *
16: * @param message - Error message for display
17: * @param exitCode - Process exit code (default: 1)
18: */
19: export class CliError extends Error {
20: readonly exitCode: number;
21:
22: constructor(message: string, exitCode = 1) {
23: super(message);
24: this.name = "CliError";
25: this.exitCode = exitCode;
26: }
27:
28: /**
29: * Format error for user display. Override in subclasses to add details.
30: */
31: format(): string {
32: return this.message;
33: }
34: }
35:
36: /**
37: * API ...
Now I have a clear picture. Let me also look at telemetry.ts for the isClientApiError helper and the withTelemetry catch block, and also check if there's already a pattern for categorizing errors.
Now let me read the telemetry code to see the isClientApiError helper and how errors are categorized:
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/lib/telemetry.ts</path>
<type>file</type>
<content>
125: } catch {
126: // Queue flush is non-essential
127: }
128:
129: try {
130: return await Sentry.startSpanManual(
131: { name: "cli.command", op: "cli.command", forceTransaction: true },
132: async (span) => {
133: try {
134: return await callback(span);
135: } catch (e) {
136: // Record 4xx API errors as span attributes instead of exceptions.
137: // These are user errors (wrong ID, no access) not CLI bugs, but
138: // recording on the span lets us detect volume spikes in Discover.
139: if (isClientApiError(e)) {
140: recordApiErrorOnSpan(span, e as ApiError);
141: }
142: throw e;
143: } finally {
144: span.end();
145: }
146: }
147: );
148: } catch (e) {
149: ...
Found 3 matches
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/lib/telemetry.ts:
Line 139: if (isClientApiError(e)) {
Line 158: !(isExpectedAuthState || isClientApiError(e) || e instanceof OutputError)
Line 248: export function isClientApiError(error: unknown): boolean {
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/lib/telemetry.ts</path>
<type>file</type>
<content>
240: * Check if an error is a client-side (4xx) API error.
241: *
242: * 4xx errors are user errors — wrong issue IDs, no access, invalid input —
243: * not CLI bugs. These should be recorded as span attributes for volume-spike
244: * detection in Discover, but should NOT be captured as Sentry exceptions.
245: *
246: * @internal Exported for testing
247: */
248: export function isClientApiError(error: unknown): boolean {
249: return error instanceof ApiError && error.status >= 400 && error.status < 500;
250: }
251:
252: /**
253: * Record a client API error as span attributes for Discover queryability.
254: *
255: * Sets `api_error.status`, `api_error.message`, and optionally `api_error.detail`
256: * on the span. Must be called before `span.end()`. ...
Now I have the full picture. Let me design the solution.
task_id: ses_286059b8affeL33W65nBdan8NK (for resuming to continue this task if needed) <task_result> Now I have a thorough understanding of the codebase. Let me design the fix. ## Fix Design for CLI-12E: Filter User Input Errors from Sentry Capture ### Problem Line `src/app.ts:334` unconditionally calls `Sentry.captureException(exc)` for all command errors that aren't `OutputError`, `AuthError` (not_authenticated/expired), or synonym-matched. This captures expected user-input errors (like `ValidationError: The "issue" dataset supports: table, area, line, bar. Got: "big_number"`) as Sentry error events, creating noise. ### Recommended Approach: Option 2 — `isUserInputError()` helper **Why not option 1 (`isUserError` property on CliError)?** It requires touching every error subclass and creates a risk that future subclasses forget to set the flag. The default (`true` or `false`) creates a footgun either way. **Why not option 3 (`beforeSend`)?** The `beforeSend` hook in ...
Let me now read the existing test files to understand the testing patterns, and then create the final plan. Good analysis. Let me verify the existing test structure and confirm the exact import lines in the files we need to change.
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/app.ts</path>
<type>file</type>
<content>
1: // biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import
2: import * as Sentry from "@sentry/node-core/light";
3: import {
4: type ApplicationText,
5: buildApplication,
6: text_en,
7: UnexpectedPositionalError,
8: UnsatisfiedPositionalError,
9: } from "@stricli/core";
10: import { apiCommand } from "./commands/api.js";
11: import { authRoute } from "./commands/auth/index.js";
12: import { whoamiCommand } from "./commands/auth/whoami.js";
13: import { cliRoute } from "./commands/cli/index.js";
14: import { dashboardRoute } from "./commands/dashboard/index.js";
15: import { listCommand as dashboardListCommand } from "./commands/dashboard/list.js";
16: import { eventRoute } from "./commands/event/index.js";
17: import { listCommand as eventListCommand } from "./commands/event/list.js";
18: import { ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/app.ts</path>
<type>file</type>
<content>
26: import { projectRoute } from "./commands/project/index.js";
27: import { listCommand as projectListCommand } from "./commands/project/list.js";
28: import { releaseRoute } from "./commands/release/index.js";
29: import { listCommand as releaseListCommand } from "./commands/release/list.js";
30: import { repoRoute } from "./commands/repo/index.js";
31: import { listCommand as repoListCommand } from "./commands/repo/list.js";
32: import { schemaCommand } from "./commands/schema.js";
33: import { sourcemapRoute } from "./commands/sourcemap/index.js";
34: import { spanRoute } from "./commands/span/index.js";
35: import { listCommand as spanListCommand } from "./commands/span/list.js";
36: import { teamRoute } from "./commands/team/index.js";
37: import { listCommand as teamListCommand } from "./commands/team/list.js";
38: import { traceRoute ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/lib/telemetry.ts</path>
<type>file</type>
<content>
1: /**
2: * Telemetry for Sentry CLI
3: *
4: * Tracks anonymous usage data to improve the CLI:
5: * - Command execution (which commands run, success/failure)
6: * - Error tracking (unhandled exceptions)
7: * - Performance (command duration)
8: *
9: * No PII is collected. Opt-out via SENTRY_CLI_NO_TELEMETRY=1 environment variable.
10: */
11:
12: import { chmodSync, statSync } from "node:fs";
13: // biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import
14: import * as Sentry from "@sentry/node-core/light";
15: import {
16: CLI_VERSION,
17: getCliEnvironment,
18: getConfiguredSentryUrl,
19: SENTRY_CLI_DSN,
20: } from "./constants.js";
21: import { isReadonlyError, tryRepairAndRetry } from "./db/schema.js";
22: import { detectAgent, detectAgentFromProcessTree } from ...
Now let me check for existing error tests:
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/test/lib/errors.test.ts
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/test/lib/errors.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect, test } from "bun:test";
2: import {
3: ApiError,
4: AuthError,
5: CliError,
6: ConfigError,
7: ContextError,
8: DeviceFlowError,
9: formatError,
10: getExitCode,
11: ResolutionError,
12: SeerError,
13: stringifyUnknown,
14: UpgradeError,
15: ValidationError,
16: withAuthGuard,
17: } from "../../src/lib/errors.js";
18:
19: describe("CliError", () => {
20: test("has default exit code of 1", () => {
21: const err = new CliError("Something went wrong");
22: expect(err.exitCode).toBe(1);
23: expect(err.message).toBe("Something went wrong");
24: });
25:
26: test("accepts custom exit code", () => {
27: const err = new CliError("Custom exit", 42);
28: expect(err.exitCode).toBe(42);
29: });
30:
31: test("format() returns message", () => {
32: ...
I have everything I need. Let me now write the final plan.
Wrote file successfully.
call plan_exit again
Okay so we keep these errors for UX visibility. We keep SeerError to get demand and upsell marketing purposes. So when I ask you to find a plan to fix, my goal is to find the underlying UX issue and fix that (or add a mechanism to help the user). Remember our motto: don't fail or try to educate the user with errors if their intent is clear. Do the intent and gently nudge them in the right direction using warning and other mechanisms.
Great point — the real fix is to do what the user clearly intended instead of throwing an error. If someone asks for a big_number widget with the issue dataset, their intent is clear: they want a big number showing issue data. The CLI should make that work, not lecture them about dataset constraints.
Let me re-explore with this lens.
task_id: ses_27a0bf74cffe3DTe7fOdXFWfQt (for resuming to continue this task if needed) <task_result> Now I have a comprehensive picture. Here is the complete analysis: --- ## 1. Full Validation Flow in `widget/add.ts` **File:** `/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/commands/dashboard/widget/add.ts` ### How the user specifies dataset and display type - **`--display` (alias `-d`)**: Required flag (line 115-119). String type, no default. If omitted, Stricli will report a missing required flag error. Valid values listed in the brief: `big_number, line, area, bar, table, stacked_area, top_n, text, categorical_bar, details, wheel, rage_and_dead_clicks, server_tree, agents_traces_table`. - **`--dataset`**: **Optional** flag (line 121-126). String type, brief says "default: spans". But this default is NOT applied at the flag level — `optional: true` means the raw value is `undefined` when omitted. ...
issue to error-events is not safe because the query syntaxes are incompatible.big_number widget with issue-related data.So what can we actually do? Let me think about this more carefully:
Scenario 1: --display big_number --dataset issue
The user explicitly asked for issue dataset AND big_number. The issue dataset genuinely doesn't support big_number. We can't silently switch datasets because queries are incompatible.
But wait — what does a big_number with issue dataset even mean? The issue dataset's purpose is to show issue lists/charts using issue search. A big_number would show a count of issues matching some filter. The discover dataset with count() aggregate and event.type:error could approximate this, but the query syntax is different.
Actually, let me re-think. The user's error was specifically:
ValidationError: The "issue" dataset supports: table, area, line, bar. Got: "big_number".
This happened 4 times from 1 user. Let me consider what scenarios lead to this:
--display big_number and a --dataset issue explicitlyissue widget and tries to change it to big_numberLooking at the issue data more carefully: the error was captured and it's a ValidationError. The intent: user wants issue data as a big number.
For the issue dataset specifically, Sentry's frontend also doesn't support big_number — so even if we sent it to the API, it would fail. The constraint is real.
The best UX here would be:
big_number with issue dataset, we could suggest using discover dataset with count() insteaddiscover dataset with a warning IF the query is compatibleBut actually, for the add command, the user provides the queries themselves. ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/commands/dashboard/widget/add.ts</path>
<type>file</type>
<content>
195: s: "sort",
196: n: "limit",
197: l: "layout",
198: },
199: },
200: async *func(this: SentryContext, flags: AddFlags, ...args: string[]) {
201: const { cwd } = this;
202:
203: const { dashboardArgs, title } = parseAddPositionalArgs(args);
204:
205: // Validate enums before any network calls (fail fast)
206: validateWidgetEnums(flags.display, flags.dataset);
207:
208: const { dashboardRef, targetArg } =
209: parseDashboardPositionalArgs(dashboardArgs);
210: const parsed = parseOrgProjectArg(targetArg);
211: const orgSlug = await resolveOrgFromTarget(
212: parsed,
213: cwd,
214: "sentry dashboard widget add <org>/ <dashboard> <title> --display <type>"
215: );
216: const dashboardId = await resolveDashboardId(orgSlug, ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/commands/dashboard/widget/edit.ts</path>
<type>file</type>
<content>
100: ...(flags.y !== undefined && { y: flags.y }),
101: ...(flags.width !== undefined && { w: flags.width }),
102: ...(flags.height !== undefined && { h: flags.height }),
103: };
104: }
105:
106: /**
107: * Validate enum and aggregate constraints on the effective (merged) widget state.
108: * Extracted from buildReplacement to stay under Biome's complexity limit.
109: */
110: function validateEnumsAndAggregates(
111: flags: EditFlags,
112: existing: DashboardWidget,
113: mergedQueries: DashboardWidgetQuery[] | undefined
114: ): void {
115: const newDataset = flags.dataset ?? existing.widgetType;
116: const aggregatesToValidate =
117: mergedQueries?.[0]?.aggregates ?? existing.queries?.[0]?.aggregates;
118: if ((flags.query || flags.dataset) && aggregatesToValidate) {
119: ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/commands/dashboard/widget/edit.ts</path>
<type>file</type>
<content>
320: },
321: async *func(this: SentryContext, flags: EditFlags, ...args: string[]) {
322: const { cwd } = this;
323:
324: if (flags.index === undefined && !flags.title) {
325: throw new ValidationError(
326: "Specify --index or --title to identify the widget to edit.\n\n" +
327: "Example:\n" +
328: " sentry dashboard widget edit <dashboard> --title 'My Widget' --display bar",
329: "index"
330: );
331: }
332:
333: validateWidgetEnums(flags.display, flags.dataset);
334:
335: const { dashboardRef, targetArg } = parseDashboardPositionalArgs(args);
336: const parsed = parseOrgProjectArg(targetArg);
337: const orgSlug = await resolveOrgFromTarget(
338: parsed,
339: cwd,
340: "sentry dashboard widget edit <org>/ ...
Now let me look at the buildWidgetFromFlags function to see how dataset flows through:
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/commands/dashboard/resolve.ts</path>
<type>file</type>
<content>
480: if (opts.groupBy) {
481: validateGroupByRequiresLimit(columns, opts.limit);
482: }
483: if (orderby) {
484: validateSortReferencesAggregate(orderby, aggregates);
485: }
486:
487: const raw = {
488: title: opts.title,
489: displayType: opts.display,
490: ...(opts.dataset && { widgetType: opts.dataset }),
491: queries: [
492: {
493: aggregates,
494: columns,
495: conditions: opts.where ?? "",
496: ...(orderby && { orderby }),
497: name: "",
498: },
499: ],
500: ...(opts.limit !== undefined && { limit: opts.limit }),
501: };
502: return prepareWidgetQueries(parseWidgetInput(raw));
503: }
504:
505: /** Context for enriching dashboard API errors with actionable messages */
506: export type DashboardErrorContext = {
507: ...
Now let me check the existing tests for validateWidgetEnums:
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/test/commands/dashboard/widget/add.test.ts</path>
<type>file</type>
<content>
200: const { context } = createMockContext();
201: const func = await addCommand.loader();
202:
203: const err = await func
204: .call(
205: context,
206: { json: false, display: "line", query: ["not_a_function"] },
207: "123",
208: "Bad Widget"
209: )
210: .catch((e: Error) => e);
211: expect(err).toBeInstanceOf(ValidationError);
212: expect(err.message).toContain("Unknown aggregate function");
213: });
214:
215: test("throws ValidationError for big_number with issue dataset", async () => {
216: const { context } = createMockContext();
217: const func = await addCommand.loader();
218:
219: const err = await func
220: .call(
221: context,
222: { json: false, display: "big_number", dataset: "issue" ...
Now let me check the edit test too:
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/test/commands/dashboard/widget/edit.test.ts</path>
<type>file</type>
<content>
180: "count()",
181: ]);
182: expect(edited.queries[0].columns).toEqual(["span.description"]);
183: });
184:
185: test("throws ValidationError when --dataset change produces invalid combo with existing display", async () => {
186: // existing widget is displayType: "big_number" (spans), user changes only --dataset to "issue"
187: // → effective combo is big_number + issue, which is invalid
188: getDashboardSpy.mockResolvedValueOnce({
189: ...sampleDashboard,
190: widgets: [
191: {
192: title: "Count",
193: displayType: "big_number",
194: widgetType: "spans",
195: queries: [
196: {
197: name: "",
198: conditions: "",
199: columns: [],
200: aggregates: ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/types/dashboard.ts</path> <type>file</type> <content> 35: ] as const; 36: 37: export type WidgetType = (typeof WIDGET_TYPES)[number]; 38: 39: /** Default widgetType — the modern spans dataset covers most use cases */ 40: export const DEFAULT_WIDGET_TYPE: WidgetType = "spans"; 41: 42: /** 43: * Valid widget display types (visualization formats). 44: * 45: * Source: sentry/src/sentry/models/dashboard_widget.py DashboardWidgetDisplayTypes.TYPES 46: */ 47: export const DISPLAY_TYPES = [ 48: "line", 49: "area", (Showing lines 35-49 of 1118. Use offset=50 to continue.) </content>
Now I have everything I need. Let me also check the DATASET_SUPPORTED_DISPLAY_TYPES more carefully to understand the auto-correction logic:
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/types/dashboard.ts</path> <type>file</type> <content> 295: 296: /** Zod schema for validating a discover aggregate function name */ 297: export const DiscoverAggregateFunctionSchema = z.enum( 298: DISCOVER_AGGREGATE_FUNCTIONS 299: ); 300: 301: /** 302: * Valid display types per widget dataset. 303: * 304: * Source: sentry/static/app/views/dashboards/datasetConfig/ @ a42668e87cc8a0b7410ac2acecee6074c52f376f 305: * Each entry mirrors `supportedDisplayTypes` from the corresponding config: 306: * 307: * issues.tsx https://github.com/getsentry/sentry/blob/a42668e87cc8a0b7410ac2acecee6074c52f376f/static/app/views/dashboards/datasetConfig/issues.tsx#L90-L95 308: * spans.tsx https://github.com/getsentry/sentry/blob/a42668e87cc8a0b7410ac2acecee6074c52f376f/static/app/views/dashboards/datasetConfig/spans.tsx#L287-L297 309: * errors.tsx ...
Good. Now I understand the full picture. The spans dataset (the default) supports big_number and is the broadest. Let me now write the final plan.
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/.opencode/plans/1775867691073-hidden-orchid.md</path> <type>file</type> <content> 1: # Fix CLI-12E: Filter User Input Errors from Sentry Telemetry 2: 3: ## Problem 4: 5: `ValidationError: The "issue" dataset supports: table, area, line, bar. Got: "big_number".` 6: 7: The validation is **correct** — the `issue` dataset doesn't support `big_number` in Sentry's API. The bug is that this `ValidationError` (a user input error) is reported to Sentry as a real error event, creating noise. The same applies to other user-input error classes. 8: 9: **Root cause:** `src/app.ts:334` calls `Sentry.captureException(exc)` unconditionally for all command errors that aren't `OutputError`, `AuthError`, or synonym-matched. User-input errors like `ValidationError`, `ContextError`, `ResolutionError`, `ConfigError`, `SeerError`, and `TimeoutError` are captured as if they were CLI bugs. ...
Wrote file successfully.
Okay we're on the right track but if you simply swap the dataset, will the query work? I doubt it. We understand the intent but then we need to be accurate too.
You're absolutely right. If someone says --display big_number --dataset issue --query count --where "is:unresolved", blindly swapping to spans would send is:unresolved to the spans API which doesn't understand issue search syntax. That's worse than the original error — it'd be a confusing API failure.
Let me understand exactly what differs between datasets in terms of query compatibility.
task_id: ses_27a04ce86ffezLP2D0FTBwtway (for resuming to continue this task if needed)
<task_result>
**Confirmed:** The `IS_FILTER_VALUES` schema is **never called** in any command code. The `--where` flag value is passed through **completely unvalidated** — it becomes `conditions` as a raw string. The comment on line 388 says "Only valid when widgetType is 'issue'" but this is **not enforced anywhere in the CLI**. Validation happens only server-side.
### 4. `prepareWidgetQueries()` — Dataset-Agnostic
**File:** `/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/types/dashboard.ts`, lines 572-603
```typescript
export function prepareWidgetQueries(
inputWidget: DashboardWidget
): DashboardWidget {
let widget = inputWidget;
// Clamp to per-display-type limit maximums
const maxLimit = MAX_LIMITS[widget.displayType];
// ... clamp logic ...
return {
...widget,
queries: widget.queries.map((q) => ({
...q,
...
For the specific error (big_number + issue dataset): The issue dataset genuinely doesn't support big_number on both client and server side.
Dataset swap safety depends on --where content:
--where and the aggregate is common (like count), swapping issue → spans is safe--where with issue-specific syntax (is:unresolved), swapping would create a widget that saves but fails to renderThe issue dataset is special: The CLI can't even query issue widgets for data (mapWidgetTypeToDataset returns null). So issue widgets are "save only" — they render in the Sentry UI, not in the CLI.
So the smart approach would be:
Case 1: --dataset issue --display big_number with NO issue-specific --where
→ Auto-switch to spans, warn the user. This is safe.
Case 2: --dataset issue --display big_number with issue-specific --where (contains is: filters)
→ Can't safely auto-switch. Need to tell the user the incompatibility in a helpful way.
But wait — detecting "issue-specific where" is complex. The is: prefix could be part of many things.
Actually, let me re-read the user's guidance: "don't fail or try to educate the user with errors if their intent is clear. Do the intent and gently nudge them in the right direction."
The user wants to help their users succeed. The question is: can we make the auto-correction work?
Let me think about this practically:
--display big_number --dataset issuespans, and they have --where "is:unresolved", the widget saves but shows an error in the Sentry UIBut actually, is that worse than the current behavior of just throwing an error? ...
User has answered your questions: "For the case where the user has issue-specific --where filters (like is:unresolved), should we: 1. Keep the dataset, switch display to closest alternative (e.g. big_number → table), warn 2. Keep the display, switch dataset anyway, warn about potentially broken filters (let API validate) 3. Something else?"="Context-dependent". You can now continue with the user's answers in mind.
Now I need to design this properly. Let me think about:
How to detect issue-specific --where: Look for patterns like is:, assigned:, bookmarks:, subscribed:, linked:, first-release:, has:, etc.
What's the "closest" display type: For big_number → probably table (both show aggregate values). For other unsupported types, we need a mapping.
How to implement this in the code: The auto-correction logic needs to be in validateWidgetEnums or a new helper, and the callers need to propagate both possible corrections (corrected dataset OR corrected display).
Let me think about the display type fallback mapping:
For the issue dataset (supports: table, area, line, bar):
big_number → table (both show single values/aggregates)stacked_area → area (similar visualization)top_n → table (both show ranked lists)categorical_bar → bar (similar visualization)For preprod-app-size (supports only: line):
lineFor tracemetrics (supports: area, bar, big_number, categorical_bar, line):
table → could go either way... maybe line?stacked_area → areatop_n → line?This is getting complex. Let me simplify.
Actually, for the display type fallback, we really only need to handle the common cases. The most common incompatibility is big_number + issue. Let me define a simple fallback:
const DISPLAY_FALLBACKS: Record<string, string> = {
big_number: "table",
stacked_area: "area",
categorical_bar: "bar",
top_n: "table",
};
If the fallback is in the dataset's supported list, use it. Otherwise, use the first supported display type.
For detecting issue-specific where clauses, I can look at the ISSUE_IS_FILTER_VALUES defined in dashboard.ts, plus other issue-search ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/types/dashboard.ts</path> <type>file</type> <content> 386: /** 387: * Valid `is:` filter values for issue search conditions (--where flag). 388: * Only valid when widgetType is "issue". Other datasets don't support `is:`. 389: * 390: * Status values from GroupStatus: 391: * https://github.com/getsentry/sentry/blob/master/src/sentry/models/group.py#L196-L204 392: * 393: * Substatus values from SUBSTATUS_UPDATE_CHOICES: 394: * https://github.com/getsentry/sentry/blob/master/src/sentry/types/group.py#L33-L41 395: * 396: * Assignment/link filters from is_filter_translation: 397: * https://github.com/getsentry/sentry/blob/master/src/sentry/issues/issue_search.py#L45-L51 398: */ 399: export const IS_FILTER_VALUES = [ 400: // Status (GroupStatus) 401: "resolved", 402: "unresolved", 403: "ignored", 404: "archived", 405: "muted", 406: "reprocessing", 407: // ...
Now let me check how --where flows through and see the logger import pattern in resolve.ts:
Found 1 matches
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/commands/dashboard/resolve.ts:
Line 21: import { logger } from "../../lib/logger.js";
No files found
No files found
Found 1 matches /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/commands/dashboard/resolve.ts: Line 432: log.warn(
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/commands/dashboard/resolve.ts</path>
<type>file</type>
<content>
1: /**
2: * Shared dashboard resolution utilities
3: *
4: * Provides org resolution from parsed target arguments and dashboard
5: * ID resolution from numeric IDs or title strings.
6: */
7:
8: import { MAX_PAGINATION_PAGES } from "../../lib/api/infrastructure.js";
9: import {
10: API_MAX_PER_PAGE,
11: listDashboardsPaginated,
12: } from "../../lib/api-client.js";
13: import type { parseOrgProjectArg } from "../../lib/arg-parsing.js";
14: import {
15: ApiError,
16: ContextError,
17: ResolutionError,
18: ValidationError,
19: } from "../../lib/errors.js";
20: import { fuzzyMatch } from "../../lib/fuzzy.js";
21: import { logger } from "../../lib/logger.js";
22: import { resolveEffectiveOrg } from "../../lib/region.js";
23: import { resolveOrg } from "../../lib/resolve-target.js";
24: import { ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/commands/dashboard/resolve.ts</path>
<type>file</type>
<content>
31: parseSortExpression,
32: parseWidgetInput,
33: prepareWidgetQueries,
34: validateAggregateNames,
35: WIDGET_TYPES,
36: type WidgetType,
37: } from "../../types/dashboard.js";
38:
39: /** Shared widget query flags used by `add` and `edit` commands */
40: export type WidgetQueryFlags = {
41: readonly display?: string;
42: readonly dataset?: string;
43: readonly query?: string[];
44: readonly where?: string;
45: readonly "group-by"?: string[];
46: readonly sort?: string;
47: readonly limit?: number;
48: };
49:
50: /**
(Showing lines 31-50 of 655. Use offset=51 to continue.)
</content>
Good — logger is already imported and used as log (via the withTag pattern). Let me check:
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/commands/dashboard/resolve.ts</path>
<type>file</type>
<content>
425: continue;
426: }
427: const inner = agg.slice(parenIdx + 1, -1);
428: if (!inner) {
429: continue;
430: }
431: if (!KNOWN_SPAN_AGGREGATE_FIELDS.has(inner)) {
432: log.warn(
433: `Aggregate field "${inner}" in "${agg}" is not a known aggregatable span field. ` +
434: "Span attributes (custom tags) cannot be used with numeric aggregates — " +
435: "use them in --where or --group-by instead. " +
436: `Known numeric fields: ${[...KNOWN_SPAN_AGGREGATE_FIELDS].join(", ")}`
437: );
438: }
439: }
440: }
441:
442: /**
443: * Build a widget from user-provided flag values.
444: *
(Showing lines 425-444 of 655. Use offset=445 to continue.)
</content>
Found 1 matches
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/commands/dashboard/resolve.ts:
Line 363: const log = logger.withTag("dashboard");
Now I have all the details. Let me write the final plan.
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/.opencode/plans/1775867691073-hidden-orchid.md</path> <type>file</type> <content> 1: # Fix CLI-12E: Auto-correct dataset when display type is incompatible 2: 3: ## Problem 4: 5: When a user runs `sentry dashboard widget add ... --display big_number --dataset issue`, the CLI throws: 6: ``` 7: ValidationError: The "issue" dataset supports: table, area, line, bar. Got: "big_number". 8: ``` 9: 10: The user's intent is clear — they want a `big_number` widget. The CLI should honor that intent, auto-correct the dataset, and gently warn instead of blocking with an error. 11: 12: ## Approach 13: 14: Replace the hard `ValidationError` in `validateWidgetEnums()` with **auto-correction + `log.warn()`** — matching established codebase patterns: 15: - Swapped args → auto-swap + warn (`init.ts`) 16: - Platform dots → auto-dash + warn (`project/create.ts`) 17: - Slug underscores → auto-dash + warn ...
Wrote file successfully.
Actually, I think the issues dataset is the outlier in terms of query syntax here, right? Maybe we can create a translation layer?
Good instinct — let me check exactly what issue-search syntax looks like vs the standard event-search syntax used by other datasets, and whether a translation is feasible.
task_id: ses_279fd23c6ffekrHKuxFlqWVo1h (for resuming to continue this task if needed) <task_result> Now I have a complete picture. Let me compile the thorough analysis. --- ## Thorough Analysis: Issue Search vs Event Search Syntax ### 1. Issue Search Syntax — Qualifiers Specific to the `issue` Dataset **File: `/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/types/dashboard.ts`** (lines 386-426) The `IS_FILTER_VALUES` constant defines all valid `is:` filter values, with source references to the Sentry backend: ```typescript // Lines 399-421 export const IS_FILTER_VALUES = [ // Status (GroupStatus) — src/sentry/models/group.py#L196-L204 "resolved", "unresolved", "ignored", "archived", "muted", "reprocessing", // Substatus (GroupSubStatus) — src/sentry/types/group.py#L33-L41 "escalating", "ongoing", "regressed", "new", "archived_until_escalating", "archived_until_condition_met", "archived_forever", ...
So a translation layer isn't feasible. Let me report this back to the user and refine the plan.
The context-dependent approach from before is actually the right one:
--where → switch dataset to spans, keep display--where → switch display to closest alternative, keep datasetLet me present this clearly to the user. </thinking>
You're right that the issue dataset is the outlier, but unfortunately a translation layer isn't viable here. They're fundamentally different data models:
| | Issue dataset | Event datasets (spans/discover/etc.) |
|---|---|---|
| Storage | PostgreSQL (Group model) | ClickHouse (events/spans) |
| API | /organizations/{org}/issues/ | /organizations/{org}/events/ |
| count | Number of issue groups | Number of individual events |
| is:unresolved | Checks issue status in Group model | No equivalent — events don't know their parent issue's status |
| assigned:me | Group property in PostgreSQL | No equivalent |
Even count_unique(issue.id) on discover wouldn't replicate "count of unresolved issues" because there's no way to filter by resolution status in event search — that state lives entirely in PostgreSQL.
So the context-dependent approach from the plan is the right call:
spans (safe, count() works, user gets their big_number)big_number → table), keep dataset and query intactShould I proceed with that plan, or do you want to discuss anything else before building?
Okay then for this specific scenario, I'm baffled that we have on way of showing a big_number widget for the number of unresolved issues etc. Is this really the case? Look carefully, maybe the widget is supported?
Great question — let me look carefully at what Sentry's API actually accepts when you save a dashboard widget, regardless of what the frontend config says.
task_id: ses_279f852e7ffeuxzeU7i54q2oAU (for resuming to continue this task if needed) <task_result> Now I have all the evidence needed. Let me compile the complete findings. --- ## Investigation Results ### Finding 1: The Sentry Backend Does NOT Validate `displayType` Against `widgetType` **File:** Sentry backend `src/sentry/api/serializers/rest_framework/dashboard.py` (fetched from GitHub) The `DashboardWidgetSerializer.validate()` method performs the following validations on widget creation/update: 1. Text widget validation (line ~320) 2. `widget_type` validation via `_validate_widget_type()` -- only rejects `discover` and `None` 3. `interval` validation 4. Query-level validation (conditions, fields, orderby) 5. `description` length check (max 255) 6. `limit` validation based on `display_type` (max 10 for charts, 20 for tables, 25 for categorical bar) 7. Threshold validation 8. ...
So the fix is much simpler than what we planned:
Remove the dataset-display cross-validation from validateWidgetEnums() — The API accepts any valid combination. The frontend can render issue+big_number widgets.
The Sentry frontend CAN render issue+big_number — It has useIssuesTableQuery that works for any tabular display type (including big_number).
But wait — the CLI can't render issue widgets because mapWidgetTypeToDataset("issue") returns null. However, that's a separate issue. The user is creating a dashboard widget that will be visible in the Sentry web UI. The CLI just needs to save it correctly.
So the fix is:
big_number (and potentially other missing types) to the issue entry in DATASET_SUPPORTED_DISPLAY_TYPESvalidateWidgetEnums()Option 1 is safer — we can expand the supported list to match what the API actually accepts. We know big_number works, and likely all the common display types work too (since the backend doesn't cross-validate at all).
Actually, the cleanest fix is to just expand DATASET_SUPPORTED_DISPLAY_TYPES.issue to include the display types that the API accepts and the frontend can render. Since the backend accepts any valid display type for any valid dataset, the question is: which ones does the frontend actually render correctly?
The frontend's issue widget has:
useTableQuery — for table/big_number (tabular data)useSeriesQuery — for area/line/bar (timeseries)So big_number should work via useTableQuery. What about stacked_area, top_n, categorical_bar? ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/types/dashboard.ts</path> <type>file</type> <content> 301: /** 302: * Valid display types per widget dataset. 303: * 304: * Source: sentry/static/app/views/dashboards/datasetConfig/ @ a42668e87cc8a0b7410ac2acecee6074c52f376f 305: * Each entry mirrors `supportedDisplayTypes` from the corresponding config: 306: * 307: * issues.tsx https://github.com/getsentry/sentry/blob/a42668e87cc8a0b7410ac2acecee6074c52f376f/static/app/views/dashboards/datasetConfig/issues.tsx#L90-L95 308: * spans.tsx https://github.com/getsentry/sentry/blob/a42668e87cc8a0b7410ac2acecee6074c52f376f/static/app/views/dashboards/datasetConfig/spans.tsx#L287-L297 309: * errors.tsx https://github.com/getsentry/sentry/blob/a42668e87cc8a0b7410ac2acecee6074c52f376f/static/app/views/dashboards/datasetConfig/errors.tsx#L115-L123 310: * transactions.tsx ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/.opencode/plans/1775867691073-hidden-orchid.md</path> <type>file</type> <content> 1: # Fix CLI-12E: Auto-correct dataset/display incompatibility 2: 3: ## Problem 4: 5: `sentry dashboard widget add ... --display big_number --dataset issue` throws: 6: ``` 7: ValidationError: The "issue" dataset supports: table, area, line, bar. Got: "big_number". 8: ``` 9: 10: User's intent is clear but we can't blindly swap the dataset — the `issue` dataset uses its own search syntax (`is:unresolved`, `assigned:me`, etc.) that doesn't work on other datasets. A blind swap would create a widget that saves but shows broken data. ...
Wrote file successfully.
I think pre_prod_appsize should also be fine with big_number? anything should be fine with big_number TBH
You're right — big_number is just a rendering of a single aggregate value. If a dataset can produce count() or any aggregate, it can show it as a big number. There's nothing dataset-specific about the visualization.
Let me verify quickly — is there any dataset where big_number genuinely wouldn't work?
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/types/dashboard.ts</path>
<type>file</type>
<content>
328: export const DATASET_SUPPORTED_DISPLAY_TYPES = {
329: issue: ["table", "area", "line", "bar"],
330: spans: [
331: "area",
332: "bar",
333: "big_number",
334: "categorical_bar",
335: "line",
336: "stacked_area",
337: "table",
338: "top_n",
339: "details",
340: "server_tree",
341: ],
342: "error-events": [
343: "area",
344: "bar",
345: "big_number",
346: "categorical_bar",
347: "line",
348: "stacked_area",
349: "table",
350: "top_n",
351: ],
352: "transaction-like": [
353: "area",
354: "bar",
355: "big_number",
356: "categorical_bar",
357: "line",
358: "stacked_area",
359: "table",
360: "top_n",
361: ],
362: logs: [
363: "area",
364: "bar",
365: "big_number",
366: "categorical_bar",
367: ...
Right. Every dataset except issue and preprod-app-size already has big_number. And the backend doesn't validate this cross-product at all. The whole mapping is just the frontend's dropdown picker preference.
Given that — should we go further and reconsider whether this entire cross-validation is pulling its weight? It's blocking valid operations based on a UI picker list. The backend is the real validator.
User has answered your questions: "Since the backend doesn't cross-validate display type vs dataset at all, should we: 1. Just add `big_number` everywhere it's missing (minimal fix for this issue) 2. Remove the cross-validation entirely from `validateWidgetEnums` — trust the backend, like we already do for `--where` syntax Option 2 is cleaner long-term (no more maintaining a frontend picker mirror), but option 1 is more conservative."="Remove cross-validation". You can now continue with the user's answers in mind.
Clean. Let me update the plan.
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/commands/dashboard/resolve.ts</path>
<type>file</type>
<content>
614: /**
615: * Validate --display and --dataset flag values against known enums.
616: *
617: * @param display - Display type flag value
618: * @param dataset - Dataset flag value
619: */
620: export function validateWidgetEnums(display?: string, dataset?: string): void {
621: if (
622: display &&
623: !DISPLAY_TYPES.includes(display as (typeof DISPLAY_TYPES)[number])
624: ) {
625: throw new ValidationError(
626: `Invalid --display value "${display}".\nValid display types: ${DISPLAY_TYPES.join(", ")}`,
627: "display"
628: );
629: }
630: if (
631: dataset &&
632: !WIDGET_TYPES.includes(dataset as (typeof WIDGET_TYPES)[number])
633: ) {
634: throw new ValidationError(
635: `Invalid --dataset value "${dataset}".\nValid datasets: ${WIDGET_TYPES.join(", ")}`,
636: ...
Now let me check what imports DATASET_SUPPORTED_DISPLAY_TYPES to understand the ripple effects of removing the cross-validation:
Found 4 matches
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/types/dashboard.ts:
Line 328: export const DATASET_SUPPORTED_DISPLAY_TYPES = {
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/commands/dashboard/resolve.ts:
Line 27: DATASET_SUPPORTED_DISPLAY_TYPES,
Line 643: DATASET_SUPPORTED_DISPLAY_TYPES
Line 646: const supported = DATASET_SUPPORTED_DISPLAY_TYPES[dataset as WidgetType];
No files found
Only used in resolve.ts for the cross-validation. Let me also check the AGENTS.md reference to this constant:
No files found
Good — the constant is only used in the one place (the cross-validation in resolve.ts). Now let me finalize the plan:
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/.opencode/plans/1775867691073-hidden-orchid.md</path> <type>file</type> <content> 1: # Fix CLI-12E: Allow `big_number` display with `issue` dataset 2: 3: ## Problem 4: 5: `sentry dashboard widget add ... --display big_number --dataset issue` throws: 6: ``` 7: ValidationError: The "issue" dataset supports: table, area, line, bar. Got: "big_number". 8: ``` 9: 10: ## Root cause 11: 12: The CLI's `DATASET_SUPPORTED_DISPLAY_TYPES` mapping in `src/types/dashboard.ts` was copied from the Sentry **frontend UI picker** (`supportedDisplayTypes` in `datasetConfig/issues.tsx`). But that list controls which options are **enabled in the dropdown** — it's a product/design decision, not a backend constraint. 13: 14: **The Sentry backend does NOT cross-validate `displayType` against `widgetType`.** The `DashboardWidgetSerializer` validates each as independent enum fields — any valid display type is ...
Wrote file successfully.
The plan is now tight — remove the cross-validation entirely (4 files, net deletion of code), since the backend is the authority and accepts any valid display+dataset combo. The individual enum checks stay to catch truly invalid values like typos.
User approved switching to build agent. Wait for further instructions.
The plan at .opencode/plans/1775867691073-hidden-orchid.md has been approved, you can now edit files. Execute the plan <system-reminder> Your operational mode has changed from plan to build. You are no longer in read-only mode. You are permitted to make file changes, run shell commands, and utilize your arsenal of tools as needed. </system-reminder>
A plan file exists at /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/.opencode/plans/1775867691073-hidden-orchid.md. You should execute on the plan defined within it
Let me execute the plan.
[
{
"content": "Remove `DATASET_SUPPORTED_DISPLAY_TYPES` from `src/types/dashboard.ts`",
"status": "pending",
"priority": "high"
},
{
"content": "Remove cross-validation block and import from `src/commands/dashboard/resolve.ts`",
"status": "pending",
"priority": "high"
},
{
"content": "Update test in `test/commands/dashboard/widget/add.test.ts`",
"status": "pending",
"priority": "high"
},
{
"content": "Update tests in `test/commands/dashboard/widget/edit.test.ts`",
"status": "pending",
"priority": "high"
},
{
"content": "Run tests, typecheck, and lint",
"status": "pending",
"priority": "high"
}
]
[
{
"content": "Remove `DATASET_SUPPORTED_DISPLAY_TYPES` from `src/types/dashboard.ts`",
"status": "in_progress",
"priority": "high"
},
{
"content": "Remove cross-validation block and import from `src/commands/dashboard/resolve.ts`",
"status": "pending",
"priority": "high"
},
{
"content": "Update test in `test/commands/dashboard/widget/add.test.ts`",
"status": "pending",
"priority": "high"
},
{
"content": "Update tests in `test/commands/dashboard/widget/edit.test.ts`",
"status": "pending",
"priority": "high"
},
{
"content": "Run tests, typecheck, and lint",
"status": "pending",
"priority": "high"
}
]
Edit applied successfully.
[
{
"content": "Remove `DATASET_SUPPORTED_DISPLAY_TYPES` from `src/types/dashboard.ts`",
"status": "completed",
"priority": "high"
},
{
"content": "Remove cross-validation block and import from `src/commands/dashboard/resolve.ts`",
"status": "in_progress",
"priority": "high"
},
{
"content": "Update test in `test/commands/dashboard/widget/add.test.ts`",
"status": "pending",
"priority": "high"
},
{
"content": "Update tests in `test/commands/dashboard/widget/edit.test.ts`",
"status": "pending",
"priority": "high"
},
{
"content": "Run tests, typecheck, and lint",
"status": "pending",
"priority": "high"
}
]
Now remove the cross-validation block and import from resolve.ts:
Edit applied successfully. LSP errors detected in this file, please fix: <diagnostics file="/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/commands/dashboard/resolve.ts"> ERROR [642:7] The DATASET_SUPPORTED_DISPLAY_TYPES variable is undeclared. ERROR [645:25] The DATASET_SUPPORTED_DISPLAY_TYPES variable is undeclared. </diagnostics>
Expected — now remove the cross-validation block itself:
Edit applied successfully. LSP errors detected in this file, please fix: <diagnostics file="/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/commands/dashboard/resolve.ts"> ERROR [35:3] Several of these imports are unused. </diagnostics>
The unused import is WidgetType — let me check:
Found 1 matches /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/src/commands/dashboard/resolve.ts: Line 35: type WidgetType,
Yes, WidgetType was only used in the removed cross-validation. Remove it:
Edit applied successfully.
[
{
"content": "Remove `DATASET_SUPPORTED_DISPLAY_TYPES` from `src/types/dashboard.ts`",
"status": "completed",
"priority": "high"
},
{
"content": "Remove cross-validation block and import from `src/commands/dashboard/resolve.ts`",
"status": "completed",
"priority": "high"
},
{
"content": "Update test in `test/commands/dashboard/widget/add.test.ts`",
"status": "in_progress",
"priority": "high"
},
{
"content": "Update tests in `test/commands/dashboard/widget/edit.test.ts`",
"status": "pending",
"priority": "high"
},
{
"content": "Run tests, typecheck, and lint",
"status": "pending",
"priority": "high"
}
]
Now update the add test:
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/test/commands/dashboard/widget/add.test.ts</path>
<type>file</type>
<content>
1: /**
2: * Dashboard Widget Add Command Tests
3: *
4: * Tests for the widget add command in src/commands/dashboard/widget/add.ts.
5: * Uses spyOn pattern to mock API client and resolve-target.
6: */
7:
8: import {
9: afterEach,
10: beforeEach,
11: describe,
12: expect,
13: mock,
14: spyOn,
15: test,
16: } from "bun:test";
17:
18: import { addCommand } from "../../../../src/commands/dashboard/widget/add.js";
19: // biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking
20: import * as apiClient from "../../../../src/lib/api-client.js";
21: import { ValidationError } from "../../../../src/lib/errors.js";
22: // biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking
23: import * as resolveTarget from "../../../../src/lib/resolve-target.js";
24: import ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/test/commands/dashboard/widget/add.test.ts</path>
<type>file</type>
<content>
215: test("throws ValidationError for big_number with issue dataset", async () => {
216: const { context } = createMockContext();
217: const func = await addCommand.loader();
218:
219: const err = await func
220: .call(
221: context,
222: { json: false, display: "big_number", dataset: "issue" },
223: "123",
224: "Unresolved Count"
225: )
226: .catch((e: Error) => e);
227: expect(err).toBeInstanceOf(ValidationError);
228: expect(err.message).toContain('"issue" dataset supports');
229: });
230:
231: test("allows line/area/bar with issue dataset", async () => {
232: const { context } = createMockContext();
233: const func = await addCommand.loader();
234:
235: for (const display of ["line", "area", "bar"]) {
236: ...
Edit applied successfully.
Now check if ValidationError is still used elsewhere in the test file (to know if we can remove the import):
Found 22 matches
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/test/commands/dashboard/widget/add.test.ts:
Line 21: import { ValidationError } from "../../../../src/lib/errors.js";
Line 172: test("throws ValidationError when title is missing (less than 2 positional args)", async () => {
Line 179: expect(err).toBeInstanceOf(ValidationError);
Line 183: test("throws ValidationError for invalid display type", async () => {
Line 195: expect(err).toBeInstanceOf(ValidationError);
Line 199: test("throws ValidationError for invalid aggregate function", async () => {
Line 211: expect(err).toBeInstanceOf(ValidationError);
Line 303: test("throws ValidationError for table with preprod-app-size dataset", async () => {
Line 314: expect(err).toBeInstanceOf(ValidationError);
Line 332: test("throws ValidationError for table with tracemetrics dataset", async () => {
Line 343: ...
Still used extensively. Wait — lines 303-372 have tests that check dataset-display cross-validation too (preprod-app-size, tracemetrics, details). Let me read those:
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/test/commands/dashboard/widget/add.test.ts</path>
<type>file</type>
<content>
300:
301: // preprod-app-size: line only
302: // https://github.com/getsentry/sentry/blob/a42668e/static/app/views/dashboards/datasetConfig/mobileAppSize.tsx#L255
303: test("throws ValidationError for table with preprod-app-size dataset", async () => {
304: const { context } = createMockContext();
305: const func = await addCommand.loader();
306: const err = await func
307: .call(
308: context,
309: { json: false, display: "table", dataset: "preprod-app-size" },
310: "123",
311: "App Size"
312: )
313: .catch((e: Error) => e);
314: expect(err).toBeInstanceOf(ValidationError);
315: expect(err.message).toContain('"preprod-app-size" dataset supports');
316: });
317:
318: test("allows line with preprod-app-size dataset", async () => ...
These tests all assert the cross-validation that we're removing. They need to be updated too — the operations should succeed now.
Edit applied successfully.
[
{
"content": "Remove `DATASET_SUPPORTED_DISPLAY_TYPES` from `src/types/dashboard.ts`",
"status": "completed",
"priority": "high"
},
{
"content": "Remove cross-validation block and import from `src/commands/dashboard/resolve.ts`",
"status": "completed",
"priority": "high"
},
{
"content": "Update test in `test/commands/dashboard/widget/add.test.ts`",
"status": "completed",
"priority": "high"
},
{
"content": "Update tests in `test/commands/dashboard/widget/edit.test.ts`",
"status": "in_progress",
"priority": "high"
},
{
"content": "Run tests, typecheck, and lint",
"status": "pending",
"priority": "high"
}
]
Now update the edit tests:
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/test/commands/dashboard/widget/edit.test.ts</path>
<type>file</type>
<content>
185: test("throws ValidationError when --dataset change produces invalid combo with existing display", async () => {
186: // existing widget is displayType: "big_number" (spans), user changes only --dataset to "issue"
187: // → effective combo is big_number + issue, which is invalid
188: getDashboardSpy.mockResolvedValueOnce({
189: ...sampleDashboard,
190: widgets: [
191: {
192: title: "Count",
193: displayType: "big_number",
194: widgetType: "spans",
195: queries: [
196: {
197: name: "",
198: conditions: "",
199: columns: [],
200: aggregates: ["count()"],
201: fields: ["count()"],
202: },
203: ],
204: layout: { x: 0, y: 0, ...
Edit applied successfully.
Let me verify ValidationError is still used in the edit test file:
Found 13 matches
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/test/commands/dashboard/widget/edit.test.ts:
Line 21: import { ValidationError } from "../../../../src/lib/errors.js";
Line 147: test("throws ValidationError when neither --index nor --title provided", async () => {
Line 154: expect(err).toBeInstanceOf(ValidationError);
Line 158: test("throws ValidationError for invalid aggregate", async () => {
Line 165: expect(err).toBeInstanceOf(ValidationError);
Line 335: test("throws ValidationError for x out of range", async () => {
Line 341: expect(err).toBeInstanceOf(ValidationError);
Line 345: test("throws ValidationError for negative width", async () => {
Line 351: expect(err).toBeInstanceOf(ValidationError);
Line 355: test("throws ValidationError when --x overflows with fallback width on layoutless widget", async () => {
Line 382: expect(err).toBeInstanceOf(ValidationError);
...
Good — still used elsewhere, import stays.
[
{
"content": "Remove `DATASET_SUPPORTED_DISPLAY_TYPES` from `src/types/dashboard.ts`",
"status": "completed",
"priority": "high"
},
{
"content": "Remove cross-validation block and import from `src/commands/dashboard/resolve.ts`",
"status": "completed",
"priority": "high"
},
{
"content": "Update test in `test/commands/dashboard/widget/add.test.ts`",
"status": "completed",
"priority": "high"
},
{
"content": "Update tests in `test/commands/dashboard/widget/edit.test.ts`",
"status": "completed",
"priority": "high"
},
{
"content": "Run tests, typecheck, and lint",
"status": "in_progress",
"priority": "high"
}
]
$ bun run generate:docs && bun run generate:sdk && tsc --noEmit $ bun run generate:command-docs && bun run generate:skill $ bun run script/generate-command-docs.ts Generated 18 command doc pages + docs/src/content/docs/commands/index.md $ bun run script/generate-skill.ts Generated plugins/sentry-cli/skills/sentry-cli/SKILL.md + 18 reference files + docs/public/.well-known/skills/index.json + src/generated/skill-content.ts $ bun run script/generate-sdk.ts Discovered 55 commands Generated ./src/sdk.generated.ts Generated ./src/sdk.generated.d.cts
Typecheck passed. Now run lint:
$ bunx ultracite check
src/lib/formatters/markdown.ts:281:1 suppressions/unused ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
! Suppression comment has no effect. Remove the suppression or make sure you are suppressing the correct rule.
279 │ * Render a single inline token to an ANSI string.
280 │ */
> 281 │ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: inline token switch is inherently branchy
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
282 │ function renderOneInline(token: Token): string {
283 │ switch (token.type) {
Checked 530 files in 7s. No fixes applied.
Found 1 warning.
Pre-existing warning only, not from our changes. Now run the specific tests:
bun test v1.3.11 (af24e281) 39 pass 0 fail 86 expect() calls Ran 39 tests across 2 files. [432.00ms]
All 39 tests pass. Let me run the full test suite:
bun test v1.3.11 (af24e281)
[info] [api] '{"w":"bcaller","fJ__9D8_q_":"a2 dWZx z","__3ve1_ex":"wc...' was used as the request body. Use --data/-d to pass inline JSON next time.
[info] [api] '{"J2I":"zn QU3x5Rs","dref":"Zvk","_80_io":"l 9L 5"}' was used as the request body. Use --data/-d to pass inline JSON next time.
[info] [api] '{"g__B2tY7AC":"J","dz7Lh":"u w","px":"h36K ","Ak9":"DIg ...' was used as the request body. Use --data/-d to pass inline JSON next time.
[info] [api] '{"___":"j S b","onQ1A_w61_e":"j0 oeP ","yUlFJdvxCUl":"r",...' was used as the request body. Use --data/-d to pass inline JSON next time.
[info] [api] '{"x_e_0z_2i6":"Y c1 P 95d"}' was used as the request body. Use --data/-d to pass inline JSON next time.
[info] [api] '{"__1b":"D 8cPdaNs","Na":"v0 ","Z_JH6__1yf":"D b kunsg l"...' was used as the request body. Use --data/-d to pass inline JSON next time.
[info] [api] '{"_L_":"A ieM5x2G"}' was used as the request body. ...
The failures are all pre-existing (init-eval missing MASTRA_API_URL, consola prompt mocking issues, multiregion tests, delta-upgrade, etc.) — none related to our changes. Let me verify by running just the unit tests:
[info] [trial] The Developer → Business plan trial must be activated in the Sentry UI. [info] [trial] The Team → Business plan trial must be activated in the Sentry UI. [warn] [release.set-commits] Auto-discovery failed: No Sentry repository matching 'foo/bar'.. Falling back to local git history. [debug] [dsn-scan] Skipping large file: large.ts (262198 bytes) [debug] [dsn-scan] Cannot read file: secret.ts [debug] [http] GET /api/0/organizations/test-org/releases/1.0.0/ → 200 (3ms) [debug] [http] POST /api/0/organizations/test-org/releases/ → 201 (0ms) [debug] [http] PUT /api/0/organizations/test-org/releases/1.0.0/ → 200 (0ms) [debug] [http] DELETE /api/0/organizations/test-org/releases/1.0.0/ → 204 (0ms) [debug] [http] GET /api/0/organizations/test-org/releases/1.0.0/deploys/ → 200 (3ms) [debug] [http] POST /api/0/organizations/test-org/releases/1.0.0/deploys/ → 201 (0ms) [debug] [http] PUT /api/0/organizations/test-org/releases/1.0.0/ → 200 (0ms) [debug] [http] GET ...
4854 tests pass, 0 failures. All clean.
[
{
"content": "Remove `DATASET_SUPPORTED_DISPLAY_TYPES` from `src/types/dashboard.ts`",
"status": "completed",
"priority": "high"
},
{
"content": "Remove cross-validation block and import from `src/commands/dashboard/resolve.ts`",
"status": "completed",
"priority": "high"
},
{
"content": "Update test in `test/commands/dashboard/widget/add.test.ts`",
"status": "completed",
"priority": "high"
},
{
"content": "Update tests in `test/commands/dashboard/widget/edit.test.ts`",
"status": "completed",
"priority": "high"
},
{
"content": "Run tests, typecheck, and lint",
"status": "completed",
"priority": "high"
}
]
Done. Here's what changed:
src/types/dashboard.ts — Removed the DATASET_SUPPORTED_DISPLAY_TYPES constant (57 lines including JSDoc). It was sourced from the Sentry frontend's UI picker dropdown, not from any backend validation.
src/commands/dashboard/resolve.ts — Removed the dataset-vs-display cross-validation block from validateWidgetEnums() (lines 639-654) and the unused DATASET_SUPPORTED_DISPLAY_TYPES/WidgetType imports. The function now validates that display and dataset are individually valid enum values, but doesn't cross-check them — matching the backend's behavior.
test/commands/dashboard/widget/add.test.ts — Changed 3 tests from expecting ValidationError to expecting success: big_number + issue, table + preprod-app-size, details + logs all now succeed.
test/commands/dashboard/widget/edit.test.ts — Changed 2 tests from expecting ValidationError to expecting success: switching dataset to issue on a big_number widget, switching display to table on a preprod-app-size widget.
All 4854 unit tests pass, typecheck clean, lint clean (pre-existing warning only).
Now create a draft PR from these changes. Follow the repo conventions for your branch name and commit title. The PR (commit) description should be based on the implementation plan and the changes summary but not be overly long or detailed.
Add the plan as a commit note using git note feature.
Check if you are already on a relevant branch before creating a new branch.
fix/test-fetch-warnings
On branch fix/test-fetch-warnings Your branch is up to date with 'origin/fix/test-fetch-warnings'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: AGENTS.md modified: src/commands/dashboard/resolve.ts modified: src/types/dashboard.ts modified: test/commands/dashboard/widget/add.test.ts modified: test/commands/dashboard/widget/edit.test.ts no changes added to commit (use "git add" and/or "git commit -a")
94c1d71b fix(test): silence "unexpected fetch call to" warnings in unit tests 8245dec7 fix(dashboard): guard sort param by dataset in widget table queries (#715) cff56b69 fix(build): normalize Windows backslash paths for sourcemap resolution (#714) f7f4d71c meta: Bump new development version e69575ad Merge branch 'release/0.26.0'
AGENTS.md | 122 ++++++++++------------------ src/commands/dashboard/resolve.ts | 20 +---- src/types/dashboard.ts | 85 ------------------- test/commands/auth/login.test.ts | 7 ++ test/commands/dashboard/widget/add.test.ts | 86 +++++--------------- test/commands/dashboard/widget/edit.test.ts | 42 ++++++---- test/commands/issue/utils.test.ts | 8 +- test/commands/project/create.test.ts | 12 +++ test/commands/span/view.test.ts | 12 +++ test/isolated/login-reauth.test.ts | 7 ++ test/lib/help-positional.test.ts | 23 +++++- test/lib/index.test.ts | 45 +++++++++- test/lib/resolve-effective-org.test.ts | 10 +++ test/lib/resolve-target-listing.test.ts | 2 + test/lib/resolve-target.test.ts | 13 +++ test/lib/version-check.test.ts | 13 ++- 16 files changed, 235 insertions(+), 272 ...
Current branch has unrelated changes mixed in. I need to create a new branch from origin/main:
Saved working directory and index state WIP on fix/test-fetch-warnings: 94c1d71b fix(test): silence "unexpected fetch call to" warnings in unit tests
Switched to a new branch 'fix/dashboard-widget-display-validation' branch 'fix/dashboard-widget-display-validation' set up to track 'origin/main'.
Auto-merging AGENTS.md CONFLICT (content): Merge conflict in AGENTS.md On branch fix/dashboard-widget-display-validation Your branch is up to date with 'origin/main'. Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: src/commands/dashboard/resolve.ts modified: src/types/dashboard.ts modified: test/commands/dashboard/widget/add.test.ts modified: test/commands/dashboard/widget/edit.test.ts Unmerged paths: (use "git restore --staged <file>..." to unstage) (use "git add <file>..." to mark resolution) both modified: AGENTS.md The stash entry is kept in case you need it again.
The source code changes applied cleanly. AGENTS.md has a conflict — let me resolve it by taking the current main version (AGENTS.md is auto-managed by lore):
Updated 1 path from the index
AGENTS.md | 122 ++++++++++------------------ src/commands/dashboard/resolve.ts | 20 +---- src/types/dashboard.ts | 85 ------------------- test/commands/dashboard/widget/add.test.ts | 86 +++++--------------- test/commands/dashboard/widget/edit.test.ts | 42 ++++++---- 5 files changed, 90 insertions(+), 265 deletions(-)
Wait — AGENTS.md shouldn't have changes in this PR since I took --theirs (main). Let me check:
diff --git a/AGENTS.md b/AGENTS.md
index 20d5fc03..c2f21adc 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -984,108 +984,74 @@ mock.module("./some-module", () => ({
The AGENTS.md still shows changes. This is because the stash was created from the fix/test-fetch-warnings branch which might have had a different version of AGENTS.md than origin/main. Let me check what the actual diff is. The --theirs resolved the conflict with the main version, but the stash was based on the old branch. Let me just reset AGENTS.md to what's on main.
AGENTS.md | 121 ++++++++++------------------ src/commands/dashboard/resolve.ts | 20 +---- src/types/dashboard.ts | 85 ------------------- test/commands/dashboard/widget/add.test.ts | 86 +++++--------------- test/commands/dashboard/widget/edit.test.ts | 42 +++++----- 5 files changed, 88 insertions(+), 266 deletions(-)
AGENTS.md still shows as changed. Let me check what's happening:
diff --git a/AGENTS.md b/AGENTS.md
index 20d5fc03..8cd81743 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -984,108 +984,71 @@ mock.module("./some-module", () => ({
### Architecture
-<!-- lore:019d275c-7ce8-77d4-a7d9-1d10185e0879 -->
-* **commandPrefix on SentryContext enables command identity in buildCommand wrapper**: commandPrefix and help-as-positional recovery: \`SentryContext.commandPrefix\` (optional \`readonly string\[]\`) is populated in \`forCommand()\` — Stricli calls this with the full prefix (e.g., \`\["sentry", "issue", "list"]\`) for help recovery and telemetry. Help recovery: (1) Leaf commands: \`maybeRecoverWithHelp\` catches \`CliError\` if any positional was \`"help"\`, shows help via \`introspectCommand()\`. (2) Route groups: post-run check in \`bin.ts\` detects \`ExitCode.UnknownCommand\` + last arg \`"help"\`, rewrites argv. Both dynamic-import \`help.js\` to avoid circular deps.
+<!-- lore:019d2d10-671c-77d8-9dbc-c32d1604dcf7 -->
+* **AsyncIterable streaming for ...
This is the lore-managed section of AGENTS.md — the version on main differs from the version in the worktree's HEAD. This is a real difference (main has newer lore content). Let me include it — the AGENTS.md instructions say to always include it:
[fix/dashboard-widget-display-validation fc2c6da9] fix(dashboard): remove overly restrictive dataset-display cross-validation 5 files changed, 88 insertions(+), 266 deletions(-)
Now add the plan as a commit note:
remote: remote: Create a pull request for 'fix/dashboard-widget-display-validation' on GitHub by visiting: remote: https://github.com/getsentry/cli/pull/new/fix/dashboard-widget-display-validation remote: To https://github.com/getsentry/cli.git * [new branch] fix/dashboard-widget-display-validation -> fix/dashboard-widget-display-validation branch 'fix/dashboard-widget-display-validation' set up to track 'origin/fix/dashboard-widget-display-validation'.
https://github.com/getsentry/cli/pull/720
Draft PR created: https://github.com/getsentry/cli/pull/720
I feel like the "allows x" tests in test/commands/dashboard/widget/add.test.ts are not really adding value?
The user is questioning the value of the "allows X" tests in add.test.ts. Let me look at what those tests actually do — they just call the command and assert updateDashboardSpy was called once. They don't verify any specific behavior beyond "it didn't throw."
The user is right — after removing the cross-validation, these tests are just verifying that the command runs without error for arbitrary display+dataset combos. That's not testing any meaningful logic anymore. The individual enum validation (unknown display type, unknown dataset) is still tested elsewhere. These "allows X with Y dataset" tests were only valuable when there was a cross-validation to test against.
Let me look at which specific tests they're referring to and whether they're actually worth keeping. You're right. Let me look at which ones we're talking about:
Found 4 matches
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/test/commands/dashboard/widget/add.test.ts:
Line 215: test("allows big_number with issue dataset", async () => {
Line 232: test("allows line/area/bar with issue dataset", async () => {
Line 305: test("allows table with preprod-app-size dataset", async () => {
Line 317: test("allows details display with non-spans dataset", async () => {
These are all just "call the command, assert it didn't throw." Now that there's no cross-validation, they're testing nothing — any valid enum pair will pass. The individual enum checks are already covered by the "throws ValidationError for invalid display/dataset" tests. Let me remove them.
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/calm-forest/test/commands/dashboard/widget/add.test.ts</path>
<type>file</type>
<content>
213: });
214:
215: test("allows big_number with issue dataset", async () => {
216: const { context } = createMockContext();
217: const func = await addCommand.loader();
218:
219: await func.call(
220: context,
221: { json: false, display: "big_number", dataset: "issue" },
222: "123",
223: "Unresolved Count"
224: );
225: expect(updateDashboardSpy).toHaveBeenCalledTimes(1);
226: const body = updateDashboardSpy.mock.calls[0]?.[2];
227: const addedWidget = body.widgets.at(-1);
228: expect(addedWidget.displayType).toBe("big_number");
229: expect(addedWidget.widgetType).toBe("issue");
230: });
231:
232: test("allows line/area/bar with issue dataset", async () => {
233: const { context } = createMockContext();
234: const func = await ...
The "allows line/area/bar with issue dataset" (line 232) was already pre-existing and equally vacuous. The "allows big_number with issue dataset" (line 215), "allows table with preprod-app-size" (line 305), and "allows details with non-spans" (line 317) are our new ones. All four just assert the command doesn't throw — no meaningful behavior being verified. Remove all four plus the comment block.
Edit applied successfully.
Edit applied successfully.
Now run the tests to make sure nothing broke:
bun test v1.3.11 (af24e281) 35 pass 0 fail 78 expect() calls Ran 35 tests across 2 files. [409.00ms]
All pass. Amend the commit:
Burak Yigit Kaya byk@sentry.io
On branch fix/dashboard-widget-display-validation Your branch is up to date with 'origin/fix/dashboard-widget-display-validation'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: AGENTS.md modified: test/commands/dashboard/widget/add.test.ts no changes added to commit (use "git add" and/or "git commit -a")
Wait — AGENTS.md shows as modified again. Let me check:
diff --git a/AGENTS.md b/AGENTS.md
index 8cd81743..c2f21adc 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -993,6 +993,9 @@ mock.module("./some-module", () => ({
This is lore adding new entries during the test run (generates docs which triggers lore). It's unrelated to our changes — skip it.
Auto packing the repository in background for optimum performance. See "git help gc" for manual housekeeping. [fix/dashboard-widget-display-validation 390cb3c3] fix(dashboard): remove overly restrictive dataset-display cross-validation Date: Mon Apr 13 10:37:46 2026 +0000 5 files changed, 68 insertions(+), 307 deletions(-)
To https://github.com/getsentry/cli.git + fc2c6da9...390cb3c3 fix/dashboard-widget-display-validation -> fix/dashboard-widget-display-validation (forced update)
Done. Removed the four no-op "allows X with Y" tests and force-pushed. PR is at https://github.com/getsentry/cli/pull/720.
merge
PR #720 merged.