Skip to content

Default to HTTP/1.1 transport + add --raw passthrough#6

Merged
vasylenko merged 15 commits into
mainfrom
fix/http1-fallback-on-cloudflare-block
Jul 6, 2026
Merged

Default to HTTP/1.1 transport + add --raw passthrough#6
vasylenko merged 15 commits into
mainfrom
fix/http1-fallback-on-cloudflare-block

Conversation

@vasylenko

@vasylenko vasylenko commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Two independent changes to the fetch layer, shipped together.

1. Default to HTTP/1.1 (fix)

WHY — Some CDNs (Cloudflare, seen on openai.com/products/release-notes/) 403 a valid Chrome header set over HTTP/2 but pass the identical request over HTTP/1.1 — undici's h2 handshake pattern trips their bot-scoring. h2 buys a single-shot GET nothing anyway.

HOW — undici v8 defaults to allowH2: true; pin the global dispatcher to allowH2: false. Corrected the SPEC decision that called Chrome-over-h1.1 "more suspicious than curl"; recorded impit in SPEC as the escalation path if stricter CDN tiers need genuine h2.

2. --raw / raw passthrough (feature)

WHY — Sometimes the useful output is the unprocessed body — JSON, XML, plain text, page source — not extracted article markdown.

HOW — A raw flag on fetchMarkdown returns the body verbatim, skipping Readability and the content-type gate; the MARKFETCH_MAX_BYTES cap stays. CLI --raw, MCP raw boolean. Body is UTF-8 text (binary not byte-preserved). fetchHtml became fetchBody(url, raw) — no second entry point.

vasylenko added 2 commits July 6, 2026 01:23
undici's h2 path hands a pre-connected socket to node:http2, whose first-flight frame pattern some CDNs (Cloudflare, observed on openai.com) score as a bot and 403 — even against a valid Chrome header set, while the identical request over HTTP/1.1 is let through. h2 buys nothing for single-shot GETs and every h2 server speaks h1.1, so pin allowH2:false.
Match CHANGELOG, README, and SPEC to the transport default. Correct the SPEC decision that called Chrome-over-h1.1 more suspicious than curl — observed evidence is the opposite for these edges. Record impit (browser-grade impersonation) as the escalation path for stricter CDN tiers.
@vasylenko vasylenko force-pushed the fix/http1-fallback-on-cloudflare-block branch from 2f01203 to 1f3d062 Compare July 5, 2026 23:24
@vasylenko vasylenko changed the title fix: HTTP/1.1 fallback when an edge 403s the HTTP/2 connection fix: default to HTTP/1.1 transport Jul 5, 2026
vasylenko added 2 commits July 6, 2026 01:56
Return the fetched body verbatim, skipping Readability and the content-type gate while keeping the fetch layer and the MARKFETCH_MAX_BYTES cap. Non-HTML responses (JSON, XML, plain text, source) come back as-is instead of unsupported_content_type. Threaded as a `raw` flag through fetchMarkdown (fetchHtml → fetchBody, content-type gate now conditional); CLI `--raw`, MCP `raw` boolean.
CHANGELOG entry, README (flags table, usage example, error-code note), and SPEC (Core Decision + pipeline note).
@vasylenko vasylenko changed the title fix: default to HTTP/1.1 transport Default to HTTP/1.1 transport + add --raw passthrough Jul 6, 2026
vasylenko added 11 commits July 6, 2026 08:18
- extractMarkdown helper + `content = raw ? body : extractMarkdown(...)` replaces the let/if-else and a duplicated comment
- reuse enforceTooLarge for the output cap; compute body byte-length once
- correct the dispatcher comment: undici v8 defaults allowH2:true, so allowH2:false is a deliberate override (deleting it re-enables h2), not a restated default
- test raw+savePath writes the raw body to disk; drop a subsumed assertion; fix the T6 invariant comment for raw
- "save markdown" → "save output" (raw+save writes the raw body) in CLI help, MCP tool description + savePath describe, and README
- README MCP signature gains raw?; extraction_failed row notes it's not raised with raw
- note that raw returns UTF-8 text (binary not byte-preserved) in MCP describe, README --raw row, and SPEC
Drop the "earlier revisions assumed" temporal framing; keep the why (the Chrome-over-h1.1 intuition doesn't hold for these edges).
The dispatcher comment defended its own necessity and lectured on h2
mechanics; fetchBody/extractMarkdown/fetchMarkdown comments restated the
adjacent code. Keep the why only: the undici default, the CDN
bot-scoring, the raw-mode error contract.
undici h2 internals belong in SPEC and the code comment, not in a
changelog entry. The "naive expectation" parenthetical restated evidence
the decision text already carries.
The [^\n]+\s* pairs in the empty-heading prune and the /\n+$/ pre-trim
backtrack quadratically on adversarial whitespace (Sonar S8786). Match
blank spans line-by-line and trim by index instead. Requiring \n before
the next heading also fixes real truncation: a heading with an inline
"#" ("## Step 1 # download") lost everything before it. Snapshot 10
pins that.
Descriptions load into every MCP client's context; platform path
examples, schema-rejection mechanics, and restated behavior were tax,
not signal.
@sonarqubecloud

sonarqubecloud Bot commented Jul 6, 2026

Copy link
Copy Markdown

@vasylenko vasylenko merged commit b91b724 into main Jul 6, 2026
4 checks passed
@vasylenko vasylenko deleted the fix/http1-fallback-on-cloudflare-block branch July 6, 2026 10:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant