TRENDING
Rows of red climbing helmets, carabiners and harness straps laid out on the ground at a ropes course
October 21, 2026
How to Build a Safe AI Agent Harness in Python With Tool Validation, a File Sandbox, and Run Limits
A thin transparent polymer test specimen stretched between the metal grips of a tensile testing machine
October 21, 2026
Ai2’s FairStress Turns Fairness Testing Into a Pressure Test, but the Released Files Do Not Match the Report
A vintage Banda Model Ten spirit duplicator, a hand-cranked paper copying machine, shown with its instruction book on a white background
October 21, 2026
An Anonymous Comment Can Hand Duplicator a Payload That Fires at Migration Time, and Under a Third of Free Installs Run the Fix
Colorful horizontal layers of sedimentary rock exposed in a cliff face at Makhtesh Ramon in Israel, with two desert shrubs in the foreground
October 21, 2026
How to Build an LSM-Tree Key-Value Store in Python With a Write-Ahead Log, SSTables, Bloom Filters, and Compaction
A broadcast studio teleprompter with script lines shown mirrored on its lower monitor and reflected in the glass in front of a camera lens
October 21, 2026
WhatsApp’s Leaked Chatbot Notes Turn a Help Page Into Two Documents, and the Hidden One Told the Bot Not to Recommend a Regulator
21 Oct 2026
SXZ.io SXZ.io
  • Home
Search the Site
Popular Searches:
Technology Amazon AI
Recent Posts
An out-of-order sign standing in an airport sink basin, with a title, a friendly explanation and a Spanish translation, like the members of an error response
How to Return RFC 9457 Problem Details From a FastAPI API and Stop Error Responses From Leaking Secrets
October 21, 2026
Heavily corroded cogwheels and a rusted ring gear inside an abandoned sulphur factory beside the sea on White Island
Cloudflare’s tsrs Turns an Agent-Written Compiler Port Into a Differential-Testing Bet, and the 8x Headline Depends on the Baseline
October 21, 2026
A black public warning siren with a wide dish-shaped horn on a short pole against a clear blue sky
PHP Warns of Two Critical Flaws and Sets a 14:00 UTC Patch, but the Leak, the Score and the Exploitation Reports Do Not Agree
October 21, 2026
SXZ.io SXZ.io
  • Home

Categories

Articles 255 Posts
News 258 Posts
Learning Hub 228 Posts
Home/Articles/GitHub’s Star History API Turns 2,511 Requests Into 24, but Its Curve Counts Only the Stars That Survived
Articles

GitHub’s Star History API Turns 2,511 Requests Into 24, but Its Curve Counts Only the Stars That Survived

GitHub’s new star history endpoint cuts react/react from 2,511 requests to 24 by filtering by week before counting. A check against the live API shows the cost now follows repository age, not stars,...

October 15, 2026 13 Min Read
37

On October 14, GitHub’s engineering blog published “From 2,500 requests to 24: Rebuilding star history without the stargazers”, by Taylor Cuilty and Sarita Iyer. It describes a new endpoint that returns a repository’s star counts by calendar week, with no usernames attached. For react/react, which has about 251,000 stars, the post says the old stargazer list took “about 2,511 requests” of 100 people each, and the new endpoint takes about 24.

Table Of Content

  • How GitHub restricted the lists and added aggregates
  • What the endpoints answered on October 15
  • Why the list was expensive to serve
  • Three designs, and where the work happens
  • A model of the three query shapes
  • The cost now follows a repository’s age
  • Polling, ETags, and the anonymous 304
  • The curve counts only the stars that survived
  • Run the check yourself
  • Dense weeks are where the time filter stops helping
  • What the post leaves open
  • Practical notes for tool builders
  • Sources and method

I checked the claim against the live API on October 15, without a token. The headline holds: react/react’s whole history came back in 24 requests, and its weekly totals add up to exactly 251,042, the repository’s current star count. The more useful findings sit behind that number. The cost of a full history now follows a repository’s age, one request for every 30 weeks, so a 76-star repository from 2014 takes 22 requests while a 250,000-star repository that is ten weeks old takes one. And the curve is built from the stars that still exist, so it changes whenever someone removes a star.

How GitHub restricted the lists and added aggregates

The new endpoint is the second half of a change that began in the summer. GitHub’s June 30 changelog entry said that access to /repos/{owner}/{repo}/stargazers and /repos/{owner}/{repo}/subscribers would be “limited to admins and collaborators”, and that /users/{username}/subscriptions would be deprecated and return empty responses. Its reason: “this information has increasingly been misused to collect user data for spam activities which negatively impacts user experience and platform trust.” The engineering post describes the same abuse from the other side: “Identities were available for bulk collection, so bad actors started scraping public stargazer lists for spam and phishing campaigns.”

Date (2026) What happened Where it is recorded
June 30 Changelog announces that the stargazers and subscribers lists will be limited to admins and collaborators GitHub changelog
July REST docs add a notice that the stargazers listing endpoints are limited to admins and collaborators GitHub REST docs
August 28 A GitHub maintainer announces GET /repos/{owner}/{repo}/stargazers/history in a community discussion Discussion 206104
September 5 to 15 Replies ask for negative counts (answered September 11), report a tracker rebuilt on the endpoint, and ask for a way to skip empty weeks Discussion 206104
October 14 The engineering post explains the design GitHub blog

The restriction broke star-history tools, which in the post’s words “used the old response to calculate star history, often without displaying or otherwise using the usernames it returned.” GitHub then talked with maintainers and tool builders and concluded that what they needed was “counts and dates, not usernames or other user data.” The engineering post appeared 47 days after the endpoint was announced in the discussion, so the endpoint had been public for nearly seven weeks, and at least one tracker had already been rebuilt on it, by the time the post came out.

What the endpoints answered on October 15

From one IP address, with no token: GET /repos/react/react/stargazers?per_page=2 returned 401 with the message “Requires authentication”, and so did /repos/react/react/subscribers. The deprecated /users/gaearon/subscriptions and /users/octocat/subscriptions both returned 204 with no body, which fits the changelog’s “empty responses”. The per-user list /users/gaearon/starred still answered 200. The new history endpoint answered 200 with no token, as its docs say: “This endpoint can be used without authentication or the aforementioned permissions if only public resources are requested.” I did not test an authenticated account that is not a collaborator, so I cannot say whether such a caller gets the empty response or the 403 that the changelog mentions.

Why the list was expensive to serve

The post’s account of the old endpoint starts with how stars are stored. The model “records the current set of people who have starred a repository”, and it “was not designed as a historical event log: when someone removes a star, that record is removed.” A tool that wanted a growth curve had to page through every current stargazer, 100 at a time, keep the timestamps and discard the people.

Part of the database cost came from a bug. Per the post, “every request also counted all of those stars to work out how many pages existed, even though the caller received only one page of up to 100 people.” In the week before access was restricted, “nearly 37,000 of these count queries ran slowly enough to be logged, averaging nearly 3 seconds each, and database safeguards killed more than 1,000 of them.” After the restriction, “the database served about 60% fewer stargazer requests, and the total time it spent on the count query fell by about 80%.” GitHub also moved the stargazers page on github.com to the cached star count, which took slow queries from that page from “hundreds each week” to zero.

Three designs, and where the work happens

GitHub weighed three designs. The first kept one record per star and returned only starred_at to callers without collaborator access; the post’s objection is that consumers “would still have to paginate through and process potentially many thousands of individual records.” The second grouped stars into daily or weekly buckets and then paginated the buckets. Here the objection is about the order of work: the database “first finds all stars for the repository, groups them into buckets, and sorts those buckets” before LIMIT and OFFSET apply, so reading react/react’s history that way “would have meant examining roughly 6 million rows, because every one of the 24 requests would have reprocessed all of its stars.” The shipped design turns page and per_page into an indexed created_at range first, filters, and only then groups into daily counts, so that “the database examines each star about once (roughly 251,000 rows) across the full history.”

A model of the three query shapes

The post does not name its database engine, so I could not rerun GitHub’s queries. I built a model in SQLite 3.50.4 instead. The table holds 491,042 star rows with an index on repository and creation time. Repository 1 stands in for react/react and carries 251,042 rows, one for each star in the daily counts I read from the live endpoint, each with a random time inside its day. Three other repositories share the table. A counting function, called once for every row that enters the grouping, measured the rows aggregated, and its total matched the bucket sums for the time-range version. These are the three shapes:

-- SQLite 3.50.4. Table: stars (id INTEGER PRIMARY KEY, repo_id INTEGER, user_id INTEGER, created_at TEXT)
-- Index: CREATE INDEX idx_repo_created ON stars (repo_id, created_at);

-- A. Old list: one page of people, plus a count on every request
SELECT user_id, created_at FROM stars WHERE repo_id = ? ORDER BY created_at LIMIT 100 OFFSET ?;
SELECT COUNT(*) FROM stars WHERE repo_id = ?;

-- B. Aggregate every star, then paginate the buckets
SELECT date(created_at) AS d, COUNT(*) FROM stars WHERE repo_id = ?
GROUP BY d ORDER BY d DESC LIMIT 210 OFFSET ?;

-- C. Time range first (the range covers the weeks of one page), then aggregate
SELECT date(created_at) AS d, COUNT(*) FROM stars WHERE repo_id = ?
  AND created_at >= ? AND created_at < ?
GROUP BY d ORDER BY d DESC;
Query shape Requests for the full history Rows touched Time on this machine
A. Old list: a page of 100 people plus a COUNT(*) on every request 2,511 630,366,462 index entries counted 28.7 s counting (11.4 ms each) plus 17.2 s reading pages with OFFSET
B. Aggregate every star, then LIMIT and OFFSET over the buckets 24 6,025,008 rows aggregated (251,042 per request) 2.62 s total, median 92.5 ms
C. Time range first, then aggregate 24 251,042 rows aggregated (1,489 to 21,584 per request) 0.103 s total, median 4.5 ms

The row counts match the post’s: about 6 million for shape B and about 251,000 for shape C. The reason is simple. Shape B pays for every star in the repository on every page, so its total is pages times stars, while shape C pays for each star once. The saving therefore equals the number of pages: 24.0 times fewer rows and 25.3 times less time here. A repository whose history fits in one page of 30 weeks gets no saving at all, because both shapes then read every star once. The timings belong to SQLite on one machine with a warm cache. GitHub’s production figures, nearly 3 seconds on average for the slow count queries it logged, come from a different regime, and the post gives no latency for the new endpoint, only rows examined. For why paging by position gets slower the deeper you go, see our keyset pagination tutorial.

The cost now follows a repository’s age

The history endpoint pages by calendar week, 30 weeks to a page, back to the week the repository was created. That makes the number of requests a function of age. For five repositories I compared the Link header’s last page with the weeks since creation divided by 30, rounded up, and all five matched; the sixth, deepseek-ai/deepseek-harness, is younger than 30 weeks and returned no Link header at all.

Repository Stars Created Calendar weeks Old list (100 per page) History (30 weeks per page)
react/react 251,042 2013-05-24 700 2,511 24
freeCodeCamp/freeCodeCamp 457,094 2014-12-24 617 4,571 21
pallets/flask 74,899 2010-04-06 863 749 29
ollama/ollama 183,221 2023-06-26 173 1,833 6
deepseek-ai/deepseek-harness 250,107 2026-08-13 10 2,502 1
99designs/cmdstalk 76 2014-05-03 651 1 22

The old-list column is my arithmetic (stars divided by 100, rounded up), because anonymous callers can no longer request the list. The history column is what the endpoint reported. A commenter in the GitHub discussion had already spotted the pattern. After rebuilding a tracker of about 32,000 Go repositories on the endpoint, which they said “works well”, they wrote: “Pages hold 30 weeks each and go back to the repo creation date, so the cost of a repo depends on its age and not its stars.” Their example was 99designs/cmdstalk: “created in 2014 and has 76 stars. Its history is 647 weeks, and only 53 of them contain a star. That is 22 requests. Without the empty weeks it would be 2.” I read all 22 pages: the repository now spans 651 calendar weeks, four more than the commenter counted on September 15, and 53 of them contain a star. The commenter proposed an opt-in way to omit empty weeks. The thread I read on October 15 shows no reply from GitHub to that request, and the docs say only that “Weeks without stars contain zero counts.”

The break-even is easy to state. The history costs about weeks divided by 30 requests and the old list cost stars divided by 100, so the new endpoint needs fewer requests only for repositories that gain more than about 3.3 stars a week on average. react/react averages 358.6 and the ten-week-old deepseek-harness repository averages more than 25,000, but cmdstalk averages 0.1. The cost is also capped by age: by the same formula a repository created in early 2008 would need 33 pages, which fits inside the 60 requests an hour that anonymous callers get.

Polling, ETags, and the anonymous 304

Every history page I read carried an ETag and Cache-Control: public, max-age=60, s-maxage=60. New stars only land on the newest page, because pages move backward from the current week. Older pages are not frozen, though, since a removed star changes a past day, so a careful tool revalidates them. GitHub’s best-practices page says that “Making a conditional request does not count against your primary rate limit if a 304 response is returned and the request was made while correctly authorized with an Authorization header.” I sent a conditional request for page 24 without a token and got 304, but X-RateLimit-Remaining still fell from 27 to 26. Free revalidation therefore needs a token; I did not test one.

The curve counts only the stars that survived

The weekly totals I read for react/react sum to 251,042, and the repository endpoint reported 251,042 stars at 10:36 UTC. A gross count of every star ever created would exceed the current total whenever anyone has removed a star, so the equality fits what GitHub says. A maintainer replied in the discussion on September 11: “The star history endpoint returns counts of stars created on each date that remain active today.” They continued: “Right now we don’t support a historical record of unstars (when users unstar a repository) so we’re only surfacing the history of stars that currently exist.” The GET /repos/{owner}/{repo}/stargazers/count endpoint, listed on the same docs page, says the same about totals: “Users who previously starred the repository but later removed their star are not included.” And the engineering post lists “removed stars” among the details that “still affect what it can return.”

Three consequences follow for anyone drawing a growth curve from it.

  • The past is rewritten. A launch-day spike that included stars later removed shrinks retroactively, so the chart cannot show how big that spike once was. One commenter, after asking whether an unstar would show up, added that the decrement appeared almost immediately.
  • Days cannot go down. The docs define the days array as “the number of stars created on each day of the week, starting on Sunday”, so a day on which a repository lost more stars than it gained looks like a quiet one. A commenter asked for “decreasing star counts” and was told that this “would require a fundamental change to how we store star records.”
  • The edges are fuzzy. The newest page includes the current partial week (the newest week I read showed 8 stars for Thursday and zeros for Friday and Saturday, fetched at 10:34 UTC that Thursday), and the docs warn that “Week and day boundaries are not guaranteed to align with UTC.” Every one of react/react’s 700 weeks began on a Sunday at 00:00 UTC, but that is an observation, not a promise.

Run the check yourself

This script needs Python 3.13 and the requests package, and no token. It walks the Link header’s next pages, adds up the weekly totals, and compares the sum with the count endpoint.

# star_history_check.py (Python 3.13, requests 2.34; no token, 60 requests an hour)
import sys

import requests

repo = sys.argv[1]  # for example ollama/ollama
base = f"https://api.github.com/repos/{repo}/stargazers"
headers = {"Accept": "application/vnd.github+json", "User-Agent": "star-history-check"}

weeks, url = [], base + "/history"
while url:
    r = requests.get(url, headers=headers, timeout=30)
    r.raise_for_status()
    weeks += r.json()
    url = r.links.get("next", {}).get("url")

total = sum(w["total"] for w in weeks)
count = requests.get(base + "/count", headers=headers, timeout=30).json()["count"]
print(f"{repo}: {len(weeks)} weeks, history sum {total}, count endpoint {count}, difference {total - count}")

On October 15, python star_history_check.py ollama/ollama printed the line below after seven requests, six history pages and one count. A separate scan of 99designs/cmdstalk, 22 history requests, gave 651 weeks and a sum of 76 against a count of 76. On a repository that gains stars quickly, a difference of a few between two requests is normal.

ollama/ollama: 173 weeks, history sum 183223, count endpoint 183223, difference 0

Dense weeks are where the time filter stops helping

The post is candid about the limit: “The time-range design limits each request to a defined period, but it does not limit the number of stars within that range.” And: “exceptionally dense weeks remain a scaling limitation.” It gives no example or numbers for a dense week. React is a gentle case. Its busiest day was June 15, 2018, with 2,497 stars, its busiest week (starting June 10, 2018) had 3,956, and its ten busiest weeks together hold 5.3 percent of all its stars. The largest single request in my model, page 15, covered the 30 weeks from the week of March 4 to the week of September 23, 2018 and touched 21,584 rows, 8.6 percent of the repository’s stars.

A young viral repository looks different. deepseek-ai/deepseek-harness, created on August 13, 2026, has 250,107 stars and a ten-week history that fits in one page. Its first calendar week holds 118,799 stars, 47.5 percent of the 250,106 that the page sums to, and 74,057 of them landed on its first day, a Thursday. One request therefore has to group every star in the repository, which is the cost the redesign set out to avoid, and the time filter has nothing to cut because the history is shorter than 30 weeks. That is one repository, and I did not survey others or measure GitHub’s latency, but it shows the quantity that bounds the new design: stars in the requested weeks, not stars in the repository.

What the post leaves open

The before-and-after numbers in the post, 60 percent fewer requests and 80 percent less count time, describe the old endpoint after its restriction. For the new endpoint the post reports rows examined, not latency, error rates or load, and the two charts it refers to did not come through as text in my fetch, so I could not check their values. It does not name the database engine, and its text does not give the restriction date, though it links the changelog entry that says June 30 (the docs say July). It points readers to the community discussion for feedback, where the open requests include skipping empty weeks, larger pages (“a larger per_page value would be nice”) and finer than daily resolution, which one tracker maintainer had aimed for at one hour. The post does not say whether any of them is planned. The docs also list a 422 for an endpoint that “has been spammed”; I did not try to trigger it, and nothing I read says what threshold applies.

Practical notes for tool builders

  • Follow the Link header’s rel="last" instead of computing pages. A per_page of 31 returned the same 30 weeks as the default rather than an error, and with per_page=1 the last page was reported as 100 although the history had 700 weeks, which fits the docs’ cap of 100 on page.
  • Read the newest page for new stars and treat older pages as revisable. Revalidate them with an authenticated conditional request if you can; an anonymous 304 still costs a request.
  • If you want to see removals, store each fetch and compare old weeks with the next one. A smaller number in a past week would mean stars were removed since. That is my suggestion and I did not test it, because I could not unstar anything.

Sources and method

The primary sources are GitHub’s engineering post of October 14, the June 30 changelog entry, the REST API documentation for starring (the star history, stargazer count and list sections), community discussion 206104 (the August 28 announcement and replies from September 5, 8, 11 and 15) and the REST best-practices page. Quotations from the discussion are from the page as it stood on October 15. For GitHub’s other engineering post this month, on rebuilding its Git infrastructure, see our earlier piece on GitHub’s Git rebuild.

The live checks ran between 10:34 and 10:49 UTC on October 15, 2026, anonymously from one IP address, within the 60-request hourly limit the responses reported. I read react/react’s 24 history pages and saved them, compared their sum with the repository’s star count, probed the edge cases and restricted endpoints listed above, and read the full histories of 99designs/cmdstalk and ollama/ollama. The old name facebook/react answered with a redirect; the repository now lives at react/react. The old-list request counts, the break-even of about 3.3 stars a week, the 33-page figure for a 2008 repository and the shares of stars in the densest weeks are my own arithmetic from the numbers above. The SQLite model uses the real daily counts but synthetic times within each day, so its timings say something about query shapes, not about GitHub’s database. I did not test with a token, did not observe an unstar, and could not verify GitHub’s internal figures such as the 37,000 slow queries.

Tags:

API DesignData PrivacyGitHubPaginationRate Limiting

Share

A rural road with many patched asphalt repairs and one open pothole
Previous Post

curl Fixes a Record 24 Vulnerabilities in One Release, Including a High-Severity Bug Reported by Anthropic

Victorian silver-framed glass pickle jar with a swirled purple and white body and a domed lid against a grey background
Next Post

How to Stop Pickle Deserialization Attacks in Python and Load Model Files Safely

No Comment! Be the first one.

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Latest
21 Oct
How to Return RFC 9457 Problem Details From a FastAPI API and Stop Error Responses From Leaking Secrets
21 Oct
Cloudflare’s tsrs Turns an Agent-Written Compiler Port Into a Differential-Testing Bet, and the 8x Headline Depends on the Baseline
Trending
October 21, 2026
How to Return RFC 9457 Problem Details From a FastAPI API and Stop Error Responses From Leaking Secrets
October 21, 2026
Cloudflare’s tsrs Turns an Agent-Written Compiler Port Into a Differential-Testing Bet, and the 8x Headline Depends on the Baseline
October 21, 2026
PHP Warns of Two Critical Flaws and Sets a 14:00 UTC Patch, but the Leak, the Score and the Exploitation Reports Do Not Agree
October 20, 2026
How to Build a Safe AI Agent Harness in Python With Tool Validation, a File Sandbox, and Run Limits
October 20, 2026
Ai2’s FairStress Turns Fairness Testing Into a Pressure Test, but the Released Files Do Not Match the Report
October 20, 2026
An Anonymous Comment Can Hand Duplicator a Payload That Fires at Migration Time, and Under a Third of Free Installs Run the Fix

Related Posts

Blue-lit server racks in a modern data center, illustrating the compute infrastructure behind the AI boom.
Articles

The AI Boom Is Spending Real Money Before Proving Real Returns

June 7, 2026
Technician working with a laptop beside server racks, representing enterprise AI retrieval infrastructure
Articles

Google’s Agentic RAG Push Makes Enterprise AI Less of a One-Shot Guess

June 7, 2026
A person with a laptop and smartphone, representing digital attention and AI-assisted work
Articles

AI Chatbots Are Making Attention a Design Problem

June 7, 2026
A customer-support representative wearing a headset against a dark studio background.
Articles

The Meta AI Support Hack Was a Plain Old Authorization Failure

June 7, 2026
SXZ.io SXZ.io
  • [email protected]

Categories

Articles
Learning Hub
News

All Rights Reserved by SXZ.io ©2026