Skip to content

Fix intermittent connection resets on scan comparison by polling the diff-scans endpoints - #283

Closed
lelia wants to merge 1 commit into
mainfrom
lelia/chipotle-scan-polling-endpoint
Closed

Fix intermittent connection resets on scan comparison by polling the diff-scans endpoints#283
lelia wants to merge 1 commit into
mainfrom
lelia/chipotle-scan-polling-endpoint

Conversation

@lelia

@lelia lelia commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Diff-mode scans on self-hosted CI runners intermittently fail on the final comparison step with Connection error after 261.68 seconds: ('Connection aborted.', ConnectionResetError(104, 'Connection reset by peer')) followed by a blank API Error:, even though the full scan itself succeeded and the results are on the dashboard.

Root Cause

The scan comparison used fullscans.stream_diff, which sends one request and then holds the connection open — completely idle — while the API computes the diff. When the comparison takes longer than a network middlebox's TCP idle timeout (Azure NAT gateways default to 4 minutes; the reported resets fired at ~262s on runners egressing through Azure), the middlebox reaps the "zombie" connection and sends a RST. Our API logs show request aborted (client-side termination) with no response ever written. Intermittency tracks diff computation time: fast diffs finish before the idle timer, slow ones (no baseline / large dependency trees) don't.

Fix

Per dougbot's suggestion, the comparison now uses the diff-scans endpoints with polling instead of a held-open connection:

  1. POST /orgs/{org}/diff-scans/from-ids creates a diff-scan resource for the two full scans (returns metadata immediately; on_duplicate=redirect makes reruns of the same pair benign).
  2. The CLI polls GET /orgs/{org}/diff-scans/{id}?cached=true — the API answers 202 while the diff is computing and 200 with the artifacts once ready. Every request is short and bounded, so nothing is ever idle long enough to be reaped.

Details:

  • Poll interval backs off 5s → 30s (1.5×), since each poll consumes 1 quota unit; 30-minute backstop timeout.
  • Transient poll failures (connection reset/timeout/502-class, via APIFailure.is_transient_error()) retry within the loop — the diff keeps computing server-side regardless.
  • Fallback: any failure of the new flow logs a warning and falls back to the legacy stream_diff path, so tokens missing the newly-required scopes (diff-scans:create, diff-scans:list, full-scans:list) keep working exactly as today. No flags, no behavior change otherwise — full-scan creation, head-scan management, and reachability finalize are untouched.
  • omit_license_details mirrors the existing lean-diff behavior from CE-224.

Dependencies / rollout:

Testing: new tests/core/test_diff_scan_polling.py covers poll-until-ready, backoff schedule, transient-error retry, non-transient propagation, timeout backstop, duplicate-redirect shortcut, and the streaming fallback. Full suite: 413 passed, 2 skipped (run against the local SDK build).

Public Changelog

Diff-mode scan comparison no longer holds an idle HTTP connection open while the API computes the diff. The CLI now polls the diff-scans endpoints with short bounded requests, fixing intermittent "Connection reset by peer" failures on the final comparison step behind network gear with TCP idle timeouts (e.g. Azure NAT gateways). The change is transparent; if the API token lacks the diff-scans scopes the CLI falls back to the previous behavior.

The scan comparison (fullscans.stream_diff) held a single HTTP
connection open, fully idle, while the API computed the diff. Network
middleboxes with TCP idle timeouts - notably Azure NAT gateways, which
default to 4 minutes - kill that connection with a RST, surfacing as
intermittent "Connection reset by peer" / blank "API Error:" failures
on the final comparison step of long scans (CE-354).

The comparison now creates a diff-scan resource
(POST /orgs/{org}/diff-scans/from-ids) and polls
GET /orgs/{org}/diff-scans/{id}?cached=true with short bounded
requests: 202 while the diff is computing, 200 with the result once
ready. No request is ever idle long enough to be reaped, and the poll
interval backs off 5s -> 30s to stay quota-friendly (each poll costs
1 quota unit). Transient poll failures retry; a 30-minute backstop
guards against a diff scan that never completes.

Any failure of the new flow (e.g. org tokens missing the
diff-scans:create / diff-scans:list / full-scans:list scopes) logs a
warning and falls back to the legacy streaming comparison, so the
change is transparent to existing users.

Requires socketdev>=3.4.0 for diffscans.get query-param/202 support.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@lelia lelia changed the title Fix intermittent connection resets on scan comparison by polling the diff-scans endpoints (CE-354) Fix intermittent connection resets on scan comparison by polling the diff-scans endpoints Aug 5, 2026
@lelia lelia closed this Aug 5, 2026
@lelia
lelia deleted the lelia/chipotle-scan-polling-endpoint branch August 5, 2026 04:01
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