webcmd web fetch currently fails on both of the URLs we tried, in two different ways. Environment: webcmd 0.6.0 (fresh webcmd update), node v22.23.1, macOS 15 (Darwin 25.6.0), webcmd doctor fully green (daemon 0.6.0, cloak 0.4.5 connected).
1. Unhandled EPIPE crashes the CLI
webcmd web fetch --url https://news.ycombinator.com/
node:events:497
throw er; // Unhandled 'error' event
^
Error: This socket has been ended by the other party
at Socket.writeAfterFIN [as write] (node:net:576:14)
at Socket.ondata (node:internal/streams/readable:1009:22)
...
code: 'EPIPE'
}
Reproducible 3/3, before and after webcmd daemon restart. The process dies with a raw Node stack — no CliError, no error envelope, no JSON — so an agent gets nothing structured to act on.
Root cause looks like dist/src/fetch/safe-proxy.js:54-68 (the CONNECT handler). The tunnel is wired as:
upstream.once('connect', () => { ...; upstream.pipe(client); client.pipe(upstream); });
upstream.once('error', error => client.destroy(error));
There is no error listener on client at all, and the upstream listener is once, so a write to a half-closed peer emits an unhandled 'error' and takes down the process. Related: proxy.close() only calls server.close(), which does not destroy established tunnel sockets, so in-flight tunnels race the teardown at the end of webFetch.
Suggested fix: persistent client.on('error', …) / upstream.on('error', …) that destroy the peer, and track + destroy open sockets in close(). Same missing-handler issue applies to the plain HTTP path (request.pipe(upstream) at line 48).
2. FETCH_BLOCKED false positive on any Cloudflare-fronted 200
webcmd web fetch --url https://example.com
# CliError: The site blocked non-browser fetches. code: 'FETCH_BLOCKED'
example.com is a plain static 200 with no challenge:
HTTP/2 200
content-type: text/html
server: cloudflare
cf-cache-status: HIT
dist/src/fetch/classify.js builds its evidence string from headers plus body and tests /cloudflare|…/i against it, gated on status === 200 among others. Any origin behind Cloudflare emits server: cloudflare and cf-* headers on perfectly good 200 responses — which today is a large fraction of the web — so the classifier reports a block that did not happen, burns both impit retries, and then pushes the agent to fetch-browser for a page plain HTTP already returned.
Suggested fix: don't treat header values naming a CDN as challenge evidence. Match challenge markers against the body (and specific challenge headers like cf-mitigated), and drop status === 200 from the gate unless a body marker such as "Just a moment" is actually present.
Both were hit while following start.md step 3 (the Hacker News verification task) on a clean install.
webcmd web fetchcurrently fails on both of the URLs we tried, in two different ways. Environment: webcmd 0.6.0 (freshwebcmd update), node v22.23.1, macOS 15 (Darwin 25.6.0),webcmd doctorfully green (daemon 0.6.0, cloak 0.4.5 connected).1. Unhandled EPIPE crashes the CLI
Reproducible 3/3, before and after
webcmd daemon restart. The process dies with a raw Node stack — noCliError, no error envelope, no JSON — so an agent gets nothing structured to act on.Root cause looks like
dist/src/fetch/safe-proxy.js:54-68(the CONNECT handler). The tunnel is wired as:There is no
errorlistener onclientat all, and theupstreamlistener isonce, so a write to a half-closed peer emits an unhandled'error'and takes down the process. Related:proxy.close()only callsserver.close(), which does not destroy established tunnel sockets, so in-flight tunnels race the teardown at the end ofwebFetch.Suggested fix: persistent
client.on('error', …)/upstream.on('error', …)that destroy the peer, and track + destroy open sockets inclose(). Same missing-handler issue applies to the plain HTTP path (request.pipe(upstream)at line 48).2.
FETCH_BLOCKEDfalse positive on any Cloudflare-fronted 200webcmd web fetch --url https://example.com # CliError: The site blocked non-browser fetches. code: 'FETCH_BLOCKED'example.comis a plain static 200 with no challenge:dist/src/fetch/classify.jsbuilds its evidence string from headers plus body and tests/cloudflare|…/iagainst it, gated onstatus === 200among others. Any origin behind Cloudflare emitsserver: cloudflareandcf-*headers on perfectly good 200 responses — which today is a large fraction of the web — so the classifier reports a block that did not happen, burns both impit retries, and then pushes the agent tofetch-browserfor a page plain HTTP already returned.Suggested fix: don't treat header values naming a CDN as challenge evidence. Match challenge markers against the body (and specific challenge headers like
cf-mitigated), and dropstatus === 200from the gate unless a body marker such as "Just a moment" is actually present.Both were hit while following
start.mdstep 3 (the Hacker News verification task) on a clean install.