Realtime leaderboard of who gives and receives the most PR approval stamps in Slack.
Warning
This is a chaotic side project held together by vibes and Convex. If you take leaderboard rankings seriously, that's on you.
StampHog gamifies code review culture. It watches your Slack channels for PR links and stamp reactions, then ranks everyone on a live leaderboard. Think of it as a hall of fame for your most prolific reviewers (and most persistent PR posters).
How it works:
- Someone posts a GitHub or Graphite PR link in Slack
- A reviewer reacts with a stamp emoji (there are 19 tracked variants)
- StampHog records the stamp and updates the leaderboard in realtime
Built with TanStack Start + Convex + PostHog.
pnpm install
npx convex dev --once
pnpm devContributors can load deterministic Faker-generated fixture data directly into Convex:
# Keep existing data, replace prior fixture rows
npx convex run seed:seedTestData '{}'
# Optional: wipe all existing data first
npx convex run seed:seedTestData '{"resetExistingData":true}'This creates sample actors, PR requests, and stamp events so the leaderboard and recent events UI are populated immediately.
- Set Event Subscriptions request URL to:
https://<your-convex-deployment>.convex.site/slack/stamps
- Subscribe to bot events:
reaction_addedreaction_removedmessage.channels(andmessage.groupsfor private channels)
- Add OAuth scopes:
reactions:readchannels:history(plusgroups:historyfor private channels)users:read(for names and avatars)emoji:read(for custom emoji URLs)
- In Convex, set environment variables:
SLACK_SIGNING_SECRET(for verifying Slack signatures)SLACK_BOT_TOKEN(for fetching message authors)CHANNEL_IDS(comma-separated channel IDs for backfill)
StampHog tracks 19 emoji variants including stamp, lgtm, approved_stamp, check, and more. The reacted message must contain a qualifying URL (github.com or graphite.dev).
- Reviewer (stamp giver) = the user who added the reaction
- Requester = the author of the reacted message (looked up via Slack API)
- PR request messages are tracked as soon as they're posted, so requesters appear even with 0 stamps
- Non-tracked emojis and messages without qualifying URLs are ignored
Import existing qualifying reactions from Slack channels:
# Single channel
npx convex run stamps:backfillChannel '{"channelId":"C0123456789"}'
# Multiple channels
npx convex run stamps:backfillChannels '{"channelIds":["C0123456789","C0987654321"]}'
# With options
npx convex run stamps:backfillChannel '{"channelId":"C0123456789","oldestTs":"1704067200","maxMessages":10000}'oldestTsis a Slack timestamp in seconds ("1704067200"= 2024-01-01 UTC)maxMessagesbounds scan cost (default5000, max50000)- Backfill is hard-limited to the most recent 90 days
- Idempotent via dedupe keys, safe to rerun
Delete data older than 90 days and clean up orphaned actors:
npx convex run stamps:pruneDataOlderThanRetentionWindow '{}'pnpm dev # Run web app + Convex together
pnpm dev:web # Web app only (Vite)
pnpm dev:convex # Convex only
pnpm build # Production build
pnpm preview # Preview production build
pnpm check-types # TypeScript check
pnpm check # Lint (ultracite/biome)
pnpm fix # Auto-fix lint issuesPRs welcome. See PostHog's contributing guide for general guidelines.
MIT
