Back to blog
FILE 0xDD·CHECKING OPEN-SOURCE DEPENDENCY HEALTH FROM YOUR CI PIPELINE

Checking open-source dependency health from your CI pipeline

June 9, 2026 · oss, ci, api, micro-saas, side-project

The badge endpoint (/badge/owner/repo) was always about README virality. But a badge is no good in a shell script or a GitHub Action — you can't jq a PNG.

So I added /api/public/repo/{owner}/{repo}: same data, JSON, no auth, 3600s cache.

$ curl -s https://osspulse.io/api/public/repo/psf/requests | jq .
{
  "repo_id": "psf/requests",
  "health_score": 87,
  "archived": false,
  "last_commit": "2026-05-28",
  "license": "Apache-2.0",
  "open_issues": 144,
  "cve_count": 0,
  "tier": "healthy"
}

The tier field is the main gate: healthy (score ≥ 80), fair (60–79), at_risk (<60), archived (GitHub flagged it archived), unknown (never scanned).


Using it in CI

A simple health gate in a GitHub Actions step:

- name: Check critical dep health
  run: |
    TIER=$(curl -s https://osspulse.io/api/public/repo/psf/requests \
      | python3 -c "import sys,json; print(json.load(sys.stdin)['tier'])")
    if [ "$TIER" = "archived" ] || [ "$TIER" = "at_risk" ]; then
      echo "::warning::psf/requests health tier: $TIER — consider reviewing this dependency"
    fi

The warning doesn't fail the build — that's intentional. A dependency going at_risk doesn't mean your code breaks today; it means someone should look. A GitHub Actions warning surfaces it without blocking.

For a harder gate (block releases when a dep is archived):

TIER=$(curl -s https://osspulse.io/api/public/repo/$OWNER/$REPO | jq -r .tier)
[ "$TIER" = "archived" ] && echo "Archived dep: $OWNER/$REPO — refusing to release" && exit 1

What counts as a scan?

The endpoint returns data from OSS Pulse's weekly scan. If a repo has never been added to anyone's watchlist, it won't have any data — you'll get a 404. Adding it to your own watchlist (free tier, up to 10 repos) kicks off the first scan.

That's the intentional funnel: you hit the API for a repo that isn't in the system yet, it 404s, you sign up to add it. Once it's in, you and anyone else with that dep in their stack can query the public endpoint.


The backend

The handler is thin:

def handle_public_repo_api(event: dict) -> dict:
    repo_id = path.removeprefix("/api/public/repo/").strip("/").lower()
    health = get_repo_health(repo_id)
    if not health:
        return error(404, "Repo not found. Add it to OSS Pulse to start scanning.")

    score = int(health.get("health_score", 0))
    archived = bool(health.get("archived", False))
    tier = "archived" if archived else "healthy" if score >= 80 else "fair" if score >= 60 else "at_risk"

    return response(200, {
        "repo_id": repo_id, "health_score": score, "archived": archived,
        "last_commit": health.get("last_commit", ""), "license": health.get("license", ""),
        "open_issues": int(health.get("open_issues", 0) or 0),
        "cve_count": int(health.get("cve_count", 0) or 0),
        "tier": tier,
    }, headers={"Cache-Control": "max-age=3600", "Access-Control-Allow-Origin": "*"})

get_repo_health() is a DynamoDB point-read on (repo_id, "latest"). Under 5ms at P99 for warm Lambda; the 3600s cache means the GitHub Actions step adds zero meaningful latency to your CI run.


OSS Pulse is at osspulse.io. Free tier: 10 repos, weekly digest, public API access.