Repository navigation
How Pages Are Read
What happens between the URL you give and the record you get back. Outputs come from fetchwright 0.1.0 against a stand-in for Google's API, with sample content (see Home).
One request per URL, to Gemini's Interactions API (POST /v1beta/interactions), with:
- the model name;
- an instruction that names the URL and asks for JSON;
- the URL context tool, and no other tool;
-
store: false, so Google does not keep the exchange (by default it would, 1 day on the free tier and 55 days on paid, per the docs on 2026-10-15).
The code refuses to build a request with Google Search in it, in any spelling. Google's terms restrict what may be done with Search results, so fetchwright never asks for them (Terms and privacy).
URL context tries Google's index first, then a live fetch. That is why a Reddit thread reads even though Reddit refuses scripts: Google already holds a copy. A thread too new to be in the index does not read. One 8.6 hours old was indexed on 2026-10-15; nothing younger was tried.
A Reddit /comments/ URL or a Hacker News item URL is read as a thread: title, post body, and comments, word for word, in page order, with the poster's own replies marked [OP]. Any other URL is read as a page: title and main content.
Pages default to a digest: a faithful section-by-section account in the page's order, with names, numbers, dates and links kept exactly and at most a sentence quoted at a time. The reason: on 2026-10-15, asking for a whole Wikipedia article word for word got HTTP 400 "Request blocked due to copyright/recitation content", and the digest read every section. Thread comments are short and copied fine.
python fetchwright.py read https://example.org/article# An example article
url: https://example.org/article
model: gemini-3.8-flash mode: page style: digest (the model's account, not quotes)
retrieval: success https://example.org/article
tokens: page 3120, output 410, total 3720
The article opens with what the project is for.
Its second section lists three releases, in 2024, 2025 and 2026.
A digest is the model's account, never a quote. --verbatim asks for the exact text. When Google blocks it as recitation, fetchwright asks once more for a digest, and the record says style: digest with both attempts in models_tried:
python fetchwright.py read https://example.org/article --verbatim --json{
"fetchwright": "0.1.0",
"url": "https://example.org/article",
"mode": "page",
"style": "digest",
...
Each response carries Google's retrieval status for every URL it touched, and fetchwright prints them all (retrieval: in text, retrieval in JSON).
- Statuses seen on 2026-10-15:
successanderror. The docs also namepaywallandunsafe. Any other value is listed undernew_statuses("new retrieval status seen"). - A Reddit thread comes back with two entries: the thread's full slug URL with
success, which is the index copy, and the bare URL you gave witherror, because Reddit refused the live fetch. That is normal. - If no URL shows
success, fetchwright throws away whatever the model wrote and reports the page as not read, even when the model claimed it was. A model can answer from memory, and this rule keeps that out.
From threads, before anything is printed:
-
Placeholders. "Comment removed by moderator", "Comment removed by Reddit", "Comment deleted by user",
[deleted],[removed], and the "Comment Image" marker are dropped. When a placeholder starts a comment and real text follows it, the placeholder goes and the text stays. A post body that is only a placeholder is reported as removed (body_removed). -
Lone usernames. Gemini sometimes returns a commenter's username as the comment's text: 20 times in 381 comments in a test on 2026-10-15. Any comment that is a single token of username shape (3 to 24 letters, digits,
_or-, optionally afteru/) is dropped. A one-word comment such as "lol" goes too, because it cannot be told apart from a username, and one word gives a note nothing to use. - Empty comments.
The counts are in the comments heading in text and in dropped in JSON. In the sample on Home, "Example_User_1234" and "Comment removed by moderator" were dropped.
Deleted posts and comments came back as placeholders or empty in every test, never as their old text.
The default list is gemini-3.8-flash, then gemini-3.5-flash-lite. The next model is tried when one answers 5xx, 404 (model gone or closed to new keys), a 400 that is not about the key, or drops the connection. A 429 that names one model's own quota moves to the next model, and the spent model is skipped for the rest of the run (since 0.2.0). Any other 429, a quota 429 when every model is spent, or a 401, 402, 403 or "API key not valid" stops the whole run.
python fetchwright.py read https://www.reddit.com/r/examplesub/comments/1abc234/ --json --max-chars 900with the first model overloaded, begins:
{
"fetchwright": "0.1.0",
"url": "https://www.reddit.com/r/examplesub/comments/1abc234/",
"mode": "thread",
"style": "verbatim",
"model": "gemini-3.5-flash-lite",
"models_tried": [
{
"model": "gemini-3.8-flash",
"http": 503,
"seconds": 0.1,
"outcome": "next",
"why": "overloaded (503): This model is currently experiencing high demand."
},
On 2026-10-15 that was the usual case: gemini-3.8-flash answered check but returned 503 or dropped the connection on URL context reads, and gemini-3.5-flash-lite read every page.
python fetchwright.py read https://www.reddit.com/r/examplesub/comments/1abc234/ --json{
"fetchwright": "0.1.0",
"url": "https://www.reddit.com/r/examplesub/comments/1abc234/",
"mode": "thread",
"style": "verbatim",
"model": "gemini-3.8-flash",
"models_tried": [
{
"model": "gemini-3.8-flash",
"http": 200,
"seconds": 0.1,
"outcome": "ok",
"why": ""
}
],
"read_ok": true,
"why_not": "",
"retrieval": [
{
"url": "https://www.reddit.com/r/examplesub/comments/1abc234/a_small_upscaler_node/",
"status": "success"
},
{
"url": "https://www.reddit.com/r/examplesub/comments/1abc234/",
"status": "error"
}
],
"new_statuses": [],
"title": "I made a small upscaler node",
"usage": {
"input": 190,
"page": 4210,
"output": 410,
"total": 4810
},
"body": "It upscales video in tiles so it fits in 8 GB.\n\nLink in the comments.",
"body_removed": false,
"comments": [
{
"text": "Works on my 3060, about 2 fps at 1080p.",
"author_is_op": false
},
{
"text": "Thanks for testing. Tiles of 512 are the default.",
"author_is_op": true
},
{
"text": "Does it keep the audio track?",
"author_is_op": false
}
],
"dropped": {
"placeholder": 1,
"lone_token": 1,
"empty": 0
}
}
| field | meaning |
|---|---|
mode, style
|
thread or page; verbatim or digest (the style finally used) |
model, models_tried
|
the model that answered, and every attempt with its HTTP status, seconds, outcome and reason |
read_ok, why_not
|
whether the page was read, and why not |
retrieval, new_statuses
|
Google's status per URL; statuses the docs do not list |
usage |
tokens: your instruction (input), the fetched page (page), the answer (output), total
|
body, body_removed, comments, dropped
|
threads only |
text |
pages only |
unparsed |
only when the model's answer was not JSON: its first 2,000 characters |
No usernames appear anywhere in the record.
In a test on 2026-10-15 with six real Reddit threads on gemini-3.5-flash-lite, titles and post bodies were right six of six. Comments returned: 20 of 21, 47 of 51, 12 of 13, 151 of 185, 62 of 174 and 89 of 251. Long threads come back in part. Strikethrough is lost, so a struck-out claim reads as asserted. Each thread cost 3,000 to 15,000 page tokens plus 600 to 10,000 output tokens. No comment was shown to be invented.
This wiki describes fetchwright 0.2.0 and was last updated on 2026-10-16. fetchwright is MIT licensed. Report problems in the issues. Releases: GitHub releases.
Using it
The releases
Contributing
Elsewhere