dgtlmoon/changedetection.io · error · EmptyReply

EmptyReply

Error message

EmptyReply

What it means

After retrying page.goto up to 3 attempts, the puppeteer fetcher still received a None response object from the browser, so it raises EmptyReply with status_code None.

Source

Thrown at changedetectionio/content_fetchers/puppeteer.py:429

        response = None
        attempt=0
        while not response:
            logger.debug(f"Attempting page fetch {url} attempt {attempt}")
            asyncio.create_task(handle_frame_navigation())
            response = await self.page.goto(url, timeout=0)
            await asyncio.sleep(1 + extra_wait)
            # Check if page still exists before sending command
            if self.page and hasattr(self.page, '_client'):
                await self.page._client.send('Page.stopLoading')

            if response:
                break
            if not response:
                logger.warning("Page did not fetch! trying again!")
            if response is None and attempt>=2:
                logger.warning(f"Content Fetcher > Response object was none (as in, the response from the browser was empty, not just the content) exiting attempt {attempt}")
                raise EmptyReply(url=url, status_code=None)
            attempt+=1

        self.headers = response.headers

        try:
            if self.webdriver_js_execute_code is not None and len(self.webdriver_js_execute_code):
                await self.page.evaluate(self.webdriver_js_execute_code)
        except Exception as e:
            logger.warning("Got exception when running evaluate on custom JS code")
            logger.error(str(e))
            # This can be ok, we will try to grab what we could retrieve
            raise PageUnloadable(url=url, status_code=None, message=str(e))

        try:
            self.status_code = response.status
        except Exception as e:
            # https://github.com/dgtlmoon/changedetection.io/discussions/2122#discussioncomment-8241962
            logger.critical(f"Response from the browser/Playwright did not have a status_code! Response follows.")

View on GitHub (pinned to 5d9c7c6da7)

Solutions

  1. Verify the URL returns an HTML document, not a download
  2. Switch fetcher to Playwright (actively maintained)
  3. Check browser service logs for crashes during navigation
Defensive patterns

Strategy: retry

Try / catch

try:
    fetcher.fetch_page()
except EmptyReply as e:
    if e.status_code is None:
        retry_with_backoff(max_attempts=3)
    else:
        mark_watch_failed(e.status_code)

Prevention

When it happens

Trigger: Repeated navigation attempts where pyppeteer's response object is None — downloads, about:blank navigation, protocol mismatch, or crashed targets.

Common situations: URL starts a download; pyppeteer protocol incompatibility with newer Chrome; browser service instability.

Related errors


AI-assisted analysis of dgtlmoon/changedetection.io@5d9c7c6da7 (2026-08-27). Data as JSON: /api/errors/69ec80d718a9aff4. Report an issue: GitHub.