Skip to content

feat(dashboard): report a bug from a run - #421

Open
danii1 wants to merge 1 commit into
feat/dashboard-delegate-taskfrom
feat/dashboard-report-bug
Open

danii1 wants to merge 1 commit into
feat/dashboard-delegate-taskfrom
feat/dashboard-report-bug

Conversation

@danii1

@danii1 danii1 commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds Report a problem to the run detail view. It files the same redacted bug report as devintern report --run <id>, and the consent preview is binding: what you send is what you previewed.

Merge order: stacked on #422 (feat/dashboard-delegate-task); #423 has already merged into main. Merge #422 first; GitHub then retargets this PR to main. The branch is rebased on #422's head and has no merge conflicts. Both new POST routes sit behind the central request guard. The UI sends Content-Type: application/json, and the browser adds the same-origin Origin header. A test checks that cross-origin preview and submit requests get 403.

Endpoints

  • POST /api/runs/:id/report/preview with { title, description, shareIdentifiers? }. It collects and redacts the bundle for that run (same collection as --run <id>, reading the dashboard's run database and workspace capture logs) and keeps the prepared payload in memory under a random previewId (15 min TTL, at most 20 held). It returns { previewId, expiresAt, runId, signedIn, contactRequired, destination, title, description, bytes, attachments: { environment, run, logs[{source,lines,bytes,truncated}], evidenceCount, identifiersShared }, omitted[], payload }. Errors: 400 (bad id or body), 404 (unknown run), 422 (payload could not be prepared).

  • POST /api/reports with { previewId, email? }. It submits the stored payload through the existing reporter/postReport, spooling it to .devintern-code/reports/ first like the CLI. Anonymous users must give a valid email (same validation as the CLI). The email is the only field added, as contact. Signed-in users need no email. Responses:

    • 200 { status: "sent", reportId, shortId, url?, receipt?, savedPath }
    • 502 { status: "saved", error, authExpired, savedPath, retryCommand: "devintern report --from <path>" }
    • 400 (missing or invalid email), 404 (unknown or expired preview), 409 (send already in flight)

    A preview is deleted after a successful send. It is kept after a failed send so the dialog's "Try again" can use it.

Refactor

  • report/submission.ts (new): submitWithSpool, isValidContactEmail, currentInstallKind, moved out of report/cli.ts. The CLI now only prints the outcome. CLI output is unchanged.
  • report/preview.ts (new): summarizeReportPayload, now used by both the CLI preview and the dashboard.
  • dashboard-server.ts: the two report routes are added to feat(dashboard): enqueue ad-hoc tasks from the dashboard #422's routeWrite helper, which handles the POST routes after the guard. There is no second helper.
  • collectDiagnostics / BugReporterOptions accept runDbPath and workerLogDirs. When omitted, the defaults are the same as before.

UI

  • The button is outlined for failed, escalated and abandoned runs. For other finished runs it is a ghost button, for wrong-behavior reports. It is hidden while a run is in progress.
  • The dialog has three steps:
    1. Compose: a guidance line linking the docs, a title, a "What did you expect / what happened" field, a warning not to paste secrets or customer data, and a share-identifiers checkbox (off by default).
    2. Preview: what is attached, what is never sent, the full payload behind a "show full payload" toggle, a contact email field when anonymous, and an explicit Send report button.
    3. Result: the report id, or the saved path with the --from retry command.
  • Reuses feat(dashboard): enqueue ad-hoc tasks from the dashboard #422's shadcn components from components/ui: Dialog with DialogHeader, DialogTitle, DialogDescription and DialogFooter, plus Field, FieldLabel, FieldDescription, FieldError, Input, Textarea and Checkbox. This PR adds no ui component of its own.
  • Draft protection matches NewTaskDialog. If you have typed something, an outside click is ignored, and Escape or Close asks before discarding the draft. Closing is blocked while a preview or send is in progress. The dialog session mounts only while the dialog is open. The dialog state is a reducer in run-detail/report-flow.ts.

Docs

docs/code/reporting-bugs.md (new "Report from the dashboard" section) and docs/code/dashboard.md.

Tests

  • packages/code/tests/dashboard-report.test.ts: cross-origin POSTs to both routes get 403, preview contents and redaction, share-identifiers, the submitted body equals the previewed payload plus contact, signed-in token forwarding, unknown and expired preview ids, anonymous submit without email rejected, a failed send saved locally with a --from hint and retry, 401 flagged, server routes.
  • packages/dashboard-ui/src/views/run-detail/ReportProblem.test.tsx: reducer transitions, dirty/busy guards, and the rendered field and checkbox markup of each step.
  • On the rebased stack, root bun run test passes: code 2956, dashboard-ui 263, e2e 198, and the other packages. Root format, lint and typecheck also pass.
  • Browser check (agent-browser) against a temporary dashboard with one failed run, using an anonymous identity and a local stub report endpoint:
    • compose → preview: a token and the task key were redacted in the preview
    • Escape on a dirty draft asked to discard; an outside click left the dialog open
    • Send stayed disabled until a contact email was entered
    • send → "Sent report" with the receipt; the stub received exactly one report
    • Done closed the dialog

🤖 Generated with Claude Code

@danii1
danii1 force-pushed the feat/dashboard-report-bug branch from 41a4308 to 84a16c2 Compare November 14, 2026 07:57
@danii1
danii1 changed the base branch from main to fix/dashboard-csrf-host-guard November 14, 2026 07:57
Base automatically changed from fix/dashboard-csrf-host-guard to main November 14, 2026 08:39
danii1 added a commit that referenced this pull request Nov 14, 2026
Replace the #/tasks/new page with a dialog opened from the top-bar
"New task" button, built on shadcn/ui components.

- Add dialog, field, input, textarea, label, and checkbox (plus the
  separator that field depends on) with `bunx shadcn add`. Adjusted to the
  repo's lint rules: named React type imports instead of `import * as
  React`, `@/lib/utils` for `cn` (the CLI wrote a bare "cn" import and
  added an unrelated "cn" npm package, which was reverted), a stable key in
  FieldError. checkbox is unused here; #421 will reuse it.
- Add the popover color tokens the dialog surface uses.
- NewTaskDialog: repo preselect, char counter, quiet-hours/--create-pr
  notice, one idempotency key per dialog session (fresh on reopen and on
  "Create another task"), title focused on open, Enter in the title does not
  submit, Cmd/Ctrl+Enter in the description does. Drafts are protected:
  outside clicks are ignored and Esc/Cancel/close ask before discarding.
  Submitting locks the fields and blocks closing. The result (task key,
  status, Already queued, Open run #N, View runs) shows inside the dialog.
- Remove the #/tasks/new route and NEW_TASK_HASH.
- Docs: dashboard.md describes the dialog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a "Report a problem" action to the run detail view that files the
same redacted bug report as `devintern report --run <id>`, with a
binding consent preview:

- POST /api/runs/:id/report/preview collects and redacts the bundle for
  the run, keeps the prepared payload in memory under a random preview
  id (15 min TTL), and returns the attachment summary, omissions, and
  the full payload.
- POST /api/reports submits exactly the stored payload (anonymous
  reports add only the typed contact email), spools it locally first,
  and returns the report id or the saved path with a --from retry hint.

Shared pieces move out of report/cli.ts into report/submission.ts
(spooled submit, contact validation, install kind) and report/preview.ts
(payload summary). Collection accepts an explicit run database and
worker log dirs so the dashboard reads the run from its own database.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@danii1
danii1 force-pushed the feat/dashboard-report-bug branch from 84a16c2 to d0e33c7 Compare November 14, 2026 09:35
@danii1
danii1 changed the base branch from main to feat/dashboard-delegate-task November 14, 2026 09:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant