staleflags.

your feature flags served their purpose. staleflags finds the dead code they left behind.

You already lint your code. staleflags audits your feature flags. It scans your .env files, compares values across environments, and tells you which flags are dead, aging, or still doing their job.

npx staleflags

run in any project with .env files. zero config.

  staleflags v0.1.0 — scanning for feature flags

  Found 5 flags across 4 files · 17 lines of dead code

  ⚰️  DEAD FLAGS (same value in every environment)
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

    ENABLE_NEW_CHECKOUT
      Value: true in .env.development, .env.staging, .env.production
      Introduced: 14 months ago
      Value unchanged: 14 months ago
      Dead code: src/checkout.ts:4-15 (else branch, 12 lines)
      Total: 12 lines of dead code

    USE_V2_PARSER
      Value: const USE_V2_PARSER = true (hardcoded in src/config.ts:1)
      Dead code: src/config.ts:7-9 (else branch, 3 lines)
      Total: 3 lines of dead code

  ✅ ACTIVE FLAGS (doing their job)
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

    FEATURE_DARK_MODE    true in .env.development · false in .env.production

  👻 PHANTOM FLAGS (read in code, never defined)
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

    FF_SECRET_FEATURE
      Referenced in code but never defined in any environment file

  SUMMARY: 5 flags · 2 dead · 1 active
  DEAD CODE: 17 lines across 2 files (removable today)

How it works

One command. Every flag classified.

1

Scans your codebase

Finds env var flags, hardcoded constants, and config flags across JS/TS/Python/Go/Ruby.

2

Compares across environments

Checks if each flag has the same value in every .env file. Same everywhere = dead.

3

Quantifies the damage

Finds the if/else blocks controlled by dead flags, counts the dead branch lines, gives you the total.

What it checks

7 checks. Zero config.

1

Cross-environment consistency

the novel one

Is this flag the same value in dev, staging, and prod? Same everywhere = dead. The other branch is unreachable.

2

Hardcoded constants

const ENABLE_X = true unchanged for months. Not a flag anymore — it's dead code with a name.

3

Flag age

How long since this flag was introduced (git history). Older flags are more likely to be done.

4

Value age

How long since the value last changed. A flag that hasn't been toggled in 6 months is suspicious.

5

Dead code quantification

Exact line ranges of unreachable code behind dead flags. Not just "this flag is stale" — how many lines can you delete.

6

Phantom detection

Code reads a flag that no config defines. Evaluates to undefined everywhere.

7

Orphan detection

Config defines a flag that no code reads. Leftover config clutter.

Why not X?

Nothing else does this.

Piranha (Uber)

Removes stale flag code

Only works with specific SDKs. YOU tell it which flags are stale.

FlagShark

Tracks flag lifecycle

Tied to LaunchDarkly/Unleash/Split. SaaS.

LD Code Refs

Finds LaunchDarkly SDK calls

Only LaunchDarkly. Not homegrown flags.

ESLint

Lints code quality

Doesn't know your .env files or flag values.

dotenvcheck

Cross-refs env vars

Doesn't quantify dead code. Doesn't check cross-env consistency.

Classifications

What each status means.

⚰️

Dead

Same value in every environment. The other branch is unreachable. Remove the flag and the dead code.

🕰️

Aging

Different in at least one env, but code behind it hasn't changed in months. Probably done.

👻

Phantom

Code reads the flag but no config defines it. Evaluates to undefined everywhere.

🔇

Orphan

Config defines it but no code reads it. Leftover config clutter.

Active

Different values across environments, recently toggled. Doing its job.

Get started

Pick how you want to use it.

Terminal

Run once in any project. Nothing to install, nothing to configure.

bash
npx staleflags
--json--markdown--fail-on dead

GitHub Action

Add one file. Runs on every PR and weekly on schedule. Uses fetch-depth: 0 for git history.

.github/workflows/staleflags.yml
name: staleflags
on:
  pull_request:
  schedule:
    - cron: '0 9 * * 1'

jobs:
  flags:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: Ijtihed/staleflags@v1
        with:
          fail-on: dead

AI Prompt

Paste into Cursor, Claude Code, or Copilot. AI does the rest.

prompt
Add staleflags to this project. Create .github/workflows/staleflags.yml that runs on every PR using Ijtihed/staleflags@v1. Then run npx staleflags locally and clean up any dead flags it finds.