Inspiration

Most subreddits enforce rules manually — mods remove content, issue bans, and track violations in spreadsheets or Discord. There's no built-in way to apply progressive discipline consistently across a mod team. A first-time spammer and a repeat harasser might get the same warning because mods can't see violation history at a glance. I wanted to bring the kind of structured escalation systems used in professional community management to Reddit, accessible with a single right-click.

What it does

Discipline Tracker adds right-click menu items to Reddit posts and comments. When a mod selects Record Violation and chooses a rule, the app automatically:

  1. Checks the user's violation history for that rule
  2. Applies escalating sanctions — warning → content removal → timed ban → permanent ban
  3. Records the violation and updates statistics
  4. Sends a PM notification to the user
  5. Tracks ban durations and auto-unbans when they expire

Default rules (Spam, Harassment, Incivility) come pre-configured, but mods can customize everything.

How I built it

  • Devvit (Reddit Developer Platform) — the entire app runs inside Reddit's serverless runtime
  • TypeScript with Devvit's Blocks UI (JSX) for the Config and Dashboard custom posts
  • Devvit Redis for persistent storage — violation records, ban queues, rule configs, and stats
  • Devvit Menu Actions for the right-click integration
  • Devvit Scheduler for the unbanCheck cron job
  • Devvit Triggers (AppInstall, AppUpgrade) for automatic rule seeding

Architecture:

src/ ├── main.tsx — App entry, menu items, scheduler, triggers ├── menu/ │ ├── recordViolation.tsx — Core enforcement workflow │ └── viewHistory.tsx — Violation history viewer ├── components/ │ ├── ConfigPage.tsx — Rule configuration viewer │ └── Dashboard.tsx — Violation statistics ├── storage/ │ └── redis.ts — Data layer (hash-based, no global redis) ├── jobs/ │ └── unbanCheck.ts — Scheduled auto-unban └── types.ts

Challenges I ran into

Network proxy hell. My environment routes traffic through a Clash proxy with DNS poisoning. The Devvit CLI uses Node.js fetch (undici), which doesn't respect HTTP_PROXY environment variables. After extensive debugging, I built an ESM preload script that monkey-patches globalThis.fetch to inject an undici ProxyAgent, routing all CLI traffic through the proxy. This took the better part of a day to solve.

Devvit API subtleties. Several API behaviors weren't obvious from the docs:

  • MenuActionRequest provides .targetId and .location, not .postId/.commentId — those come from context
  • Form select fields return values as arrays (["rule_spam"]), not strings
  • RedisClient has hScan/zScan but no scan — stats had to be refactored to hash-based storage
  • context.redis.global doesn't exist on all apps — cross-subreddit discovery needed a per-installation job pattern

App versioning. Regular devvit upload doesn't auto-update existing installations — each upload bumps the version number but the installed subreddit stays on the old version. Solved with explicit devvit install subreddit app@version commands.

What I learned

  • How to structure a serverless Reddit application with persistent state via Redis
  • The Devvit component model (Blocks UI, Menu Actions, Custom Posts, Triggers, Scheduler)
  • Undici's dispatcher architecture and how to proxy Node.js fetch at the global level
  • The importance of incremental debugging — stepping through each layer (menu → form → handler → action → storage) with diagnostic forms to isolate failures

Built With

Share this project:

Updates