diff --git a/crates/worker/src/worker.rs b/crates/worker/src/worker.rs index 18c440419..6b91903ea 100644 --- a/crates/worker/src/worker.rs +++ b/crates/worker/src/worker.rs @@ -5291,6 +5291,10 @@ impl Worker { info!(new_segment_id = %new_segment_id, "Manual compaction succeeded"); Ok(ManualCompactResult::Compacted { new_segment_id }) } + Err(WorkerError::CompactCancelled) => { + info!("Manual compaction was cancelled"); + Err(WorkerError::CompactCancelled) + } Err(e) => { warn!(error = %e, "Manual compaction failed"); self.alert( diff --git a/crates/worker/tests/compact_events_test.rs b/crates/worker/tests/compact_events_test.rs index 68842c5c9..2c71f3e46 100644 --- a/crates/worker/tests/compact_events_test.rs +++ b/crates/worker/tests/compact_events_test.rs @@ -1736,7 +1736,11 @@ async fn manual_compact_cancel_clears_progress_before_returning_idle() { Event::CompactionProgress { compaction: None } => { saw_interrupted = true; } - Event::Error { .. } => saw_cancel_error = true, + Event::Error { .. } + | Event::Alert(protocol::Alert { + level: protocol::AlertLevel::Error, + .. + }) => saw_cancel_error = true, Event::WorkerState { snapshot } if snapshot.catalog_status() == protocol::WorkerStatus::Idle => { diff --git a/web/workspace/src/lib/workspace/console/model.test.ts b/web/workspace/src/lib/workspace/console/model.test.ts index b831db902..adaa5c669 100644 --- a/web/workspace/src/lib/workspace/console/model.test.ts +++ b/web/workspace/src/lib/workspace/console/model.test.ts @@ -376,6 +376,35 @@ Deno.test("durable compaction failure reconciles live alert and error in both vi assertEquals(merged.find((line) => line.kind === "error")?.entryId, "failed-entry"); }); +Deno.test("reapplying one committed run_error entry is idempotent in both views", () => { + const committed = (eventId: string): ConsoleEventInput => ({ + eventId, + event: { + event: "session_entry_committed", + data: { + entry: { + entry_id: "same-run-error", + timestamp: 10, + provenance: "legacy_unknown", + kind: "run_error", + message: "mid-run compaction failed: summary unavailable", + failure: "compaction", + }, + }, + }, + }); + + const projection = projectConsole([ + committed("live-delivery"), + committed("snapshot-replay"), + ]); + const errors = projection.lines.filter((line) => line.kind === "error"); + assertEquals(errors.length, 1); + assertEquals(errors[0].entryId, "same-run-error"); + assertEquals(projectConsoleLines(projection.lines, "normal").length, 1); + assertEquals(projectConsoleLines(projection.lines, "overview").length, 1); +}); + Deno.test("snapshot restores compaction terminal without reviving transition progress", () => { const projection = projectConsole([{ eventId: "snapshot", diff --git a/web/workspace/src/lib/workspace/console/model.ts b/web/workspace/src/lib/workspace/console/model.ts index 1471417aa..cdfca96d2 100644 --- a/web/workspace/src/lib/workspace/console/model.ts +++ b/web/workspace/src/lib/workspace/console/model.ts @@ -1287,15 +1287,19 @@ function hasDurableRunFailure( function appendDurableRunFailure( projection: ConsoleProjection, eventId: string, + entryId: string | undefined, message: string, failure: string | undefined, ): void { + if ( + entryId && projection.lines.some((line) => line.entryId === entryId) + ) return; projection.lines = projection.lines.filter((line) => { if (isDurableRunFailure(line)) return true; return !(line.kind === "error" && runFailureMessagesMatch(line.body, message)); }); - projection.lines.push( - line( + projection.lines.push({ + ...line( eventId, "error", failure === "compaction" ? "Compaction failed" : "Run error", @@ -1304,7 +1308,8 @@ function appendDurableRunFailure( false, true, ), - ); + entryId, + }); } function appendAlertLine( @@ -2198,6 +2203,7 @@ function applySessionEntry( appendDurableRunFailure( projection, eventId, + stringField(value, "entry_id"), stringField(value, "message") ?? "Worker run failed.", stringField(value, "failure"), );