Seth Claude Sonnet 5 commited on
Commit
e35d2a3
·
1 Parent(s): 63e197a

Surface real website-crawl failures instead of silently reporting fake success

Browse files

Same bug class as the earlier Drive fix: if the homepage itself couldn't be
reached or came back as a bot-challenge/CAPTCHA interstitial (which we
already confirmed ezofis.com's SiteGround WAF does to automated clients),
crawl_website silently returned an empty list, and reindex_website reported
"success, 0 pages crawled" with no indication anything was wrong.

crawl_website now fetches the homepage first, outside the tolerant BFS loop,
and raises a clear RuntimeError if it's unreachable or is a bot-challenge
page. Individual sub-page failures deeper in the crawl are still tolerated
(a normal, acceptable partial result) — only a broken homepage fetch is
treated as a real failure, surfaced via reindex_website's existing error
handling into the Chat Agent status log.

Verified: a simulated WAF-block (the exact SiteGround captcha page observed
from ezofis.com) now marks the log status "error" with an actionable
message; a normal successful crawl still indexes correctly (regression
check).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Files changed (1) hide show
  1. backend/app/chat_agent_crawler.py +31 -4
backend/app/chat_agent_crawler.py CHANGED
@@ -221,27 +221,54 @@ def crawl_website(base_url: str, max_pages: int = MAX_PAGES) -> List[Dict[str, s
221
  so pages that aren't well-linked from nav still get discovered, then supplements via BFS
222
  link-following from every fetched page.
223
  Returns list of {"url": ..., "title": ..., "markdown": ...}.
 
 
 
 
224
  """
225
  base_url = (base_url or "").strip()
226
  if not base_url.startswith(("http://", "https://")):
227
  base_url = "https://" + base_url
228
  parsed_root = urlparse(base_url)
229
  if not parsed_root.netloc:
230
- return []
231
  root_host = parsed_root.netloc
232
 
233
  robots = _load_robots(base_url)
234
 
235
- sitemap_urls = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  try:
237
  sitemap_urls = _discover_sitemap_urls(base_url, root_host)
238
  except Exception as e:
239
  logger.warning("Sitemap discovery failed for %s: %s", base_url, e)
240
 
241
- visited: Set[str] = set()
242
- queue: List[str] = [base_url] + [u for u in sitemap_urls if u != base_url]
243
  results: List[Dict[str, str]] = []
244
 
 
 
 
 
 
 
 
245
  while queue and len(results) < max_pages:
246
  url = queue.pop(0)
247
  url, _ = urldefrag(url)
 
221
  so pages that aren't well-linked from nav still get discovered, then supplements via BFS
222
  link-following from every fetched page.
223
  Returns list of {"url": ..., "title": ..., "markdown": ...}.
224
+
225
+ Raises RuntimeError if the homepage itself can't be reached or comes back as a bot-challenge
226
+ page — a caller treating that the same as "0 real pages found" would misreport a broken crawl
227
+ (e.g. blocked by a WAF) as a successful index of an empty site.
228
  """
229
  base_url = (base_url or "").strip()
230
  if not base_url.startswith(("http://", "https://")):
231
  base_url = "https://" + base_url
232
  parsed_root = urlparse(base_url)
233
  if not parsed_root.netloc:
234
+ raise RuntimeError(f"'{base_url}' is not a valid URL")
235
  root_host = parsed_root.netloc
236
 
237
  robots = _load_robots(base_url)
238
 
239
+ # Fetch the homepage first, outside the tolerant loop below — if this specific fetch fails
240
+ # or is a bot-challenge page, the crawl is fundamentally broken, not just "site has 0 pages".
241
+ home_resp = _fetch(base_url)
242
+ if home_resp is None:
243
+ raise RuntimeError(
244
+ f"Could not reach {base_url} (network error, or a non-2xx/non-HTML response after retries)."
245
+ )
246
+ home_title, home_text, home_links = _extract_title_text_links(home_resp.text, base_url)
247
+ if _looks_like_bot_challenge(home_text):
248
+ raise RuntimeError(
249
+ f"{base_url} appears to be behind bot/CAPTCHA protection (Cloudflare, SiteGround, Sucuri, "
250
+ "etc.) that blocks automated crawling — it returned a bot-challenge page instead of real "
251
+ "content. Try allowlisting this server, or add this content via the Google Drive "
252
+ "knowledge base instead."
253
+ )
254
+
255
+ sitemap_urls: List[str] = []
256
  try:
257
  sitemap_urls = _discover_sitemap_urls(base_url, root_host)
258
  except Exception as e:
259
  logger.warning("Sitemap discovery failed for %s: %s", base_url, e)
260
 
261
+ visited: Set[str] = {base_url}
262
+ queue: List[str] = [u for u in sitemap_urls if u != base_url]
263
  results: List[Dict[str, str]] = []
264
 
265
+ if len(home_text) >= 60:
266
+ markdown = f"# {home_title or base_url}\n\nSource: {base_url}\n\n{home_text[:20000]}"
267
+ results.append({"url": base_url, "title": home_title or base_url, "markdown": markdown})
268
+ for link in home_links:
269
+ if link not in visited:
270
+ queue.append(link)
271
+
272
  while queue and len(results) < max_pages:
273
  url = queue.pop(0)
274
  url, _ = urldefrag(url)