-
[OPIK-7139] [BE] [FE] Surface diagnostics run failures with a reason in the UI (#7272)
发布于
2026-07-01 10:52:24 +00:00 - feat(diagnostics): surface run failures with a reason in the UI
A triggered Diagnostics run was fire-and-forget: if it crashed (e.g. out of
LLM credits) or never started, nothing was recorded and the UI spun until the
client timeout, indistinguishable from "completed, no issues".Capture the failure on the job and surface it:
- Migration 000087 adds last_failure_reason/detail/last_failed_at to
agent_insights_jobs; a successful report clears them. - New POST /v1/private/agent-insights/run-failure ingest endpoint + service.
- The report subscriber records "did_not_start" when the trigger never reaches
Ollie, so the run still ends. - FE ends the run on a failure signal and shows a reason-specific failed state
(out_of_credits / rate_limited / provider_error / generic) with a Try again
action; run timeout cut 12m -> 5m.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
- fix(diagnostics): address review — derive failure from job, migration newline
- Derive the failed state from the job's last_failed_at instead of a client-only
flag, so the banner is correct after reload, in other tabs, and auto-clears on
a later successful (incl. auto) run. The hook now owns only the "running" state. - Tolerate the pre-failBaseline localStorage shape so a run in flight across a
deploy isn't dropped on reload. - Add the trailing blank line required for Liquibase formatted-SQL migrations.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
- fix(diagnostics): backfill legacy run-state failBaseline to startedAt
A legacy run-state blob (no failBaseline) backfilled to 0 made a reload treat
any pre-existing last_failed_at as a fresh failure, flipping an in-flight run to
failed. Backfill to the run's startedAt so only a failure recorded after the run
began counts.Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
- feat(diagnostics): per-reason failure copy, details, stale-results banner
- Distinct copy per failure code (out_of_credits / rate_limited /
provider_error / did_not_start / internal_error) instead of one catch-all. - Surface the backend's failure detail: inline "Show details" toggle in the
banner (issues present), always-shown plain text in the empty state. - New "Results might be outdated" banner when the last scan is older than 3
days, showing how many last-24h traces a re-run would analyze. - Run-status banners (running / failed / stale) are suppressed on the Resolved
view; precedence is running > failed > stale. - Issue list item: description is muted unless selected; stats always muted.
- Failure surfaces use #F14668 (icon) / #F146681A (wash); Try again uses the
rotate-ccw icon; severity badge corner radius 4px.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
- fix(diagnostics): expose latest-day count alongside cross-day total
A multi-day issue showed two contradicting numbers: the "Occurrences" stat is
the cross-day SUM, while the description prose embeds the latest run's count.- BE: findIssues now returns latest_count (most recent report day's count) via
window functions, keeping total_occurrences as the cross-day sum. - FE: the occurrence headline shows "N total · M latest" for multi-day issues
(collapses to just the total for single-day), so the number matches the prose.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
- fix(diagnostics): address review — stale-banner edge cases + run-failure validation test
- Stale check derives scanAt from the displayed
lastScan(which falls back to
the latest issue update), so records without last_scan_at still resolve. - Last-24h cutoff is bucketed to the hour so it tracks the rolling window
instead of freezing at mount, without churning the query key each render. - Add a parameterized 400/422 test for POST /run-failure covering missing
projectId and missing/blank errorCode.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
- fix(diagnostics): round stale-window cutoff up so it never exceeds 24h
Bucketing the last-24h cutoff with floor() widened the window to ~25h, letting
the count include traces slightly older than 24h. Round up instead — the window
stays <=24h while the query key remains hour-stable.Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
- refactor(diagnostics): address review — hooks layering, traces-by-id hook, theme tokens
Addresses andriidudar's suggestions from OPIK-6817:
- Move useDiagnosticsRunState/useDiagnosticsSeen from pages/SignalsPage to
@/hooks so the v2/layout sidebar badge no longer reaches into a page. - AffectedTracesSample now fetches via a new api/traces/useTracesByIds (reusing
useTraceById's fetcher + query key, no double-fetch) and caps the sample to 5
trace requests instead of firing one per example id. - Replace hardcoded hex with theme tokens: toggle-outline-active, light-slate,
and the upload-chip-bg / upload-chip-icon-bg CSS variables.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
- chore(diagnostics): trim verbose comments
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
- refactor(diagnostics): make Resolved issues a route instead of local state
Resolved issues is now its own route (/diagnostics/resolved) rendering
SignalsPage with showResolved, instead of a local useState toggle. The header
uses the shared BackButton (Link) to return to /diagnostics — matching how back
navigation works elsewhere in the app — and the "Resolved issues" / "Back to
open issues" actions navigate between the two routes.Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
- refactor(diagnostics): store run failures in a generic report_failures table
Per review, move run failures off agent_insights_jobs (which only held the
latest failure) into a dedicated, generic report_failures table keyed by
(type, entity_id) so failure history is preserved and other features can reuse
it. 000087 already shipped to dev, so this is forward-only: a new table + a
column-drop migration rather than editing 000087.- 000088 creates report_failures (utf8mb4_unicode_ci to match the schema-wide
collation, avoiding illegal-mix-of-collations on the join). - 000089 drops last_failure_reason/detail/at from agent_insights_jobs.
- New generic ReportFailureDAO.insert; Agent Insights inserts with
type="agent_insights", entity_id=project_id. - AgentInsightsJobDAO.findByProject LEFT JOINs the latest failure newer than
last_scan_at, aliased to last_failure_* — so the job DTO/JSON and the FE are
unchanged. markScanned just advances last_scan_at; markFailed removed.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
- feat(diagnostics): generic report-failures endpoints; drop agent-insights run-failure path
Add a generic POST/GET /v1/private/report-failures over the report_failures
table (ReportFailure DTO, ReportFailureService, ReportFailuresResource, DAO
find/count). Ollie now records run failures here with type="agent_insights".
The BE still computes "currently failed" by joining the latest failure against
the job's last_scan_at, so the job DTO/JSON and FE are unchanged.Remove the now-redundant POST /v1/private/agent-insights/run-failure endpoint,
its AgentInsightsRunFailure DTO, and AgentInsightsIssueService.reportRunFailure.Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
- refactor(diagnostics): address review on report-failures endpoint
- create now returns 201 Created with a Location header (was 204), so clients
learn the new resource id (matches the create-endpoint convention elsewhere). - reject agent_insights failures for a non-existent project (per-type integrity
guard via ProjectService) so orphan rows can't later surface on a job. - parameterize reportFailure__invalidBody__returns422.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
- fix(diagnostics): record run failure when manual trigger fails to enqueue
The manual trigger's enqueue-error path only logged + a metric, so a
publisher-side failure (the run never reaches Ollie, which would otherwise
report its own failure) left the UI spinning until the client timeout. Record
adid_not_startfailure in that path so the failure surfaces. Addresses
review feedback that failures were only marked on the subscriber side.Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
- refactor(diagnostics): rename report_failures entity_id -> project_id, type enum
Address review (BorisTkachenko): the failure log is project-scoped, so rename
the column/field/param/query-param entity_id -> project_id across the migration,
DTO, DAO, service, resource, the agent-insights job join, and tests; maketype
a MySQL ENUM('agent_insights'); reorder the lookup index to
(workspace_id, project_id, type, created_at).Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
- refactor(diagnostics): apply report_failures rename via forward migration 000090
000088 is already deployed to dev, so editing it in place would break the
liquibase checksum there (and CREATE TABLE IF NOT EXISTS wouldn't alter the
existing table anyway). Restore 000088 to its deployed form and add a forward
000090 that renames entity_id -> project_id, makes type ENUM('agent_insights'),
and rebuilds the lookup index as (workspace_id, project_id, type, created_at).
Same forward-only approach as 000089; no manual dev DB surgery needed.Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
- fix(diagnostics): validate report-failure type up front; migration EOF newline
Now that report_failures.type is an ENUM, reject an unsupported type with a 400
in ReportFailureService.create instead of letting it fail at the DB with a 500
(addresses review). Add a test for the 400. Append the trailing newline to
000090 per the Liquibase formatting convention.Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
- refactor(diagnostics): address review — type enum, faster issues query, clearer doc
- ReportFailure.type is now a ReportFailureType enum (AGENT_INSIGHTS) with a
@JsonCreator that returns 400 for unknown values; drops the manual type check. - AgentInsightsIssueDAO.findIssues: replace window functions with a GROUP BY
aggregate subquery + a join to the latest report-day row (per BorisTkachenko),
so the aggregation can use the details index instead of partitioning every row. - Clarify the latestCount @Schema description.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
- fix(diagnostics): ReportFailureType.fromString returns null for missing input
So an absent/null
typefalls through to @NotNull (422) instead of being
reported as an unknown type (400); a present-but-unknown value still yields 400.
Makes the documented validation explicit (addresses review).Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
- refactor(diagnostics): consolidate report_failures migrations into one clean CREATE
Staging/prod have never run this branch, so collapse the dev-driven incremental
migrations (000087 add cols → 000088 create → 000089 drop cols → 000090 alter)
into a single 000087_create_report_failures with the final schema (project_id,
type ENUM('agent_insights'), lookup index). No agent_insights_jobs column churn.Dev, which already ran the incremental set, needs a one-time reset (drop the
empty report_failures table + delete the old 000087-000090 changelog rows)
before its next deploy.Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
- chore(diagnostics): trailing newline on 000087_create_report_failures
Liquibase formatted-SQL files end with a blank line (addresses review).
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
Co-authored-by: Claude Opus 4.8 noreply@anthropic.com
下载附件