Repository navigation
feat(fetch): read a teaser article's own page for its body - #152
Conversation
Some feeds carry one sentence per item: the 2026-11-03 morning story from pjwu.me was summarized from a 27-character description while its page holds about 1300 characters. 17 of 51 feeds measured that day had a median under 500 characters of text. adapters/fetch/article_page.lengthen_articles reads the page of each RSS article whose text is shorter than summarize_snippet_length, extracts the body with trafilatura (html_text.page_text, no tag-strip fallback, which on a whole page would return nav and footer) and keeps whichever text is longer. Any failure keeps the feed text, one page at a time. Bounds: 8 pages in flight, 10 s per page including redirects and body, 5 MiB of raw bytes. Pages are asked for uncompressed and an encoded answer is refused, since httpx inflates before a cap could count it. Only http(s) is asked; IP literals that are not global, in any IPv4 spelling, and localhost names are refused on every hop. Hostnames are not resolved: pinning addresses needs a custom transport, which the outbound-http-honours-environment spec forbids; the owner accepted the residual risk. Video links (media_links in keywords.json: /watch, /shorts, youtu.be) get no page read, because a video page's text is the site's navigation. The User-Agent names cyris and nothing else. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deps.lengthen_articles is an optional callable that run_digest calls once on the articles a run keeps: after the cap and the vote-similarity holds, before DigestPipeline. Page text is used in that run only and is never stored; scoring has already read the feed text. A failure is logged and the run carries on with feed text. Rejected: reading pages inside a FetchSource, which would store page text in stored_articles and fetch the whole buffer before the cap; and wrapping the ArticleRepository, which would hide outbound fetches behind a store read. The owner chose this call site on 2026-11-03. build_deps wires it for `cyris run` (dry runs included) and for a `regenerate` rerun, which summarizes again and would otherwise rebuild an issue from teasers; rerender and the comparison commands get none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
§2 gains the direct-injection row, §3.2 describes what is read and refused, §5 grades the three bounds, and §8 plus CLAUDE.md name the one shape by which IO may be called from the service layer: an optional injected callable whose IO lives in adapters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review |
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Reviewed the diff and the call site in What I checked:
Minor points, not blocking:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 51cc61c2ee
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| short = [i for i, a in enumerate(articles) if teaser(a)] | ||
| if not short: | ||
| return list(articles) | ||
|
|
There was a problem hiding this comment.
Enforce response bounds before following redirects
When a teaser URL returns a redirect with a large or compressed body, follow_redirects=True lets HTTPX read and decompress that entire intermediate response before client.stream() yields the final response. Consequently, neither the encoding rejection nor MAX_PAGE_BYTES protects redirect bodies. I reproduced this with the installed HTTPX using a gzipped 302 response: its 6 MiB decoded body was already buffered in response.history before application code received the final response. A larger payload can exhaust the run container's memory instead of falling back to feed text. Follow redirects manually, closing intermediate responses without reading their bodies, or otherwise enforce the bounds before HTTPX buffers them.
Useful? React with 👍 / 👎.
httpx's follow_redirects reads every redirect's body whole, and decompressed, before the final response reaches the caller, so neither the identity-encoding refusal nor MAX_PAGE_BYTES bounded it: a gzipped 302 could inflate past the cap in memory (Codex review on #152). _last_hop follows the redirects itself, closing each hop without reading it, within the client's max_redirects; the request hook still refuses a private host on every hop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review |
|
Reviewed the diff and the surrounding What I checked:
Two minor notes, neither blocking:
🤖 Generated with Claude Code |
|
Codex Review: Didn't find any major issues. Chef's kiss. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Why
Some feeds give one sentence per item. On the 2026-11-03 morning issue, the pjwu.me story "別人制定的標準" was summarized from a 27-character description, while its page holds about 1300 characters. In a measurement that day, 17 of 51 feeds had a median under 500 characters of text per item. #151 made feed HTML into text; this PR fills in the text that teaser feeds never send.
What
adapters/fetch/article_page.lengthen_articlesreads the page of each RSS article whose text is shorter thansummarize_snippet_length. It extracts the body with trafilatura and keeps whichever text is longer. Any failure keeps the feed text, one page at a time.run_digestcalls it once, through an optionalDepscallable. The call comes after the cap and the vote-similarity holds, and beforeDigestPipeline. The page text is used in that run only and is never stored. Scoring has already read the feed text by then.cyris run, including dry runs, and for aregeneratererun.rerenderand the comparison commands get none.media_linksinkeywords.json:/watch,/shorts,youtu.be).Owner decisions (2026-11-03)
FetchSource, which would store page text and fetch before the cap, and a wrapper around theArticleRepository, which would hide network IO behind a store read.outbound-http-honours-environmentspec forbids.Verification
scripts/check.sh: 4834 passed on the branch tip.follow_redirectsread each redirect's body whole, decompressed, before any cap.91ea0adbfollows redirects in_last_hopand closes each hop unread; a test with a watched 302 body was red before the fix.Open, not blocking
🤖 Generated with Claude Code