Skip to content

Fix search hydration crashes on bad HTML and empty domains - #199

Closed
Alien10140 wants to merge 3 commits into
jez500:mainfrom
Alien10140:fix/search-hydration-resilience
Closed

Fix search hydration crashes on bad HTML and empty domains#199
Alien10140 wants to merge 3 commits into
jez500:mainfrom
Alien10140:fix/search-hydration-resilience

Conversation

@Alien10140

@Alien10140 Alien10140 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Stop aborting search jobs when a single URL fails hydration (safe error logging; no Closure stack frames in cache)
  • Sanitize non-UTF-8 HTML before persisting url_research
  • Harden Store::domainFilter / empty domains so SearchXNG hits without a host cannot TypeError the job
  • Quiet mode for research lookups so they do not overwrite the live search progress log

Why

A single legacy page (invalid UTF-8) could:

  1. fail MySQL utf8mb4 persistence, then
  2. crash again while logging $e->getTrace() (Closures are not serializable),

leaving the UI stuck on “Analyzing …” forever because complete was never set.

Empty/null domains from some search hits caused a separate hard crash in scopeDomainFilter.

Related

Companion package PR (recommended together): jez500/Web-scraper-for-Laravel — sanitize UTF-8 at scrape time.

Test plan

  • Feature tests for hydration failure logging, UTF-8 sanitize on persist, null domains, quiet mode, domainFilter
  • CI green

Made with Cursor

Summary by CodeRabbit

  • Bug Fixes
    • Improved store domain filtering to safely handle null and empty/invalid domain inputs, returning no matches instead of erroring.
    • Prevented store lookups when a URL host is blank.
    • Refined search URL fetching to consistently build, filter, and return results.
    • Hardened search hydration/persistence so failures don’t stop the search and logs are cleaner.
    • Sanitized scraped HTML to ensure saved content is valid UTF-8.
  • New Features
    • Added “quiet” mode to suppress search-log writes.
  • Tests
    • Added coverage for domain filtering edge cases, hydration/persistence failure behavior, UTF-8 sanitization, store assignment tolerance, and quiet-mode logging.

Search jobs could die mid-run when a page returned non-UTF-8 HTML
(MySQL utf8mb4 reject) or when error logging tried to cache Closure
stack frames. Empty/null result domains also typed-errored domainFilter.

Keep the job alive: sanitize HTML on persist, log failures without
serializable traces, and harden domain matching.
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Search execution now supports quiet logging and explicit result filtering. Domain handling tolerates null or blank values, URL store lookup validates hosts, and hydrated research persistence sanitizes HTML and isolates failures. Tests cover these behaviors.

Changes

Search and domain persistence

Layer / File(s) Summary
Quiet search query execution
app/Models/UrlResearch.php, app/Services/SearchService.php, tests/Feature/Services/SearchServiceTest.php
Search queries run quietly, filter results before selecting URLs, and suppress cached log writes when quiet mode is enabled.
Domain-based store resolution
app/Models/Store.php, app/Services/SearchService.php, app/Services/ScrapeUrl.php, tests/Feature/Models/StoreTest.php, tests/Feature/Services/SearchServiceTest.php
Domain filtering accepts nullable or empty inputs; store assignment and URL lookup ignore blank domains and hosts.
Hydration logging and persistence
app/Services/SearchService.php, tests/Feature/Services/SearchServiceTest.php
Hydration traces move to warning logs, cached failures use simplified messages, HTML is sanitized to UTF-8, and persistence exceptions are caught.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers: jez500

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the main changes around search hydration hardening and empty-domain handling.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/Services/SearchService.php`:
- Around line 515-518: Update replaceLastLogEntry() in
app/Services/SearchService.php at lines 515-518 to return immediately when quiet
mode is enabled, before popping or writing the cache. Add coverage in
tests/Feature/Services/SearchServiceTest.php at lines 270-276 by seeding a log
entry, invoking replaceLastLogEntry() in quiet mode, and asserting the entry
remains unchanged.
- Around line 348-360: Update both per-result recovery catch blocks in
SearchService: the hydration handler at app/Services/SearchService.php lines
348-360 and the sanitization/persistence handler at lines 404-420. Catch
Throwable instead of Exception in each block so TypeError and other
non-exception errors are handled by the existing recovery logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ad8f6b90-482f-44c9-ba02-f090af555536

📥 Commits

Reviewing files that changed from the base of the PR and between ed901c7 and 3713813.

📒 Files selected for processing (6)
  • app/Models/Store.php
  • app/Models/UrlResearch.php
  • app/Services/ScrapeUrl.php
  • app/Services/SearchService.php
  • tests/Feature/Models/StoreTest.php
  • tests/Feature/Services/SearchServiceTest.php

Comment thread app/Services/SearchService.php Outdated
Comment thread app/Services/SearchService.php
Catch Throwable in per-result hydration/persist so TypeError cannot abort
the job. Make replaceLastLogEntry honor quiet mode before mutating the
log cache. Prefer UTF-8 repair before Windows-1252 conversion.
Avoid re-decoding mostly-UTF-8 pages as Windows-1252 when invalid bytes
are present alongside real multi-byte sequences.
@Alien10140

Copy link
Copy Markdown
Contributor Author

Addressed CodeRabbit feedback:

  • Catch Throwable in per-result hydration/persist recovery
  • replaceLastLogEntry() early-returns in quiet mode (with test)
  • UTF-8 sanitize now only applies Windows-1252 to single-byte legacy bodies; mixed UTF-8 is repaired in place

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/Services/SearchService.php (1)

405-415: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Avoid caching failed hydrations.

The call at Line 368 persists the result even when hydration failed. The supplied failure test confirms this creates a UrlResearch row; on the next run, Lines 328-335 treat that row as a valid cache hit and skip hydration, turning a transient scrape failure into a stale permanent miss.

Track hydration success and skip persistence for failed new hydrations, or mark failed rows so getUrlResearch() excludes them. Add a two-run regression test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/Services/SearchService.php` around lines 405 - 415, Update the hydration
flow in SearchService around getUrlResearch() and the
UrlResearch::updateOrCreate() call to distinguish successful hydrations from
failures, and skip caching newly hydrated results when hydration fails. Preserve
valid existing cache behavior, and add a regression test covering two runs: the
first failed hydration must create no valid cache hit, allowing the second run
to retry hydration.
🧹 Nitpick comments (1)
app/Services/SearchService.php (1)

427-450: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add mixed-encoding regression coverage.

The existing test covers only a single Windows-1252 byte. Add a case containing valid UTF-8 bytes (for example é) alongside an invalid legacy byte, asserting the UTF-8 text is preserved while the invalid byte is removed. This directly protects the new branch at Lines 441-448.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/Services/SearchService.php` around lines 427 - 450, Extend the tests for
SearchService::sanitizeUtf8 with a mixed-encoding input containing valid UTF-8
text such as é plus an invalid legacy byte. Assert the result preserves the
valid UTF-8 text while removing the invalid byte, covering the repair branch
without changing the existing single-byte Windows-1252 case.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@app/Services/SearchService.php`:
- Around line 405-415: Update the hydration flow in SearchService around
getUrlResearch() and the UrlResearch::updateOrCreate() call to distinguish
successful hydrations from failures, and skip caching newly hydrated results
when hydration fails. Preserve valid existing cache behavior, and add a
regression test covering two runs: the first failed hydration must create no
valid cache hit, allowing the second run to retry hydration.

---

Nitpick comments:
In `@app/Services/SearchService.php`:
- Around line 427-450: Extend the tests for SearchService::sanitizeUtf8 with a
mixed-encoding input containing valid UTF-8 text such as é plus an invalid
legacy byte. Assert the result preserves the valid UTF-8 text while removing the
invalid byte, covering the repair branch without changing the existing
single-byte Windows-1252 case.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 43fc2868-6905-471f-bb7c-d10d9507e935

📥 Commits

Reviewing files that changed from the base of the PR and between 78d40b1 and 9f6b5d9.

📒 Files selected for processing (2)
  • app/Services/SearchService.php
  • tests/Feature/Services/SearchServiceTest.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/Feature/Services/SearchServiceTest.php

This was referenced Aug 7, 2026
@jez500

jez500 commented Aug 8, 2026

Copy link
Copy Markdown
Owner

thanks, merged here #214 as it needed a bunch of fixes

@jez500 jez500 closed this Aug 8, 2026
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.

2 participants