# How Agent-Reach Switched Bilibili Backends from yt-dlp to bili-cli and OpenCLI

> Agent-Reach switched Bilibili backends from yt-dlp to bili cli due to HTTP 412 errors. Learn how their three-tier fallback chain probes external binaries to ensure continued access.

- Repository: [Pnant/Agent-Reach](https://github.com/Panniantong/Agent-Reach)
- Tags: migration-guide
- Published: 2026-07-13

---

**Agent-Reach deprecated yt-dlp in favor of bili-cli and OpenCLI after Bilibili's risk-control system began returning HTTP 412 errors, implementing a three-tier fallback chain that probes external binaries in order until one reports "ok".**

Agent-Reach provides a dedicated `BilibiliChannel` for extracting content from the Chinese video platform. The channel implementation in [`agent_reach/channels/bilibili.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/bilibili.py) manages backend switching logic that replaced the unreliable yt-dlp integration with purpose-built tools, ensuring stable access to video streams and metadata even as platform restrictions evolve.

## Why yt-dlp Was Removed from the Bilibili Channel

The original implementation relied on **yt-dlp** to fetch video streams from Bilibili. Bilibili's risk-control system started returning **HTTP 412** (Precondition Failed) for every yt-dlp request, even when executed with warmed cookies, proxies, or the latest version. As documented in the module docstring (lines 4-9 of [`agent_reach/channels/bilibili.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/bilibili.py)), the maintainers removed yt-dlp entirely and replaced it with the more reliable `bili-cli` and OpenCLI solutions.

## The Three-Tier Backend Architecture

The `BilibiliChannel` supports three mutually exclusive backends, probed in order of preference:

### bili-cli (Primary Backend)

The **bili-cli** backend uses the `bilibili-cli` binary (installable via `pipx install bilibili-cli`). This backend provides search, hot-list retrieval, video detail extraction, and audio extraction without requiring a logged-in session. Availability is determined by `probe_command("bili", ["--version"], ...)` at lines 82-94 of [`bilibili.py`](https://github.com/Panniantong/Agent-Reach/blob/main/bilibili.py). If the command is missing, the backend is ignored; otherwise, the probe result determines the status (*ok*, *warn*, or *error*).

### OpenCLI (Secondary Backend)

**OpenCLI** reuses the user's Chrome session via the OpenCLI bridge, enabling subtitle extraction and other features that require a logged-in browser. The channel queries `opencli_status()` from [`agent_reach/backends/opencli.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/backends/opencli.py) (lines 96-110 of [`bilibili.py`](https://github.com/Panniantong/Agent-Reach/blob/main/bilibili.py)). This status object reports whether the Chrome extension is installed, connected, or idle, allowing the channel to return the appropriate availability status without false negatives when the extension is merely sleeping.

### B站搜索 API (Zero-Dependency Fallback)

The **B站搜索 API** serves as a zero-dependency fallback that only supports keyword search. The `_search_api_ok()` method (lines 24-32 of [`bilibili.py`](https://github.com/Panniantong/Agent-Reach/blob/main/bilibili.py)) sends a test request to `https://api.bilibili.com/x/web-interface/search/all/v2?keyword=test&page=1` and verifies the JSON response contains `code == 0`. If the API is reachable, the backend reports *ok* and becomes available for search operations.

## How Automatic Backend Selection Works

The `check()` method implements the switching logic. It iterates through the backends in the order defined by `ordered_backends()`, which respects user configuration. The first backend reporting **"ok"** becomes the `active_backend`.

If a backend fails with an *error* but a later backend succeeds, the error messages are collected and appended to the success message. This allows users to see both the functional backend and any broken alternatives simultaneously (lines 46-72 of [`bilibili.py`](https://github.com/Panniantong/Agent-Reach/blob/main/bilibili.py)). All backend contracts are defined in the base class at [`agent_reach/channels/base.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/base.py).

## Working with the BilibiliChannel API

The following example demonstrates how the channel automatically selects and uses the appropriate backend:

```python
from agent_reach.channels.bilibili import BilibiliChannel

# Create channel and probe available backends

chan = BilibiliChannel()
status, message = chan.check()
print(f"Active backend: {chan.active_backend}")
print(message)

# Search uses whichever backend is active (bili-cli preferred)

results = chan.search("Python programming")
print(results)

# Read video URL routes to bili-cli or OpenCLI automatically

video_info = chan.read("https://www.bilibili.com/video/BV1xx411y7Zt")
print(video_info)

```

When `bili-cli` is installed, the channel invokes it internally (e.g., `bili search ...`). When only OpenCLI is available, the channel calls `opencli bilibili ...`. The probing utilities in [`agent_reach/probe.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/probe.py) safely execute these external commands and interpret their exit statuses.

## Summary

- **yt-dlp was removed** due to HTTP 412 blocking from Bilibili's risk-control system, making it unusable even with cookies and proxies.
- **Three backends exist**: bili-cli (full features, no login), OpenCLI (requires Chrome/subtitles), and a public search API (search only).
- **Automatic selection** occurs via `check()` and `ordered_backends()`, which probes each option until one reports "ok".
- **Error aggregation** ensures users see both successful connections and any failed backend warnings in the status message.
- **Key files**: [`agent_reach/channels/bilibili.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/channels/bilibili.py) (main logic), [`agent_reach/backends/opencli.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/backends/opencli.py) (Chrome integration), [`agent_reach/probe.py`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/probe.py) (command probing).

## Frequently Asked Questions

### Why did Agent-Reach stop supporting yt-dlp for Bilibili?

Bilibili's anti-bot measures began returning HTTP 412 errors for all yt-dlp requests, rendering the tool ineffective regardless of cookie warming, proxy rotation, or version updates. The maintainers removed yt-dlp support entirely and replaced it with bili-cli and OpenCLI, which use different request patterns that currently bypass these restrictions.

### What is the difference between bili-cli and OpenCLI backends?

**bili-cli** is a standalone Python package that provides video search, hot-lists, and audio extraction without requiring a Bilibili login. **OpenCLI** bridges to your Chrome browser to reuse an authenticated session, which is necessary for subtitle extraction and accessing restricted content. bili-cli is preferred for anonymous access, while OpenCLI is required for features needing authentication.

### How does the automatic fallback to the search API work?

If neither `bili-cli` nor OpenCLI is installed on the system, the `BilibiliChannel` automatically falls back to the **B站搜索 API**. This public endpoint requires zero installation but only supports keyword search functionality. The `_search_api_ok()` method verifies API reachability before marking this backend as available, ensuring the channel always has at least minimal search capability.

### Do I need to configure which backend to use manually?

No. The channel automatically probes available backends in the order defined by `ordered_backends()` when you call `check()`. However, you can influence the selection by installing the preferred tools—install `bilibili-cli` via pipx for the best experience, or add the OpenCLI Chrome extension if you need subtitle support. The channel respects this installation order unless explicitly configured otherwise.