Skip to content

Commit 29bbc56

Browse files
leliaclaude
andauthored
Raise failure on SBOM fetch errors (#288)
* fix(core): raise on SBOM fetch failure instead of writing empty reports (CE-362) get_sbom_data returned {} when the full-scan stream fetch failed, so report generation continued and produced empty GitLab dependency scanning, license, and SARIF output with exit code 0. Raise APIFailure instead so the failure goes through the CLI's existing API-error handling (exit code 3 by default, still exit 0 with --disable-blocking). Bump the socketdev floor to 3.4.2, the bundled release that adds the missing purl types (e.g. "generic") and per-artifact parse resilience that caused this failure mode. Merge after socketdev 3.4.2 is on PyPI. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: lock socketdev 3.4.2 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: bump version to 2.5.11 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci(e2e): retry reachability on empty results, upload diagnostics on failure The e2e-reachability job intermittently fails with 'no components with alerts in .socket.facts.json': the tier-1 reachability backend can return empty results while the CLI reports success (ENG-5093), and the same flake has hit unrelated PRs. - Add a retry-probe hook to the e2e matrix: entries that define it get up to 3 scan attempts, retrying only when the probe says the output looks incomplete. Persistent failures still fail via the validate step. Each retry emits a warning annotation and a step-summary line so flake frequency stays visible. - Add tests/e2e/reach-facts-probe.sh: exits 0 when the facts file has alerted components, non-zero (retry) when empty or missing. - Upload /tmp/e2e-output.log, SARIF/GitLab outputs, and facts files as artifacts when any e2e job fails, so flakes are diagnosable without a re-run. Also bump version to 2.6.2 (2.6.0 and 2.6.1 are being released ahead of this PR). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: require socketdev 3.5.0 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Drop ticket references from e2e comments and note the retry hardening in the changelog Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> * Move e2e retry changelog entry out and drop remaining ticket reference The e2e retry hardening ships with the dependency pinning PR instead, so its changelog entry moves there. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> * docs: changelog phrasing tweak Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 7566334 commit 29bbc56

6 files changed

Lines changed: 42 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Changelog
22

3+
## 2.6.2
4+
5+
### Fixed: SBOM fetch failures no longer produce empty reports
6+
7+
- `Core.get_sbom_data` now raises `APIFailure` when the full-scan stream fetch
8+
fails, so the run exits through the CLI's API-error handling (exit code 3 by
9+
default; `--disable-blocking` still exits 0) instead of writing empty
10+
GitLab dependency-scanning, license, and SARIF reports.
11+
- The underlying stream-parse failure was fixed in `socketdev` 3.4.2 (already
12+
pinned to `3.5.0`): unrecognized purl types such as `generic` now resolve
13+
instead of raising, and individual unparseable artifacts are skipped rather
14+
than failing the whole response.
15+
316
## 2.6.1
417

518
### Changed: scan comparison now polls the diff-scans endpoints

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
66

77
[project]
88
name = "socketsecurity"
9-
version = "2.6.1"
9+
version = "2.6.2"
1010
requires-python = ">= 3.11"
1111
license = {"file" = "LICENSE"}
1212
dependencies = [

socketsecurity/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
__author__ = 'socket.dev'
2-
__version__ = '2.6.1'
2+
__version__ = '2.6.2'
33
USER_AGENT = f'SocketPythonCLI/{__version__}'

socketsecurity/core/__init__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,13 @@ def get_sbom_data(self, full_scan_id: str) -> Dict[str, SocketArtifact]:
173173
"""Returns SBOM artifacts for a full scan keyed by artifact ID."""
174174
response = self.sdk.fullscans.stream(self.config.org_slug, full_scan_id, use_types=True)
175175
if not response.success:
176-
log.debug(f"Failed to get SBOM data for full-scan {full_scan_id}")
177-
log.debug(response.message)
178-
return {}
176+
# Raise instead of returning {} so a failed fetch surfaces as an
177+
# API error (exit code 3 by default) rather than empty reports.
178+
log.error(f"Failed to get SBOM data for full-scan {full_scan_id}")
179+
log.error(response.message)
180+
raise APIFailure(
181+
f"Failed to get SBOM data for full-scan {full_scan_id}: {response.message}"
182+
)
179183
if not hasattr(response, "artifacts") or not response.artifacts:
180184
return {}
181185
return response.artifacts

tests/core/test_sdk_methods.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import pytest
2-
from socketdev.fullscans import FullScanParams
2+
from socketdev.exceptions import APIFailure
3+
from socketdev.fullscans import FullScanParams, FullScanStreamResponse
34

45
from socketsecurity.config import CliConfig
56
from socketsecurity.core import Core
@@ -277,6 +278,23 @@ def test_get_added_and_removed_packages_license_override(core):
277278
include_license_details="true",
278279
)
279280

281+
def test_get_sbom_data_failure_raises(core):
282+
"""A failed SBOM stream fetch raises instead of returning {}.
283+
284+
Returning {} let report generation continue and emit empty results with
285+
exit code 0; raising routes the failure through the CLI's API-error
286+
handling instead.
287+
"""
288+
core.sdk.fullscans.stream.side_effect = None
289+
core.sdk.fullscans.stream.return_value = FullScanStreamResponse.from_dict({
290+
"success": False,
291+
"status": 200,
292+
"message": "Error parsing stream response",
293+
})
294+
295+
with pytest.raises(APIFailure, match="Failed to get SBOM data"):
296+
core.get_sbom_data("head")
297+
280298
def test_empty_alerts_preserved(core):
281299
"""Test that empty alerts arrays stay as empty arrays and don't become None"""
282300
# Get the scan that contains dp2 (which has empty alerts array)

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)