Inspiration

Reddit mod teams in 2026 are fighting a two-front war. On one side: AI-generated spam waves that arrive faster than any human queue can absorb. On the other: the coordination failure that comes from every moderator working from their own isolated view of the queue. One mod removes a post. An hour later, another mod tries to remove the same post. A third mod never saw it at all.

The tools most teams rely on are browser extensions. They require every moderator to install and configure them individually. They break on mobile. They have no shared state. When one mod takes an action, nobody else knows.

We built Bastion to fix both problems at once: a native Devvit app that gives the whole team a shared, real-time view of moderation activity, with automated detection and coordinated enforcement built in from day one.

What It Does

Bastion is a single install that gives every moderator on a Reddit team six capabilities that previously required multiple separate tools — or no tool at all:

Wave Detection. Every new post and comment is automatically scored against five signals: new account, low karma, high posting frequency, similar content (title fingerprinting), and known spam domain. Domain matching scans the URL, title, and full post body — not just link posts. When an item hits two or more signals, it's grouped into an active wave and surfaced in the dashboard for the whole team to see.

Council Cases. Any mod can right-click a post or comment and send it to a council. A dedicated post is created where mods vote Remove, Approve, or Escalate. The referring mod's initial recommendation counts as their first vote. When quorum is reached, the action executes automatically — no manual follow-up, no double execution.

User Dossiers. Every user gets a persistent dossier tracking their warning level (Clean / Watch / Warned / Escalated), full council case history, and team mod notes. Warning levels update automatically as cases resolve. The council form pre-selects Escalate for repeat offenders.

Domain Blacklisting. Tag spam domains once from the dashboard. Every future post mentioning a red-tagged domain in any field gets flagged in wave detection automatically.

Comment Nuke. Bulk-remove up to 500 comments from a thread, or all replies to a specific comment, in one action.

Mod Analytics. A 7-day stats table showing every mod's removes, approves, bans, and nukes — including automated Bastion actions.

How We Built It

Bastion is built entirely on Devvit-native primitives: no external APIs, no paid services, no infrastructure to deploy or operate.

The core architecture challenge was that Devvit only honours the last registered custom post type renderer — meaning all posts would render with the same component regardless of their intended view. We solved this with a single BastionPost component that reads a postkind:{postId} Redis key to decide whether to render as the mod dashboard or as an individual council case post. All React hooks are called unconditionally at the top of the component to satisfy Devvit's Blocks runtime invariant, with early returns only after the hook boundary.

Wave detection runs fully in the PostSubmit and CommentSubmit triggers using pure heuristics: Redis-keyed frequency counters with 1-hour TTLs, normalized title hashing for content fingerprinting, and substring matching against red-tagged domains. No ML, no external call — just fast, synchronous scoring.

The council quorum system uses an optimistic concurrency check before executing any Reddit API action, so simultaneous votes from two mods can never fire the same action twice. The referring mod's recommendation is recorded as their first vote at case creation, so with quorum set to 1 a mod can self-resolve cases immediately.

All persistent state lives in Devvit Redis, namespaced by subreddit ID, with an hourly scheduler for case expiry and a 6-hour scheduler for wave cleanup.

Challenges

The custom post type renderer limitation. Devvit only uses the last-registered addCustomPostType renderer for all posts. We discovered this the hard way when the dashboard started rendering as "Case not found." The single-component solution with a Redis routing key was the right fix, but it required restructuring the entire UI layer to call hooks unconditionally.

Forms inside custom posts. Forms created with Devvit.createForm only work from menu item handlers — they are not registered with the Blocks handler. Calling context.ui.showForm with such a form from a custom post button throws "Form does not exist." The fix is useForm from the Blocks API, which registers the form as a hook and makes it available to button press handlers.

Devvit ID prefixes. The Reddit API client throws if a content ID passed to remove() does not start with t1_ or t3_. IDs from trigger events are already prefixed; IDs returned by certain Redis lookups may not be. Getting this consistently right across council execution, wave nuke, and comment removal required careful tracking of where each ID originated.

Wave detection with mod accounts. Mod accounts have old ages and high karma, so the new-account and low-karma signals never fire during development testing. We added lower configurable thresholds and documented how to test with the high-frequency signal (post twice in under an hour) for mod accounts.

What We Learned

Building a real moderation tool reveals how much coordination overhead gets absorbed silently by experienced mod teams. The features that seem simple — "just show who removed what" — turn out to matter enormously at scale. The wave detector, in particular, forced us to think carefully about what "coordinated activity" actually looks like in signal terms, and how to keep false positive rates low enough that mods trust the output.

On the technical side: Devvit's constraint that all hooks must be called unconditionally is stricter than React's equivalent rule in practice, because the post-level context switch (dashboard vs. council) happens at a layer where early returns would normally be natural. Designing around that constraint while keeping the component readable was the most interesting architectural problem in the project.

Built With

Share this project:

Updates