Skip to content

fix(library): address CodeRabbit CRITICAL/MAJOR findings from PR #2068#2085

Closed
hognek wants to merge 6 commits into
jaylfc:devfrom
hognek:feat/library-p2-rebased-v2
Closed

fix(library): address CodeRabbit CRITICAL/MAJOR findings from PR #2068#2085
hognek wants to merge 6 commits into
jaylfc:devfrom
hognek:feat/library-p2-rebased-v2

Conversation

@hognek

@hognek hognek commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes CRITICAL and MAJOR findings from CodeRabbit review of PR #2068 (review rounds 2026-07-21T16:06Z and 2026-07-21T17:04Z).

CRITICAL

  • Stream context bug (library_pipeline.py:432-447): resp from client.stream() was accessed after its async with context exited when the redirect loop broke. Moved raise_for_status() + body consumption inside the stream context.

MAJOR

  • Reprocess failure-safety (routes/library.py:347-362): After CAS sets pending and artifacts are deleted, scheduling failure now rolls back to error (not ready) — rolling back to ready leaves a data-less item.
  • Handoff serialisation race (routes/library.py:169-210): _ingest_task now CAS-flips back to processing before the collections handoff, so reprocess sees busy state during the handoff window. Handoff now explicitly sets ready on success, error on failure.
  • yt-dlp timeout (library_pipeline.py:289): Wrapped fetch() with asyncio.wait_for(timeout=120) to prevent hung yt-dlp subprocesses from blocking the pipeline indefinitely.
  • Collection-link validation (library_collections.py:328-349): raise_for_status() on the link POST response so HTTP 401/404/500 are caught instead of silently clearing retryable state.

MINOR

  • Replaced flaky asyncio.sleep(0.5) with bounded status polling in reprocess tests
  • Removed redundant local imports of AsyncMock/MagicMock/patch in test_handoff_with_qmd
  • Fixed docstring count (6→5) in test_unauth_library_endpoints
  • Prefer next() over list comprehension indexing for thumbnail lookup

Skipped

  • Reference-only artifact (library_pipeline.py:647-669): Correct behavior — only triggers for unknown URL kinds without registered processors. url:youtube and url:web have dedicated processors and skip this path.
  • Title assertion (test_library.py:416-419): Already fixed in prior round — the assertion updated_title == "Test Video" exists.
  • WebProcessor redirect-loop/SSRF test coverage: Deferred — lower priority.

Testing

All 50 library tests pass (pytest tests/test_library.py -n auto).

Fixes findings from reviews on #2068.


Summary by Gitar

  • New processors:
    • Added YouTubeProcessor to ingest metadata, transcripts, and chapters using yt-dlp with a 120s timeout.
    • Added WebProcessor to extract readable content from HTML pages with SSRF protection and response size capping.
  • Library infrastructure:
    • Guarded LibraryStore lazy initialization with asyncio.Lock to prevent race conditions during concurrent access.
  • Improved state management:
    • Updated _ingest_task to CAS-flip status to processing before handoff to ensure reprocess ignores in-flight tasks.
  • Refactored tests:
    • Replaced flaky asyncio.sleep calls with robust status polling in test_library.py.

This will update automatically on new commits.

Summary by CodeRabbit

  • New Features

    • Added support for importing YouTube videos, including metadata, thumbnails, transcripts, and chapters.
    • Added web page importing with readable text extraction, metadata, and previews.
    • URL items now use dedicated processors when available.
  • Bug Fixes

    • Improved processing reliability by preventing duplicate work and accurately reporting ready or error states.
    • Collection linking now reports failed requests instead of silently continuing.
    • Reprocessing status updates are more consistent when scheduling fails.

hognek added 6 commits July 21, 2026 18:27
…ngestor

Add YouTubeProcessor for url:youtube items — fetches metadata, thumbnail,
transcript, and chapters via knowledge_fetchers.youtube (yt-dlp). Cheap-tier
only: no video download, just text artifacts for collection indexing.

Add WebProcessor for url:web items — fetches HTML (SSRF-guarded), extracts
readable text via readability-lxml (falls back to tag-stripping). Auto-titles
from <title> tag.

Both registered in _PROCESSORS dict; run_pipeline dispatches to them
for url:youtube and url:web kinds.

Design doc: docs/design/library-app.md sections 3-4.
Tests: 8 new tests, all 47 library tests pass.
… exceptions

- _render_item_card: escape all user-controlled values (title, kind, id,
  status) with html.escape() to prevent reflected XSS through crafted
  page titles, YouTube metadata, or user form fields.
- WebProcessor: use html.unescape() on extracted <title> text to decode
  HTML entities before storage.
- YouTubeProcessor/WebProcessor: remove bare except Exception that
  swallowed fetch failures. Let network/SSRF/yt-dlp errors propagate to
  run_pipeline which already marks items as 'error' — a failed URL fetch
  must not silently appear 'ready' with zero artifacts.

All 47 library tests pass.
…ponse-size cap

- WebProcessor: disable auto-redirects, manually validate every redirect
  hop with validate_url_or_raise() against the SSRF blocklist (same
  pattern as knowledge_ingest._download_article). Max 5 redirects.
- WebProcessor: cap response body at 10 MB, stream with aiter_bytes()
  to avoid OOM on large/hostile pages.
- Tests: update _mock_httpx_response with is_redirect=False, encoding,
  and aiter_bytes() to match the new fetch flow.

All library tests pass.
…nt TOCTOU race

Two concurrent requests that both see library_store as None could create
and init two separate LibraryStore instances, leaking connections and
racing on early writes. Add _store_init_lock (module-level asyncio.Lock)
with double-checked pattern — same approach as _config_lock in config.py.

Addresses CodeRabbit finding from PR jaylfc#2068 review cycle.
…#2068

CRITICAL: fix httpx stream context bug — resp used after async with exits
in WebProcessor redirect loop. Body consumption moved inside stream context.

MAJOR: reprocess failure-safety — roll back to error (not ready) after
artifact deletion to avoid data-less items stuck in ready state.

MAJOR: serialise reprocess against collections handoff — CAS-flip back to
processing in _ingest_task so reprocess sees busy state during handoff.

MAJOR: add asyncio.wait_for(120s) timeout on yt-dlp fetch in YouTubeProcessor.

MAJOR: validate collection-link POST response — raise_for_status() so
HTTP 401/404/500 are caught instead of silently clearing retryable state.

MINOR: replace flaky asyncio.sleep(0.5) with bounded status polling in
reprocess idempotent/409 tests.

MINOR: remove redundant local imports of AsyncMock/MagicMock/patch in
test_handoff_with_qmd; promote AsyncMock to module level.

MINOR: fix docstring/comment from 6 to 5 endpoints in test_unauth_library.

MINOR: prefer next() over list comprehension indexing for thumbnail lookup.

50/50 tests pass.
@hognek
hognek marked this pull request as ready for review July 21, 2026 18:14
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Library ingestion now supports YouTube and web URL processors, validates collection-link responses, serializes store initialization, claims ingestion atomically, and records explicit ready/error states. Tests add async dependency mocks and poll processing state transitions deterministically.

Changes

Library ingest flow

Layer / File(s) Summary
YouTube and web URL processors
tinyagentos/library_pipeline.py, tests/test_library.py
YouTube ingestion stores metadata, thumbnails, transcripts, and chapters; web ingestion validates redirects and response size, extracts readable text, and stores metadata and previews.
Pipeline references and collection linking
tinyagentos/library_pipeline.py, tinyagentos/library_collections.py, tests/test_library.py
Dedicated URL processors are routed instead of creating generic reference artifacts, and collection-link responses now enforce HTTP success.
Initialization and item state transitions
tinyagentos/routes/library.py
Store initialization is locked, ingestion uses an atomic processing claim, and successful or failed handoffs and reprocessing scheduling set explicit statuses.
Deterministic processor and lifecycle tests
tests/test_library.py
Async fetch and streaming mocks cover processor outputs, while polling replaces fixed sleeps for pipeline completion checks.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant LibraryRoute
  participant run_pipeline
  participant YouTubeProcessor
  participant WebProcessor
  participant LibraryStore
  participant CollectionHandoff

  LibraryRoute->>LibraryStore: claim item as processing
  LibraryRoute->>run_pipeline: process item
  run_pipeline->>YouTubeProcessor: route url:youtube
  YouTubeProcessor->>LibraryStore: save video artifacts and metadata
  run_pipeline->>WebProcessor: route url:web
  WebProcessor->>LibraryStore: save extracted text and metadata
  LibraryRoute->>CollectionHandoff: hand off indexed collection
  CollectionHandoff-->>LibraryRoute: link response
  LibraryRoute->>LibraryStore: set ready or error status
Loading

Possibly related PRs

  • jaylfc/taOS#2062: Related to collection handoff, indexing, and validation of the taosmd link request.
  • jaylfc/taOS#2068: Overlaps with YouTube and web processor support and its asynchronous test coverage.
  • jaylfc/taOS#2070: Overlaps with URL processor behavior, routing, artifacts, and related tests.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the PR’s goal of fixing library issues flagged in the CodeRabbit review, which is consistent with the changeset.
✨ 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.

@gitar-bot

gitar-bot Bot commented Jul 21, 2026

Copy link
Copy Markdown

Important

You are using the Gitar free plan. Upgrade to unlock code review, CI analysis, auto-apply, custom automations, and more.

Gitar

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@tinyagentos/routes/library.py`:
- Around line 181-188: Prevent the collections handoff from reacquiring items
whose processing already failed: update the status transition in the handoff
logic to exclude the "error" state, or propagate failures from run_pipeline so
the handoff is not executed after an error. Preserve the existing CAS behavior
for pending and processing items and ensure failed ingest items cannot be
overwritten as ready.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 19984ca4-b02c-496a-a2b7-57c06ee25229

📥 Commits

Reviewing files that changed from the base of the PR and between 056350a and 52c3c7a.

📒 Files selected for processing (4)
  • tests/test_library.py
  • tinyagentos/library_collections.py
  • tinyagentos/library_pipeline.py
  • tinyagentos/routes/library.py

Comment on lines +181 to +188
# Re-acquire processing state so reprocess sees us as busy during the
# collections handoff window. If we lose the CAS race, another pipeline
# already claimed the item — abort cleanly (it will do its own handoff).
if not await store.try_update_item_status(
item_id, "processing", if_not_in=("pending", "processing")
):
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Show run_pipeline to verify its except block does not re-raise.
fd -t f 'library_pipeline.py' --exec ast-grep run --pattern $'async def run_pipeline($$$):\n  $$$' --lang python {}
rg -nP -A6 'except Exception' -g '**/library_pipeline.py'

Repository: jaylfc/taOS

Length of output: 149


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the relevant files and inspect the surrounding code.
fd -a 'library.py|library_pipeline.py|store\.py|.*store.*\.py' .

printf '\n--- tinyagentos/routes/library.py outline ---\n'
ast-grep outline tinyagentos/routes/library.py --view expanded || true

printf '\n--- tinyagentos/routes/library.py lines 140-240 ---\n'
sed -n '140,240p' tinyagentos/routes/library.py

printf '\n--- search for run_pipeline definitions/usages ---\n'
rg -n "async def run_pipeline|run_pipeline\(" .

printf '\n--- files with try_update_item_status / get_item / update_item_status ---\n'
rg -n "try_update_item_status|get_item\(|update_item_status\(" tinyagentos .

Repository: jaylfc/taOS

Length of output: 22569


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- tinyagentos/library_pipeline.py lines 632-720 ---\n'
sed -n '632,720p' tinyagentos/library_pipeline.py

printf '\n--- tinyagentos/library_store.py lines 100-220 ---\n'
sed -n '100,220p' tinyagentos/library_store.py

printf '\n--- tinyagentos/routes/library.py lines 168-222 ---\n'
sed -n '168,222p' tinyagentos/routes/library.py

Repository: jaylfc/taOS

Length of output: 10413


Failed ingest items can be resurrected to ready. run_pipeline records "error" and returns, so this path still reacquires the item from error, runs the collections step, and can overwrite the failure with ready. Skip the handoff when the item is already in an error state, or have run_pipeline propagate failures.

🤖 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 `@tinyagentos/routes/library.py` around lines 181 - 188, Prevent the
collections handoff from reacquiring items whose processing already failed:
update the status transition in the handoff logic to exclude the "error" state,
or propagate failures from run_pipeline so the handoff is not executed after an
error. Preserve the existing CAS behavior for pending and processing items and
ensure failed ingest items cannot be overwritten as ready.

@jaylfc

jaylfc commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Closing this as a duplicate. Superseded by #2068, which is the canonical Library P2 PR.

Both PRs now point at the identical commit (52c3c7ab) and produce byte-identical diffs, verified by hashing both. So there is no content here that #2068 does not already have, and the CodeRabbit findings referenced in the title are addressed on that branch.

Worth naming the pattern rather than just closing, since it is the third time this shape has come up: a finding on an open PR gets folded into that PR, not into a sibling. A second PR for the same slice splits the review history, doubles the CI cost, and makes it ambiguous which one a reviewer should read. The taOS development skill covers this under "one PR per slice" and pitfall 13 (a fix and its test belong in one PR).

Nothing lost by closing this one: #2068 carries the same commits plus the earlier review thread. Continue there.

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