diff --git a/src/commands/scan/handle-create-new-scan.mts b/src/commands/scan/handle-create-new-scan.mts index 6c76abc3c..455ccc565 100644 --- a/src/commands/scan/handle-create-new-scan.mts +++ b/src/commands/scan/handle-create-new-scan.mts @@ -318,7 +318,27 @@ export async function handleCreateNewScan({ const scanId = fullScanCResult.ok ? fullScanCResult.data?.id : undefined if (reach && scanId && tier1ReachabilityScanId) { - await finalizeTier1Scan(tier1ReachabilityScanId, scanId) + // The finalize call is what transitions the tier1 reachability scan row + // out of INIT on depscan's side. Swallowing the failure here left tens + // of thousands of rows stuck in INIT. Capture the result, retry once on + // any failure, and log clearly if it still fails. We deliberately do + // NOT change the user-facing exit code: the full scan upload above + // already succeeded, and finalize is metadata-only. + let finalizeResult = await finalizeTier1Scan( + tier1ReachabilityScanId, + scanId, + ) + if (!finalizeResult.ok) { + debugFn('warn', 'tier1 finalize failed, retrying once') + debugDir('inspect', { finalizeResult }) + finalizeResult = await finalizeTier1Scan(tier1ReachabilityScanId, scanId) + } + if (!finalizeResult.ok) { + logger.error( + `Failed to finalize tier1 reachability scan (scan_id=${scanId}, tier1_reachability_scan_id=${tier1ReachabilityScanId}): ${finalizeResult.message}${finalizeResult.cause ? ` — ${finalizeResult.cause}` : ''}`, + ) + debugDir('inspect', { finalizeResult }) + } } if (report && fullScanCResult.ok) { diff --git a/src/commands/scan/handle-create-new-scan.test.mts b/src/commands/scan/handle-create-new-scan.test.mts index 0998e9530..30ca607ef 100644 --- a/src/commands/scan/handle-create-new-scan.test.mts +++ b/src/commands/scan/handle-create-new-scan.test.mts @@ -7,6 +7,7 @@ import type { HandleCreateNewScanConfig } from './handle-create-new-scan.mts' const { mockFetchCreateOrgFullScan, mockFetchSupportedScanFileNames, + mockFinalizeTier1Scan, mockFindSocketYmlSync, mockGenerateAutoManifest, mockGetPackageFilesForScan, @@ -15,6 +16,7 @@ const { } = vi.hoisted(() => ({ mockFetchCreateOrgFullScan: vi.fn(), mockFetchSupportedScanFileNames: vi.fn(), + mockFinalizeTier1Scan: vi.fn(), mockFindSocketYmlSync: vi.fn(), mockGenerateAutoManifest: vi.fn(), mockGetPackageFilesForScan: vi.fn(), @@ -31,7 +33,7 @@ vi.mock('./fetch-supported-scan-file-names.mts', () => ({ })) vi.mock('./finalize-tier1-scan.mts', () => ({ - finalizeTier1Scan: vi.fn(), + finalizeTier1Scan: mockFinalizeTier1Scan, })) vi.mock('./handle-scan-report.mts', () => ({ @@ -126,6 +128,7 @@ describe('handleCreateNewScan excludePaths', () => { data: { size: 1 }, ok: true, }) + mockFinalizeTier1Scan.mockResolvedValue({ data: undefined, ok: true }) mockFindSocketYmlSync.mockReturnValue({ data: { parsed: { projectIgnorePaths: ['fixtures/**'] } }, ok: true,