# Finding the Failed Build and Its Vulnerabilities

Use the `pup` Datadog CLI for everything here. **Never use Jenkins tools** (`mcp__jenkins__*` or similar) for this skill, even if they're available in the session — go through Datadog CI Visibility instead. This is a hard requirement, not a preference: Jenkins access has been explicitly off-limits for this workflow, and `pup` carries everything needed. If the user doesn't have `pup` installed, prompt them to install it.

## Step 1 — Determine which build number to investigate

**If the user named a specific build** (e.g. "check build 187", "what failed on #185"), use that number directly. The pipeline id is deterministic — `jenkins-theorchard-docker-parent-images-master-<N>` — so there's nothing to look up; skip straight to Step 2.

**Otherwise, default to the latest *failed* master build** — use this exact command to determine it:

```bash
pup cicd pipelines list --pipeline-name="theorchard/docker-parent-images" --branch=master \
  --query='@ci.pipeline.result:error' --from="30d" --limit=5
```

The first result is the top-level pipeline-status event for the most recent *failed* master run — read its `ci.pipeline.number` and `ci.pipeline.id` off of it. If the query comes back empty, there's no failed master build in the last 30 days; say so rather than falling back to an unfiltered "latest build" (which could report on a build that actually passed).

`pup cicd events search --level=job` (or `--level=stage`/`--level=step`) reliably returns nothing for this pipeline, so don't reach for it — `pipelines list` is the one that actually returns job/stage-level detail.

## Step 2 — Pull just that build's events, scoped server-side

Use the `ci.pipeline.id` from step 1 as a `--query` filter, rather than pulling a wide time window and filtering client-side — this fetches only events belonging to that exact build, instead of relying on `--from` alone (which, without the id filter, can and does return events from more than one build number in a single response, e.g. #189 and #190 mixed in one 30-day pull):

```bash
pup cicd pipelines list --pipeline-name="theorchard/docker-parent-images" \
  --query='@ci.pipeline.id:"jenkins-theorchard-docker-parent-images-master-<N>"' \
  --from="30d" --limit=1000 > build.json
```

Keep `--limit=1000` generous — this pipeline fans out into ~30 parallel image-variant branches, and a low limit will truncate before every branch's job events are captured.

## Step 3 — Parse with the bundled script

Hand the file to the bundled parser rather than re-deriving the table-parsing logic by hand. The script assumes the file is already scoped to one build — because step 2's `--query='@ci.pipeline.id:"..."'` filter guarantees that — so it doesn't ask for or re-derive a build number itself:

```bash
uv run ${CLAUDE_PLUGIN_ROOT}/skills/fix-docker-parent-image-build-failures/scripts/parse_build_findings.py build.json
```

This script:
- Parses the ASCII vulnerability table embedded in each failing job's `error.message` field into structured findings (CVE, package, installed/fixed version, severity, blocking, grace period).
- Extracts the image variant tag (e.g. `python312`, `kafka-connect77`) from the message text, falling back to the `IMAGE_TAG=` value in the nearby "Scan Docker Image" job's script if the message doesn't name it.
- Surfaces `warnings` for: zero error-result job events found at all, an image tag that couldn't be extracted, or an image-tag count that looks implausibly high.

**Read every warning in the output before treating any of the findings as trustworthy.** If `image_counts.total` is 0, or the image count doesn't roughly match the number of Dockerfile directories in `docker-parent-images`, stop and investigate before reporting anything to the user — most likely the `--query` filter in step 2 didn't match what you expected.

**The stdout output is deliberately a lean summary, not a full dump** — `blocking_findings`, `near_expiry_warnings`, and `image_counts` are all you need for triage, and that's what's printed. Per-image detail for the images with nothing urgent (no blocking findings, no near-expiry warnings) isn't worth putting in context, so it's written to a side file instead (path given in `images_detail_file`). Only open that side file if you need to double-check a specific image that isn't already covered by the summary — and don't read the raw `build.json` pup output directly either; everything needed for triage is already surfaced by the script.

The two message formats you'll see, for reference (the script handles both):

```
Docker scan completed successfully with warnings for non-blocking vulnerabilities for docker-parent-images:<tag>.
+------------------+--------------------+---------------+------+----------+----------+--------------+
| Vulnerability ID | Installed Version  | Fixed Version | Path | Severity | Blocking | Grace Period |
+------------------+--------------------+---------------+------+----------+----------+--------------+
| CVE-...          | pkg:...             | ...           | ...  | HIGH     | No       | 47           |
```

```
Docker scan failed due to vulnerabilities found for docker-parent-images:<tag>.
```
(same table format follows, with at least one `Blocking: Yes` row)

## Step 4 — Sort findings into three buckets

The script's `blocking_findings` and `near_expiry_warnings` are already exactly these first two buckets; `image_counts.comfortable` is the third:

1. **Blocking** (`blocking_findings`) — these failed the build. Every one needs a recommendation: fix the Dockerfile, or add an exception. See `fix-vs-exception.md`.
2. **Non-blocking, near-expiry** (`near_expiry_warnings`) — didn't fail the build but will soon (default threshold: grace period under 7 days; ask the user if they want a different number, and re-run the script with `--grace-threshold` if so). Flag them as a heads-up alongside the blocking ones.
3. **Everything else** (`image_counts.comfortable`) — non-blocking with a comfortable grace period. Don't spend time on these; mention the count if useful context, but don't itemize them — there's no need to open `images_detail_file` for this bucket.
