@prodcycle/prodcycle 0.6.4 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/api-client.js +32 -4
  2. package/package.json +1 -1
@@ -47,10 +47,25 @@ const MAX_RETRY_AFTER_SECONDS = envInt('PC_MAX_RETRY_AFTER_SECONDS', 300);
47
47
  /**
48
48
  * Per-request fetch timeout. Without this a stalled connection would tie
49
49
  * up the CLI indefinitely, bypassing both the retry cap and the async-poll
50
- * deadline. Default is 2 minutes — long enough for the largest non-async
51
- * sync `/validate` call, short enough that a hung TCP socket gets aborted.
50
+ * deadline.
51
+ *
52
+ * Default is 5 minutes — chosen so the chunked-session `/chunks` upload
53
+ * path has enough headroom under server-side load. The bottleneck on
54
+ * busy servers is the per-chunk transaction (policy eval + per-finding
55
+ * unique-index check on `(scan_id, fingerprint)`), which can take tens
56
+ * of seconds on big chunks. Sync `/validate` scans normally finish in
57
+ * seconds, so a longer default doesn't hurt them — it only matters
58
+ * when a single request stalls. CI runs that want tighter feedback can
59
+ * shrink via `PC_REQUEST_TIMEOUT_MS`.
60
+ *
61
+ * Pre-fix this was 120 s and a megarepo chunked scan (infisical-
62
+ * infisical, ~11.5 k files, 2026-05-13 GA-validation sweep) burned
63
+ * through the full retry budget (4 × 120 s per stuck chunk) before
64
+ * giving up with `Failed to connect to ProdCycle API: The operation
65
+ * was aborted due to timeout`. The body-read retry path from #30 was
66
+ * firing correctly — it just wasn't enough budget.
52
67
  */
53
- const REQUEST_TIMEOUT_MS = envInt('PC_REQUEST_TIMEOUT_MS', 120_000);
68
+ const REQUEST_TIMEOUT_MS = envInt('PC_REQUEST_TIMEOUT_MS', 300_000);
54
69
  /**
55
70
  * Conservative client-side chunk sizing for the chunked-session flow. The
56
71
  * /chunks endpoint accepts up to 50 MB / 2000 files per request, but most
@@ -384,7 +399,20 @@ class ComplianceApiClient {
384
399
  const retryAfterSeconds = parseRetryAfter(response.headers.get('retry-after'));
385
400
  const errorBody = parsed ?? null;
386
401
  const errorMessage = errorBody?.error?.message ?? `API request failed with status ${response.status}`;
387
- const isRetryable = response.status === 429 || response.status === 503;
402
+ // 429 (rate limit) and 503 (service unavailable) honor Retry-After.
403
+ // 502 (bad gateway) and 504 (gateway timeout) are transient ALB-layer
404
+ // failures — the backend wasn't reached / didn't respond in time, so
405
+ // the request was not processed and a fresh attempt has a clean
406
+ // chance of succeeding. Concrete case: openbao-openbao got an
407
+ // instantaneous 502 during the 2026-05-13 GA-validation sweep and
408
+ // the CLI bailed without retry, even though the very next repo
409
+ // scanned cleanly. 500 is deliberately NOT retried — that's an
410
+ // application-level error and retrying could double-process or
411
+ // just deterministically refail.
412
+ const isRetryable = response.status === 429 ||
413
+ response.status === 502 ||
414
+ response.status === 503 ||
415
+ response.status === 504;
388
416
  if (isRetryable && attempt < MAX_RETRY_ATTEMPTS - 1) {
389
417
  const delayMs = retryAfterSeconds != null ? retryAfterSeconds * 1000 : retryBackoffMs(attempt);
390
418
  const cappedDelayMs = Math.min(delayMs, MAX_RETRY_AFTER_SECONDS * 1000);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prodcycle/prodcycle",
3
- "version": "0.6.4",
3
+ "version": "0.6.6",
4
4
  "description": "Multi-framework policy-as-code compliance scanner for infrastructure and application code.",
5
5
  "homepage": "https://docs.prodcycle.com",
6
6
  "repository": {