---
title: "I logged every Claude WebFetch request, and here's what it asked for"
date: 2026-10-13
description: "What a logging proxy showed me about Claude Code's WebFetch: it asks for markdown first, and a second model summarizes the page."
---

I wanted to know what Claude actually does when it fetches a page, so I put Claude Code in a container behind a proxy and logged every request.

The setup is small. A Docker container runs `claude -p` with `HTTP_PROXY` and `HTTPS_PROXY` pointed at a forward proxy. The proxy writes down each request, and for chosen URLs it can quietly send them somewhere else, like a preview deploy. The agent only ever sees the original domain.

Three things showed up.

**WebFetch asks for markdown first.**

Every WebFetch in my logs went out with `Accept: text/markdown, text/html, */*`. Here's one proxy log line, trimmed, with the hostname swapped out:

```json
{
  "host": "example.com",
  "path": "/apps/slack/integrations",
  "accept": "text/markdown, text/html, */*",
  "matched": true,
  "content_type": "text/markdown; charset=utf-8"
}
```

When a page has a markdown mirror, that's what comes back. The `content_type` on the response is how I know the mirror gets read. I wrote about [why mirrors are worth having](/notes/markdown-mirrors) earlier, and this is a better reason than the one I gave.

**A second model reads the page before the one you're talking to.**

Each run's `result.json` has a `modelUsage` block listing every model that did work. Trimmed down:

```json
"modelUsage": {
  "claude-opus-5[1m]": { "outputTokens": 817, "costUSD": 0.054 },
  "claude-haiku-4-5-20251001": { "outputTokens": 370, "costUSD": 0.012 }
}
```

The first is the model I'm talking to. The second is the summarizer. It receives the page and a prompt and sends back a summary, and through WebFetch the main model never sees the raw page.

I also logged the request to Haiku. The system prompt is one generic line. Your page goes in verbatim between `---` fences, then a prompt the main model rewrote on its own, then a fixed block of rules:

```
Web page content:
---
{the page, inserted verbatim}
---

{the main model's rewritten prompt}

Provide a concise response based only on the content above. In your response:
 - Enforce a strict 125-character maximum for quotes from any source document.
 - Use quotation marks for exact language from articles; any language
   outside of the quotation should never be word-for-word the same.
```

I sampled 17 of these calls across two runs. The wrapper and rules were byte-identical every time. The only part that changed between repeats was the rewritten prompt, even when my own prompt was the same.

My read is that long, polished paragraphs won't survive into an answer unchanged. A short fact might. A product name, a command, or a specific term fits inside 125 characters and can come through as written.

**My favorite confusing result.**

To test whether the mirror mattered, I cut one down to almost nothing and expected a blank, confused answer. I got a normal summary of the page instead.

Either the model filled the gaps from what it already knew about the page, or it found another way to read it. I couldn't always tell from the final answer alone. The transcript could, which is the main thing I took from this: check how the agent read a page, not just what it said afterward.

A few caveats. This is one Claude Code build and a handful of pages. The proxy only sees fetches that happen on the local machine, so a server-side fetch would be invisible to it. And the 125-character rule is what I captured, not a documented guarantee, so it could change.

Curious what other people see when they put their own agents behind a proxy.
