Loading...
FlagShark is a small piece of plumbing. It runs in your existing GitHub Actions, reads your code, and opens PRs. There is no agent to deploy, no daemon to run, no SDK to integrate. The whole pipeline runs inside the CI you already have.
FlagShark is a published GitHub Action. You add a workflow file. That's the install.
No tokens. No flag-vendor API keys. No prod credentials. The Action runs inside your existing GitHub Actions environment and reads your code via the standard contents: read permission.
You can also install via the GitHub Marketplace one-click, or via gh extension install flagshark/flagshark if you prefer the CLI flow.
# .github/workflows/flagshark.yml name: FlagShark cleanup on: [pull_request] permissions: contents: read pull-requests: write jobs: scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: flagshark/action@v1 with: vendor: launchdarkly
// vendor: launchdarkly | statsig | split | configcat | flagsmith | unleash | posthog | openfeature
The next pull request you open triggers the workflow. FlagShark walks your repo's AST in 12 languages (tree-sitter WASM, no native deps), identifies every flag reference, cross-checks against the SDK call patterns of your vendor, and decides which are stale.
The comment posts in one of four states: findings, nothing to flag, scan failed (with reason), or scan deferred (repo too large). There is no fifth state. We never ghost a PR.
Each finding in the comment has a link. Click it, and FlagShark opens a separate PR on your repo that removes the stale flag: deletes the dead else-branches, removes the SDK call sites, deletes orphan helper functions, updates affected tests.
The cleanup PR is a normal PR. It runs your CI. It needs a review. It targets your default branch. We never auto-merge anything. If your tests fail, the PR fails. We don't ship breakage.
You review it like any other PR. Diffs are small (median: 24 lines). Approve, merge, the flag is gone: from code, from your SDK call sites, from your mental load.
// before — packages/checkout/src/index.ts import { useFlag } from '@launchdarkly/react'; export function Checkout() { const useV2 = useFlag('checkout_v2_redesign'); if (useV2) { return<CheckoutV2/>; } return<CheckoutLegacy/>; }
// after — flagshark removed 'checkout_v2_redesign' export function Checkout() { return<CheckoutV2 />; }
Also removed: 1 import, 3 references in test files, 1 unused helper function.
git blame > 90d && no_recent_modification
if (flag) return X; else return X;
const ENABLED = true;
if (ENABLED) {...} else {...}useFlag('deleted_in_vendor_ui')log({ variant: flag('exp_a') });flags.yml: experiment_pricing_v2: true
The dishonest pitch is "we catch everything." The honest one is "we catch what we can prove. If we can't prove it's stale, we say nothing." Here's where we stay quiet.
useFlag(flagName) where flagName is a variable computed at runtime, we can't follow the data flow. We skip these silently..generated.ts, *_pb.go) because the next regen will re-add them.node_modules/, vendor/, and anything in your .gitignore. Cleanup PRs on dependencies aren't yours to ship.Install the free Action. Open any PR. Get the comment within seconds.