still_active

How do you know if your Ruby dependencies are still maintained?

bundle outdated tells you version drift. bundler-audit catches known CVEs. Neither tells you whether anyone is still working on the thing. still_active checks maintenance activity, version freshness, security scores, vulnerabilities, libyear drift, and archived repos for every gem in your Gemfile.

Findings ship as terminal / markdown / JSON / SARIF / CycloneDX — SARIF lands in your GitHub Security tab and as inline PR annotations on Gemfile.lock; CycloneDX feeds Trivy / Dependency-Track / Snyk. PR mode (--baseline=FILE) reports only what got worse since main, so reviewers see one line ("vcr newly archived") instead of an absolute snapshot of every dep.

Gem Version GitHub Action Code Quality analysis RSpec Rubocop analysis

Name                    Version          Activity  OpenSSF  Vulns  License
──────────────────────────────────────────────────────────────────────────────
async                   2.36.0 (latest)  ok        7.1/10   0      MIT
backbone-rails          1.2.3 (latest)   archived  3.6/10   0      MIT
bootstrap-slider-rails  9.8.0 (latest)   critical  -        0      MIT
gitlab-markup           2.0.0 (latest)   ok        -        0      MIT
local_gem               0.1.0 (path)     -         -        0      -
nested_form             0.3.2 (git)      archived  3.3/10   0      MIT
remotipart              1.4.4 (git)      critical  3.1/10   0      MIT

7 gems: 4 up to date, 0 outdated · 2 active, 2 stale, 2 archived · 0 vulnerabilities
Ruby 4.0.1 (latest)

Why still_active?

still_active is complementary to -- not a replacement for -- the established Ruby tooling. bundle outdated, bundler-audit, and libyear-bundler are purpose-built and battle-tested at what they do. still_active answers a different question: is anyone still maintaining this gem? -- and folds in the version/CVE/libyear signals so you get one report instead of three.

bundle outdated bundler-audit libyear-bundler still_active
Outdated versions Yes - Yes Yes
Known vulnerabilities (CVEs) - Yes (ruby-advisory-db) - Yes (deps.dev + ruby-advisory-db)
Libyear drift - - Yes Yes
Last commit activity - - - Yes
Archived repo detection - - - Yes
OpenSSF Scorecard - - - Yes
Yanked version detection - - - Yes
Ruby version freshness - - - Yes (EOL + libyear)
GitLab support - - - Yes
CI quality gates - Exit code - Yes (4 flags)
Output formats Text Text Text Terminal, JSON, Markdown, SARIF, CycloneDX

The bolded rows are the gap still_active fills: nobody else answers "is the maintainer still around?" The CVE column is worth a closer look: bundler-audit reads ruby-advisory-db and still_active reads deps.dev, which sometimes diverge. If bundler-audit is installed alongside still_active, we read its ruby-advisory-db checkout too and merge the results (deduplicated, each advisory tagged with its source) — so running both no longer means reconciling two different vuln counts by hand.

Installation

gem install still_active

Quick Start

# audit your Gemfile (auto-detects output format)
still_active

# check specific gems
still_active --gems=rails,nokogiri,sidekiq

# CI pipeline: fail if any gem is critically stale or has vulnerabilities
still_active --fail-if-critical --fail-if-vulnerable

# ignore specific gems in CI checks
still_active --fail-if-warning --ignore=legacy_gem,internal_gem

# markdown table for pull requests or documentation
still_active --markdown

Usage

Authentication

still_active discovers a GitHub token in this order:

  1. --github-oauth-token=TOKEN CLI flag
  2. GITHUB_TOKEN environment variable (CI convention)
  3. GH_TOKEN environment variable (gh CLI convention)
  4. gh auth token (if gh is installed and authenticated)

Without a token, GitHub API calls are unauthenticated and rate-limited to 60 requests/hour — you will hit the limit on anything beyond a handful of gems. With a token the limit is 5000 requests/hour.

GitLab cascade mirrors GitHub: --gitlab-tokenGITLAB_TOKENglab auth status --show-token. Optional for public repos, required for private ones.

CLI options

Usage: still_active [options]

        all flags are optional

        --gemfile=GEMFILE            path to gemfile
        --gems=GEM,GEM2,...          Gem(s)
        --terminal                   Coloured terminal output (default in TTY)
        --markdown                   Markdown table output
        --json                       JSON output (default when piped)
        --sarif[=PATH]               SARIF 2.1.0 output for GitHub Code Scanning
        --cyclonedx[=PATH]           CycloneDX SBOM output (stdout, or a file path)
        --cyclonedx-version=VERSION  CycloneDX spec version: 1.6 (default) or 1.7
        --baseline=PATH              Compare current state to baseline JSON; emit markdown deltas
        --github-oauth-token=TOKEN   GitHub OAuth token to make API calls
        --gitlab-token=TOKEN         GitLab personal access token for API calls
        --simultaneous-requests=QTY  Number of simultaneous requests made
        --safe-range-end=YEARS       maximum years since last activity considered safe (no warning)
        --warning-range-end=YEARS    maximum years since last activity that triggers a warning (beyond this is critical)
        --fail-if-critical           Exit 1 if any gem has critical activity warning
        --fail-if-warning            Exit 1 if any gem has warning or critical activity warning
        --fail-if-vulnerable[=SEVERITY]
                                     Exit 1 if any gem has vulnerabilities (optionally at or above SEVERITY)
        --fail-if-outdated=LIBYEARS  Exit 1 if any gem exceeds LIBYEARS behind latest
        --ignore=GEM,GEM2,...        Exclude gems from pass/fail checks (still shown in output)
        --critical-warning-emoji=EMOJI
        --futurist-emoji=EMOJI
        --success-emoji=EMOJI
        --unsure-emoji=EMOJI
        --warning-emoji=EMOJI
    -h, --help                       Show this message
    -v, --version                    Show version

Output formats

Terminal (default on TTY) -- coloured table with summary line. Shown above.

JSON (default when piped) -- structured data for automation:

still_active --json --gemfile=spec/still_active/edge_case_gemfile/Gemfile
{
  "gems": {
    "async": {
      "source_type": "rubygems",
      "version_used": "2.36.0",
      "latest_version": "2.36.0",
      "repository_url": "https://github.com/socketry/async",
      "last_commit_date": "2026-01-22 04:09:48 UTC",
      "archived": false,
      "scorecard_score": 7.1,
      "vulnerability_count": 0,
      "license": "MIT",
      "libyear": 0.0
    },
    "nested_form": {
      "source_type": "git",
      "version_used": "0.3.2",
      "repository_url": "https://github.com/ryanb/nested_form",
      "last_commit_date": "2021-12-11 21:47:02 UTC",
      "archived": true,
      "scorecard_score": 3.3,
      "vulnerability_count": 0
    },
    "local_gem": {
      "source_type": "path",
      "version_used": "0.1.0",
      "scorecard_score": null,
      "vulnerability_count": 0
    }
  },
  "ruby": {
    "version": "4.0.1",
    "eol": false,
    "latest_version": "4.0.1",
    "libyear": 0.0
  }
}

Markdown -- table for pull requests, documentation, or wikis:

still_active --markdown
activity up to date? OpenSSF vulns name version used latest version latest pre-release last commit libyear license
7.1/10 async 2.36.0 (2026/01) 2.36.0 (2026/01) 2026/01 0.0y MIT
🚩 3.6/10 backbone-rails 1.2.3 (2016/02) 1.2.3 (2016/02) 2016/02 0.0y MIT
local_gem 0.1.0 (path) - -
🚩 3.3/10 nested_form 0.3.2 (git) 2021/12 - MIT

Ruby 4.0.1 (latest) ✅

CycloneDX -- a standards-track SBOM so your dependency graph and still_active's signals flow into Trivy, Dependency-Track, or Snyk:

still_active --cyclonedx                 # CycloneDX 1.6 to stdout
still_active --cyclonedx=sbom.json       # write to a file
still_active --cyclonedx --cyclonedx-version=1.7

Emits 1.6 by default — the version mainstream consumers ingest today (cyclonedx-core-java/Dependency-Track and cyclonedx-go/Trivy both cap at 1.6 as of 2026); --cyclonedx-version=1.7 opts into the latest. Gem name/version/purl/licenses and vulnerabilities map to native CycloneDX fields; maintenance signals with no native home (archived, OpenSSF score, libyear, last commit, yanked) ride in still_active:-namespaced properties. The serialNumber is content-derived, so two SBOMs of the same lockfile differ only by their generation timestamp.

SARIF output (GitHub Code Scanning)

Emit findings as SARIF 2.1.0 — they show up in the GitHub Security tab and as inline annotations on Gemfile.lock in pull requests.

See it live: this repo audits itself on every push. Browse the live findings in the Code Scanning Security tab — currently 2× SA005 (low OpenSSF Scorecard).

still_active --sarif                       # writes still_active.sarif.json
still_active --sarif=path/to/out.sarif.json
still_active --sarif=-                     # stdout

Easy mode — use the still_active-action wrapper:

permissions:
  contents: read
  security-events: write   # required for SARIF upload

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ruby/setup-ruby@v1
        with: { ruby-version: '3.4' }
      - uses: SeanLF/still_active-action@v0
        with:
          github-token: ${{ github.token }}
          sarif: still_active.sarif.json
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with: { sarif_file: still_active.sarif.json }

Plain bundle exec if you'd rather pin still_active in your Gemfile:

      - run: bundle exec still_active --sarif
        env:
          GITHUB_TOKEN: ${{ github.token }}
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with: { sarif_file: still_active.sarif.json }

Rule reference (SA001–SA007) and how to suppress: see docs/rules.md.

Baseline diff (PR review)

--baseline=FILE compares the current run against a previously captured JSON snapshot and emits a markdown delta report. Designed for the PR question reviewers actually ask: what got worse?

# Locally — capture from main, compare to your branch
git checkout main && still_active --json > /tmp/main.json
git checkout my-branch && still_active --baseline=/tmp/main.json

In CI, capture a baseline on main and compare on PR branches. Exits 1 if any regression is detected (new vulns, newly-archived deps, scorecard drops crossing 7.0, libyear growth on unchanged versions, Ruby newly EOL, etc.).

The diff supersedes --sarif, --terminal, --markdown, and --json when set.

When a run is detected as Dependabot- or Renovate-authored (via GITHUB_ACTOR, a dependabot//renovate/ branch, or the commit subject), the report leads with a one-line narrative — "Dependabot bump: rack 2.0.0 → 2.0.6" — and --json gains a top-level pr_context. Detection is best-effort and conservative: it never produces a false positive on an ordinary commit, and a miss costs only the narrative line.

Alongside dependency-review-action

GitHub's first-party dependency-review-action runs server-side on PRs and surfaces vulnerabilities, licenses, and OpenSSF Scorecard scores from GitHub's dependency-graph diff. It does not surface maintenance signals — last-commit activity, archived repos, libyear, Ruby EOL, or yanked versions — and is GitHub.com / GHES only. still_active is the complement, not a replacement:

dependency-review-action still_active
Platform GitHub.com / GHES only Any CI
Languages Multi (GitHub dep graph) Ruby
Vulnerabilities GHSA deps.dev + ruby-advisory-db (merged)
Licenses Yes (allow/deny gating) Surfaced (no gating)
OpenSSF Scorecard Yes (display) Yes (display + threshold)
Last-commit activity - Yes
Archived repo detection - Yes
Libyear drift - Yes
Ruby EOL detection - Yes
Yanked version detection - Yes
Diff vs base Native (GitHub API) --baseline=FILE
Output Inline PR annotations Terminal / Markdown / JSON / SARIF / CycloneDX

Run both: let dependency-review-action gate CVEs and licenses, and still_active add the maintenance lens on the same PR.

on: pull_request

jobs:
  dependency-review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/dependency-review-action@v4
        with:
          fail-on-severity: high
          show-openssf-scorecard: true

  maintenance-review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ruby/setup-ruby@v1
        with: { ruby-version: ".ruby-version", bundler-cache: true }
      - uses: SeanLF/still_active-action@v0
        with:
          fail-if-critical: true

CI quality gating

Use exit-code flags to fail CI pipelines based on dependency status:

# fail on critically stale or archived gems
still_active --fail-if-critical --json

# fail on any stale, critical, or archived gem
still_active --fail-if-warning --json

# fail if any gem has known vulnerabilities
still_active --fail-if-vulnerable --json

# fail only on high/critical severity vulnerabilities
still_active --fail-if-vulnerable=high --json

# fail if any gem is more than 3 libyears behind
still_active --fail-if-outdated=3 --json

# combine flags and exclude known exceptions
still_active --fail-if-warning --fail-if-vulnerable --ignore=legacy_gem --json

Activity thresholds

Activity is determined by the most recent signal across last commit date, latest release date, and latest pre-release date:

  • ok: last activity within 1 year (configurable with --safe-range-end)
  • stale: last activity between 1 and 3 years ago (configurable with --warning-range-end)
  • critical: last activity over 3 years ago

Data sources

  • Versions, release dates, and licenses from RubyGems.org or GitHub Packages
  • Last commit date and archived status from the GitHub or GitLab API
  • OpenSSF Scorecard, vulnerability counts, and CVSS severity from Google's deps.dev API
  • Additional advisories from ruby-advisory-db, merged in when bundler-audit is installed alongside (run bundle audit update to keep its checkout current)
  • Ruby version freshness from endoflife.date

Configuration defaults

Option Default Description
output_format auto-detect Coloured terminal on TTY, JSON when piped
safe_range_end 1 year Last activity within this range is "ok"
warning_range_end 3 years Last activity within this range is "stale"; beyond is "critical"
simultaneous_requests 10 Concurrent API requests

Development

After checking out the repo, run bin/setup to install dependencies and wire git hooks. Then run rake to run the full lint + test suite (rake spec for just tests, rake rubocop for just lint). You can also run bin/console for an interactive prompt that will allow you to experiment.

A pre-push hook runs rake automatically before each git push, so cross-file rubocop rules don't escape to CI. Skip with git push --no-verify if you really need to.

To install this gem onto your local machine, run bundle exec rake install. New versions are published automatically to rubygems.org when a GitHub Release is created (via trusted publishing).

Contributing

Bug reports and pull requests are welcome.

License

The gem is available as open source under the terms of the MIT License.