Skip to content

fix: record shutdown interruptions and keep scan cancellation requests - #1241

Merged
crypt0rr merged 3 commits into
mainfrom
fix/scan-cancellation-causes
Oct 7, 2026
Merged

crypt0rr merged 3 commits into
mainfrom
fix/scan-cancellation-causes

Conversation

@crypt0rr

@crypt0rr crypt0rr commented Oct 7, 2026

Copy link
Copy Markdown
Owner

Summary

Restarts no longer look like cancellations (#1227)

  • A scan that stops because EdgeWatch stopped (shutdown, upgrade, or loss of the daemon lease) keeps the canceled status. Its error now reads "scan interrupted because EdgeWatch stopped"; a resumable attempt adds "; progress was saved".
  • It emits a new scan-interrupted event. Activity shows it as Scan interrupted, and it is never queued for notification destinations (model.EventDelivered, checked in queueEventsTx). A restart therefore no longer sends one "Scan canceled" alert per destination. The job-silence alert still reports a daemon that keeps stopping.
  • How a scan is classified:
    • Interrupted: the run context ended and nobody asked to cancel the scan.
    • Canceled, as today: someone asked to cancel the scan, either through Cancel scan or by pausing the business unit. This includes a cancel request that arrives during a shutdown.

Cancellation state survives progress updates (#1228)

  • Active scans now report cancel_requested: true once a cancel is requested. phase keeps following the scanner, which reports one last progress update when its process stops, so finalizing stays visible.
  • The job page and the Overview's active-scan row both show Cancellation requested instead of the Cancel scan button while the flag is set. Before this change the Overview row re-enabled the button as soon as the cancel request returned.
  • Once finalization has started, POST /scans/{id}/cancel returns 409 scan_finalizing, because cancelling could no longer change the result.

model.Fingerprint (#1237)

  • It sorts a copy of the CPEs instead of the caller's slice. The defensive copy in acceptedServiceForEvidence is no longer needed and has been removed. The fingerprint values are unchanged.

Compatibility

  • New fields and values:
    • cancel_requested on active scans;
    • a scan-interrupted event type in Activity and the live stream;
    • a 409 scan_finalizing from the cancel route.
  • Behaviour change: a scan interrupted by shutdown no longer queues a scan-canceled notification.
  • Unchanged: the schema, scan status values, and the user-cancel alert (still pinned by TestManagedTerminalOutcomesQueueNotifications).

Validation

  • gofmt, go vet ./...
  • go test -race for internal/app, internal/engine, internal/model and internal/web, plus the outbox, runtime and incident tests in internal/store.
  • npm run lint
  • Vitest for main, Activity, Dashboard and JobDetail.
  • npm --prefix docs run build

New tests:

  • internal/app/scan_interruption_test.go:
    • stopping the run context during a direct scan and during a resumable scan stores the interruption text, returns scan-interrupted, writes it to Activity, and queues nothing in the outbox;
    • cancel_requested survives a progress update;
    • a cancel after finalization begins returns ErrScanFinalizing;
    • classification edge cases (timeout, request during shutdown, no run).
  • Engine: cases for interrupted scans, direct and paused.
  • Model: EventDelivered, and Fingerprint leaves the caller's slice unchanged.
  • Console: the Overview and job page keep the pill while the phase is scanning or finalizing; Activity labels the event; the stream invalidates for it.

Docs: the notifications guide covers interrupted scans, and the scanning guide covers the cancel flow.

Fixes #1227
Fixes #1228
Fixes #1237

- A scan that stops because EdgeWatch stopped is recorded as canceled
  with "scan interrupted because EdgeWatch stopped" and a
  scan-interrupted activity event that is never delivered, instead of a
  scan-canceled alert to every destination. Resumable attempts keep
  their saved progress and say so.
- Active scans report cancel_requested once someone asks to cancel, so
  the job page and Overview keep showing Cancellation requested while
  the scanner reports its last progress and the result is saved.
- A cancel request after finalization has begun is refused with 409
  scan_finalizing, because it could no longer change the outcome.
- model.Fingerprint sorts a copy of the CPEs instead of the caller's
  slice.

Fixes #1227, #1228, #1237
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Deploying edgewatch with  Cloudflare Pages  Cloudflare Pages

Latest commit: 67f0788
Status: ✅  Deploy successful!
Preview URL: https://e5968c1b.edgewatch-cpd.pages.dev
Branch Preview URL: https://fix-scan-cancellation-causes.edgewatch-cpd.pages.dev

View logs

@crypt0rr
crypt0rr merged commit 89b9e65 into main Oct 7, 2026
15 checks passed
@crypt0rr
crypt0rr deleted the fix/scan-cancellation-causes branch October 7, 2026 16:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant