From 737506d4b20b9bbd076923664ccaa690613e5b8f Mon Sep 17 00:00:00 2001 From: rozin-not-found Date: Sun, 16 Aug 2026 14:10:04 +0800 Subject: [PATCH 1/2] Add reproduction scripts for triage --- bug_triage/repro_1278.py | 19 +++++++++++++++++++ bug_triage/repro_1367.py | 25 +++++++++++++++++++++++++ bug_triage/repro_1455.py | 32 ++++++++++++++++++++++++++++++++ bug_triage/repro_570.py | 19 +++++++++++++++++++ bug_triage/repro_699.py | 20 ++++++++++++++++++++ 5 files changed, 115 insertions(+) create mode 100644 bug_triage/repro_1278.py create mode 100644 bug_triage/repro_1367.py create mode 100644 bug_triage/repro_1455.py create mode 100644 bug_triage/repro_570.py create mode 100644 bug_triage/repro_699.py diff --git a/bug_triage/repro_1278.py b/bug_triage/repro_1278.py new file mode 100644 index 000000000..dab15c3f4 --- /dev/null +++ b/bug_triage/repro_1278.py @@ -0,0 +1,19 @@ +import asyncio +from crawl4ai import AsyncWebCrawler + +async def main(): + async with AsyncWebCrawler() as crawler: + # Target a page explicitly known for HTML tables + result = await crawler.arun(url="https://www.w3schools.com/html/html_tables.asp") + + if result.success: + if " tags are stripped from cleaned_html.") + else: + print("Fixed: Tables are preserved in cleaned_html.") + else: + print(f"Crawl failed: {result.error_message}") + +if __name__ == "__main__": + asyncio.run(main()) + \ No newline at end of file diff --git a/bug_triage/repro_1367.py b/bug_triage/repro_1367.py new file mode 100644 index 000000000..19c0d43e1 --- /dev/null +++ b/bug_triage/repro_1367.py @@ -0,0 +1,25 @@ +import asyncio +from crawl4ai import AsyncWebCrawler, BrowserConfig, CrawlerRunConfig + +async def main(): + # Bug often triggers with persistent contexts and multiple concurrent navigations + browser_cfg = BrowserConfig(headless=True, use_persistent_context=True) + crawl_config = CrawlerRunConfig(wait_until="networkidle") + + urls = ["https://example.com", "https://example.org", "https://example.net"] + + async with AsyncWebCrawler(config=browser_cfg) as crawler: + print("Running async crawls to trigger ERR_ABORTED race condition...") + results = await crawler.arun_many(urls=urls, config=crawl_config) + + reproduced = False + for res in results: + if not res.success and "ERR_ABORTED" in str(res.error_message): + print(f"Bug reproduced on {res.url}: {res.error_message}") + reproduced = True + + if not reproduced: + print("Fixed or unable to reproduce: No ERR_ABORTED errors encountered.") + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/bug_triage/repro_1455.py b/bug_triage/repro_1455.py new file mode 100644 index 000000000..0d3d80058 --- /dev/null +++ b/bug_triage/repro_1455.py @@ -0,0 +1,32 @@ +import os +import asyncio +from pydantic import BaseModel +from crawl4ai import AsyncWebCrawler, BrowserConfig, CrawlerRunConfig, CacheMode, LLMConfig +from crawl4ai import LLMExtractionStrategy + +class Product(BaseModel): + name: str + +async def main(): + llm_strategy = LLMExtractionStrategy( + llm_config=LLMConfig(provider="openai/gpt-4o-mini", api_token=os.getenv('OPENAI_API_KEY', 'dummy')), + schema=Product.schema_json(), + extraction_type="schema", + instruction="Extract products." + ) + crawl_config = CrawlerRunConfig(extraction_strategy=llm_strategy, cache_mode=CacheMode.ENABLED) + + async with AsyncWebCrawler(config=BrowserConfig(headless=True)) as crawler: + print("First run (caching)...") + await crawler.arun(url="https://example.com", config=crawl_config) + + print("Second run (cache hit)...") + result = await crawler.arun(url="https://example.com", config=crawl_config) + + if not result.extracted_content: + print("Bug reproduced: extracted_content is empty on cache hit.") + else: + print("Fixed: extracted_content populated from cache.") + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/bug_triage/repro_570.py b/bug_triage/repro_570.py new file mode 100644 index 000000000..f760886c9 --- /dev/null +++ b/bug_triage/repro_570.py @@ -0,0 +1,19 @@ +import asyncio +from crawl4ai import AsyncWebCrawler + +async def main(): + async with AsyncWebCrawler() as crawler: + result = await crawler.arun(url="https://docs.crawl4ai.com/") + + if result.success: + markdown = result.markdown or "" + if "<.>" in markdown or "<#>" in markdown: + print("Bug reproduced: Relative URLs are incorrectly formatted with brackets (e.g., <.>).") + else: + print("Fixed: No malformed relative URL brackets found in markdown.") + else: + print(f"Crawl failed: {result.error_message}") + +if __name__ == "__main__": + asyncio.run(main()) + \ No newline at end of file diff --git a/bug_triage/repro_699.py b/bug_triage/repro_699.py new file mode 100644 index 000000000..e01887428 --- /dev/null +++ b/bug_triage/repro_699.py @@ -0,0 +1,20 @@ +import asyncio +from crawl4ai import AsyncWebCrawler, CrawlerRunConfig + +async def main(): + # Enable robots.txt checking + config = CrawlerRunConfig(check_robots_txt=True) + + async with AsyncWebCrawler() as crawler: + # The path /awardsearch/advancedSearch.jsp is disallowed in nsf.gov/robots.txt + url = "https://www.nsf.gov/awardsearch/advancedSearch.jsp" + result = await crawler.arun(url=url, config=config) + + if result.success: + print(f"Bug reproduced: Successfully crawled {url} despite robots.txt disallow.") + else: + print(f"Fixed/Expected behavior: Blocked from crawling. Message: {result.error_message}") + +if __name__ == "__main__": + asyncio.run(main()) + \ No newline at end of file From 24be3dc11135e8f9b7c9a6f71b48ddf3c784166b Mon Sep 17 00:00:00 2001 From: rozin-not-found Date: Fri, 28 Aug 2026 15:35:38 +0800 Subject: [PATCH 2/2] fix: allow single-label hostnames in deep crawling strategies (#2079) --- crawl4ai/deep_crawling/bff_strategy.py | 2 -- crawl4ai/deep_crawling/bfs_strategy.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/crawl4ai/deep_crawling/bff_strategy.py b/crawl4ai/deep_crawling/bff_strategy.py index 511fde692..131322a00 100644 --- a/crawl4ai/deep_crawling/bff_strategy.py +++ b/crawl4ai/deep_crawling/bff_strategy.py @@ -83,8 +83,6 @@ async def can_process_url(self, url: str, depth: int) -> bool: raise ValueError("Missing scheme or netloc") if parsed.scheme not in ("http", "https"): raise ValueError("Invalid scheme") - if "." not in parsed.netloc: - raise ValueError("Invalid domain") except Exception as e: self.logger.warning(f"Invalid URL: {url}, error: {e}") return False diff --git a/crawl4ai/deep_crawling/bfs_strategy.py b/crawl4ai/deep_crawling/bfs_strategy.py index dfb759272..70529a92c 100644 --- a/crawl4ai/deep_crawling/bfs_strategy.py +++ b/crawl4ai/deep_crawling/bfs_strategy.py @@ -70,8 +70,6 @@ async def can_process_url(self, url: str, depth: int) -> bool: raise ValueError("Missing scheme or netloc") if parsed.scheme not in ("http", "https"): raise ValueError("Invalid scheme") - if "." not in parsed.netloc: - raise ValueError("Invalid domain") except Exception as e: self.logger.warning(f"Invalid URL: {url}, error: {e}") return False