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 — the last lands in your GitHub Security tab and as inline PR annotations on Gemfile.lock. 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.
Name Version Activity OpenSSF Vulns
───────────────────────────────────────────────────────────────────
async 2.36.0 (latest) ok 7.1/10 0
backbone-rails 1.2.3 (latest) archived 3.6/10 0
bootstrap-slider-rails 9.8.0 (latest) critical - 0
gitlab-markup 2.0.0 (latest) ok - 0
local_gem 0.1.0 (path) - - 0
nested_form 0.3.2 (git) archived 3.3/10 0
remotipart 1.4.4 (git) critical 3.1/10 0
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) |
| 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 |
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 and still_active use different data sources (ruby-advisory-db vs deps.dev), so coverage isn't identical. If you care about CVEs in CI, keep running bundler-audit alongside still_active.
Installation
gem install still_activeQuick 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 --markdownUsage
Authentication
still_active discovers a GitHub token in this order:
-
--github-oauth-token=TOKENCLI flag -
GITHUB_TOKENenvironment variable (CI convention) -
GH_TOKENenvironment variable (ghCLI convention) -
gh auth token(ifghis 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-token → GITLAB_TOKEN → glab 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
--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,
"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 |
|---|---|---|---|---|---|---|---|---|---|
| ✅ | 7.1/10 | ✅ | async | 2.36.0 (2026/01) | 2.36.0 (2026/01) | ❓ | 2026/01 | 0.0y | |
| 🚩 | ✅ | 3.6/10 | ✅ | backbone-rails | 1.2.3 (2016/02) | 1.2.3 (2016/02) | ❓ | 2016/02 | 0.0y |
| ❓ | ❓ | ❓ | ✅ | local_gem | 0.1.0 (path) | ❓ | ❓ | ❓ | - |
| 🚩 | ❓ | 3.3/10 | ✅ | nested_form | 0.3.2 (git) | ❓ | ❓ | 2021/12 | - |
Ruby 4.0.1 (latest) ✅
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=- # stdoutEasy 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.jsonIn 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.
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 --jsonActivity 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 and release dates 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
- 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.