Skip to content
Back to skills

Reddit Digest

ASecurity

Detect cross-subreddit narratives — stories surfacing in multiple unrelated subs at once

  • 6 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 5, 2026
ai-agentsrustgobashgitapi

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 5, 2026

npx -y skills add anajuliabit/aeon --skill reddit-digest --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Reddit Digest?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Reddit Digest
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/anajuliabit-reddit-digest/badge)](https://www.skillsdirectory.com/skills/anajuliabit-reddit-digest)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: Reddit Digest
description: Detect cross-subreddit narratives — stories surfacing in multiple unrelated subs at once
var: ""
tags: [news]
---
<!-- autoresearch: variation D — rethink: cross-sub narrative detector, not per-sub summary -->

> **${var}** — Optional topic filter or single subreddit name. If empty, scans all tracked subs.

If `${var}` is set, restrict candidates to that subreddit or filter narratives by that topic.

## Thesis

A per-subreddit top-10 competes with everyone's own Reddit scroll and loses. The signal Reddit *uniquely* provides that no single feed does: **the same story surfacing in multiple unrelated subs at once**. That's the narrative detector. This skill is built around that — not around per-sub digests.

## Config

Read `memory/subreddits.yml`. If missing, bootstrap it with ≥8 diverse subs seeded from MEMORY.md interests (spread across unrelated communities — narratives are only meaningful if the subs don't normally overlap). Example default:

```yaml
subreddits:
  - { name: r/MachineLearning, subreddit: MachineLearning }
  - { name: r/programming,      subreddit: programming }
  - { name: r/LocalLLaMA,       subreddit: LocalLLaMA }
  - { name: r/netsec,           subreddit: netsec }
  - { name: r/rust,             subreddit: rust }
  - { name: r/technology,       subreddit: technology }
  - { name: r/science,          subreddit: science }
  - { name: r/cryptocurrency,   subreddit: cryptocurrency }
```

Read `memory/MEMORY.md` for tracked interests (influences standout selection and narrative labelling).
Read the last 2 days of `memory/logs/` to avoid repeating narratives already surfaced.

## Steps

### 1. Fetch broadly

For each subreddit, fetch the top of the last 24h (note: `t=day` only applies to `top`, **not** `hot` — original skill had this bug):

```bash
curl -sL -H "User-Agent: aeon-bot/1.0 (by /u/aeon)" \
  "https://www.reddit.com/r/${SUBREDDIT}/top.json?t=day&limit=25"
```

Unauthenticated Reddit JSON API rate limits at ~10 req/min. **Pace requests ≥7s apart** (sequential, not inside parallel tool calls). If curl returns 429 or a network error, retry once after 15s; if still failing, fall back to **WebFetch** on the same URL. If both fail, mark the source `error` and continue — never abort the whole run for one dead sub.

Record a per-source status: `{sub: ok | empty | error}`.

### 2. Clean candidates

For each post under `data.children[].data`, drop if any of:
- `stickied == true` or `pinned == true`
- `removed_by_category` non-null, or `selftext ∈ {"[removed]", "[deleted]"}`
- `over_18 == true` (unless the sub is explicitly NSFW-tracked)
- `created_utc` > 24h old
- `upvote_ratio < 0.80` (drama/brigaded — the "controversial" signal, not the "interesting" signal)

Extract: `id`, `title`, `url` (external), `permalink` (Reddit), `subreddit`, `score`, `num_comments`, `upvote_ratio`, `selftext` (first 500 chars), `is_self`.

### 3. Normalize URLs

For each post with an external URL:
- Lowercase scheme + host
- Strip `www.`, trailing slashes, URL fragments (`#...`)
- Drop query params: `utm_*`, `ref`, `ref_src`, `source`, `fbclid`, `gclid`
- For self posts, use `self:${subreddit}/${id}` as the canonical key (so they never cluster with anything)

### 4. Detect cross-sub narratives

Group posts into clusters:
- **URL clusters:** posts sharing the exact same canonical URL.
- **Title clusters:** posts across different subs whose titles share ≥50% Jaccard similarity on normalized word sets (lowercase, strip punctuation, drop stopwords like `a/the/of/to/is/are/and/or`).  <!-- heuristic — tune if cluster over/undersplits -->

A **narrative** = a cluster with ≥2 posts from ≥2 distinct subreddits. Single-sub clusters are not narratives.

Dedup narratives against the last 2 days of logs: if any post ID in the cluster, or a ≥70%-similar title, was already surfaced, drop the whole narrative.  <!-- heuristic — tune if dedup is too aggressive/loose -->

**Cluster-count fallback:** if clustering produces **fewer than 2** narratives (rare — usually a quiet day or too-strict threshold) **or more than 5** (over-fragmented), skip the narrative format and fall back to a **flat ranked list** of the top individual posts by signal score. Log the fallback reason in the source-status footer.

### 5. Score narratives

```
narrative_signal = Σ log10(score_i + 1) × 1.5
                 + Σ log10(num_comments_i + 1)
                 + 0.5 × (distinct_sub_count − 1)    # cross-community bonus
```

The cross-community bonus makes a 3-sub narrative strictly beat an equal-engagement 2-sub one.

### 6. Standouts (single-sub big stories)

A narrative-only digest is too restrictive on slow days. Also surface up to **2** single-sub standouts — posts with:
- `score ≥ 1000` AND `num_comments ≥ 200` AND `upvote_ratio ≥ 0.90`
- Not already part of a narrative cluster

Rank standouts by `log10(score+1)×3 + log10(comments+1)×2`.

### 7. Summarize — insight, not paraphrase

Pick the top 3-5 narratives by signal + up to 2 standouts (cap at 6 items total). For each:

- If the canonical is an external URL, **WebFetch** it to ground the insight (skip paywalled or failed fetches — fall back to the Reddit discussion).
- For self posts, use `selftext`.
- For discussion-heavy items (`num_comments > score`), identify the *disagreement axis* rather than summarizing the OP.

Write ONE line per item. **Never paraphrase the title. Never write "This post discusses…"** Write the *claim*, the *surprise*, or the *disagreement* — something a reader couldn't derive from just reading the title.

### 8. Format and send via `./notify`

Keep under 4000 chars. Lead with a one-sentence shape signal (e.g., "Quiet AI news; heavy open-source drama.").

```
*Reddit Narratives — 2026-04-20*
_Shape: Quiet AI news; heavy open-source drama crossing rust + programming._

🔗 *OpenAI retracts jailbreak paper 14 days post-publication*
   Spread: r/MachineLearning (450↑ 120💬) · r/OpenAI (220↑ 60💬) · r/ChatGPT (80↑ 30💬)
   Insight: Retraction cites internal safety review, not author request — unusual for a peer-reviewed venue.
   [Canonical](https://example.com/article)

🔗 *Rust 1.83 async-trait ergonomics split*
   Spread: r/rust (880↑ 340💬) · r/programming (310↑ 95💬)
   Disagreement axis: dyn-safe async now vs. waiting for variance fixes.
   [Canonical](https://example.com/rfc)

📍 *Standout — r/netsec*
   • [Title](https://reddit.com/...) — 2100↑ 900💬
     Insight: First CVE confirmed exploited via the Linux eBPF verifier since 2024's bug class.

_sources: 7 ok · 1 empty · 0 error · 12 narratives considered · 3 surfaced_
```

### 9. Suppression

If **zero narratives** AND **zero standouts** pass filters: log `REDDIT_DIGEST_OK (quiet day)` with the source-status line and send **nothing**. Digests that fire every day get tuned out. Only fire when there's signal.

If **all sources errored**: log `REDDIT_DIGEST_ERROR` and send a short alert `"Reddit digest: all N sources errored — check rate limits / API"`.

### 10. Log

Append to `memory/logs/${today}.md`:
```
### reddit-digest
- Sources: 7 ok, 1 empty, 0 error
- Narratives considered: 12
- Surfaced: 3 narratives + 1 standout
- Post IDs: abc123, def456, ... (for cross-day dedup)
```

## Sandbox note

Outbound curl may be blocked in the GitHub Actions sandbox. **Always** fall back to WebFetch on the identical URL for any failed curl. If rate-limited often, drop `scripts/prefetch-reddit.sh` that fetches all configured subs before Claude runs (see CLAUDE.md's pre-fetch pattern) — the skill should read from `.reddit-cache/` first if present.

## Why this skill is different from "what you'd see scrolling"

Per-sub top-10 = noise you can get yourself in two minutes.
Cross-sub narrative = signal that only an aggregator watching ≥8 subs at once can produce.
The skill's job is the thing a human can't do cheaply.

## Environment Variables

None required — Reddit's public JSON API is unauthenticated. A custom User-Agent (`aeon-bot/1.0 (by /u/aeon)`) is required to avoid shared-bucket throttling.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…