Skip to content

release: Crawl4AI v0.9.1#2061

Merged
unclecode merged 28 commits into
mainfrom
release/v0.9.1
Jul 8, 2026
Merged

release: Crawl4AI v0.9.1#2061
unclecode merged 28 commits into
mainfrom
release/v0.9.1

Conversation

@ntohidi

@ntohidi ntohidi commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • PruningContentFilter whitelist: New preserve_classes / preserve_tags parameters to protect specific elements from density-based pruning ([Bug]: md-fit strips meaningful content metadata (usernames, attribution) not just boilerplate #1900)
  • 12 bug fixes across Docker (auth gate UI, supervisord/redis dirs, tmpfs, FastAPI cap, redis auth), browser (Windows channel crash, context snapshot leak), core (HTTP timeout unit mismatch, best-first ordering), extraction (html2text table attributes), and dependencies (lxml 6.x)
  • Housekeeping: dead code removal, sponsor logo fixes

Issues fixed

#1894, #1900, #1998, #1999, #2007, #2019, #2025, #2027, #2037, #2040, #2047, #2051

Test plan

  • Demo verification script passes (docs/releases_review/demo_v0.9.1.py) — 6/6 tests
  • pip install -e . and crawl4ai-doctor pass
  • Docker build succeeds
  • Spot-check release notes render correctly

hafezparast and others added 28 commits April 3, 2026 11:04
#1894)

page_timeout is in milliseconds (Playwright convention, default 60000ms),
but AsyncHTTPCrawlerStrategy passed it directly to aiohttp.ClientTimeout
which expects seconds. This made the default timeout 60,000 seconds
(16.7 hours) instead of 60 seconds, effectively disabling timeouts
for HTTP mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PruningContentFilter's density-based scoring strips short metadata
elements (author names, timestamps, attribution) alongside actual
boilerplate. Add opt-in whitelist params so users can protect specific
CSS classes or HTML tags from pruning.

- preserve_classes: set of CSS class names to always keep
- preserve_tags: set of HTML tag names to always keep
- Whitelisted nodes skip scoring entirely (score = always keep)
- Default: empty sets — no behavior change for existing users

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix(docker): make read-only tmpfs writable
…2037)

The AuthGateMiddleware blocked UI static pages with 401 because browsers
cannot attach Authorization headers to top-level navigation. The UI shell
serves no data, so it is safe to load without credentials.

- Add public_prefixes to AuthGateMiddleware for prefix-based path bypass
- Register /dashboard, /playground, /static as public prefixes
- Add token input bar to both playground and dashboard UIs
- Replace all bare fetch() calls with authFetch() that attaches Bearer token
- Append ?token= to monitor WebSocket URL (gate already accepts it for WS)

All API/data routes remain fail-closed behind the auth gate.

Closes #2037
fix: close browser contexts from snapshot
adaptive_crawler copy.py was an uncommitted editor artifact that ended up
tracked in the repo. It is byte-for-byte identical to adaptive_crawler.py
and is not imported anywhere.
Two unreachable functions in utils.py:

- The first `normalize_url` (plain urljoin wrapper) was silently shadowed
  by the extended `normalize_url` defined ~20 lines later. Python last-write
  wins, so the first definition was never callable.

- `normalize_url_tmp` was a hand-rolled URL joiner (string split on "/")
  with no callers outside utils.py itself. `urllib.parse.urljoin` already
  covers this correctly.
fix: remove dead code from utils and adaptive_crawler
fix(docker): authenticate rate limit Redis storage
- supervisord: write pidfile to /tmp (tmpfs) instead of read-only /app
- redis: set working dir to /var/lib/redis (tmpfs) for RDB snapshots

Fixes "could not write pidfile" and "Failed opening temp RDB file" errors
in read-only root filesystem deployment (docker-compose.yml security hardening).

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…rvisord-redis-dirs

fix(docker): use writable directories for supervisord pid and redis data
BrowserConfig.chrome_channel defaults to 'chromium'. When passed to
playwright.chromium.launch() as channel='chromium', Playwright looks
for a system Chrome installation instead of using the bundled
ms-playwright binary.  On Windows this causes Chrome to exit
immediately with code 0, resulting in:

  TargetClosedError: BrowserType.launch: Target page, context or
  browser has been closed

Skip passing channel when it equals the default 'chromium' value.
fix: skip channel chromium on Windows to prevent TargetClosedError
The auth gate fix (cbf5e5c) intentionally makes UI shell pages load
without credentials. Update the posture-gate tests to match: move
/dashboard/ and /playground/ from PROTECTED_ENDPOINTS to a new
PUBLIC_UI_PATHS list with an explicit 200 assertion.
fix(docker): let dashboard/playground load when auth gate is active
fix(docker): cap FastAPI below 0.137
Relax lxml~=5.3 (>=5.3,<6) to >=5.3,<7 so crawl4ai can co-install
with packages requiring lxml 6.x (e.g. scrapling). Crawl4ai only uses
stable lxml APIs (etree, html, fromstring, CSSSelector) unchanged in 6.x.
fix: stabilize best-first batch ordering
…-whitelist-1900

feat: add preserve_classes/preserve_tags whitelist to PruningContentFilter (#1900)
…t-mismatch-1894

fix: convert page_timeout ms to seconds for aiohttp (#1894)
Bump version to 0.9.1 across all references.
Add release notes, demo verification script, and doc updates.

Key highlights:
- PruningContentFilter preserve_classes/preserve_tags whitelist
- 12 bug fixes across Docker, browser, core, and extraction
- lxml ceiling widened to allow 6.x
@unclecode unclecode merged commit 987541e into main Jul 8, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants