# Contributor handoff — yt-dlp/yt-dlp

> Canonical page: https://codevetter.com/unpack/yt-dlp/agent-handoff

# Contributor handoff — yt-dlp/yt-dlp

Scanned commit c7fb478d21e9e59524befbe23f7801bb267fb880 (2026-09-16). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

Read CONTRIBUTING.md (developer conventions) and .NO_AI/README.md (contribution policy) first. Style is enforced by ruff + autopep8: 120-col limit, single quotes, banned `from X import Y` for many stdlib modules. Tests run via `python -m devscripts.run_tests` (core/download/<IE name>) or `make test`; download tests need network. The main traps: extractor order is load-bearing, the params dict is the config contract, `lazy_extractors.py` is a generated artifact, plugins load from user dirs by default, and the public extractor/postprocessor API surface is intentionally stable while plugin internals are not.
- Before writing any patch intended for this repo's upstream, note `.NO_AI/README.md`: the project forbids AI/LLM-authored contributions and instructs agents to refuse; CONTRIBUTING.md reiterates under 'AUTOMATED CONTRIBUTIONS (AI / LLM) POLICY'. — [.NO_AI/README.md](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/.NO_AI/README.md) [CONTRIBUTING.md#L33-55](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/CONTRIBUTING.md#L33-L55)
- Conventions to follow: CONTRIBUTING.md 'yt-dlp coding conventions' covers mandatory metafields, relaxed-but-simple `_VALID_URL` regexes, fallback patterns, quotes (single for inline, double for docstrings), and the long-lines policy; ruff enforces line-length 120 and bans many `from <stdlib> import` forms. — [CONTRIBUTING.md#L319-684](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/CONTRIBUTING.md#L319-L684) [pyproject.toml#L258-409](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/pyproject.toml#L258-L409)
- Add a new site: create `yt_dlp/extractor/<site>.py` with a `*IE` class (`_VALID_URL`, `_TESTS`, `_real_extract`), register the import in `_extractors.py`, then run `python -m devscripts.run_tests <Site>`; CONTRIBUTING.md documents this flow. — [CONTRIBUTING.md#L220-248](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/CONTRIBUTING.md#L220-L248) [yt_dlp/extractor/_extractors.py](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/_extractors.py) [devscripts/run_tests.py#L56-59](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/devscripts/run_tests.py#L56-L59)
- Test commands: `make test` (pytest -Werror + ruff + autopep8), `make offlinetest` / `devscripts.run_tests core` for no-network runs, `devscripts.run_tests download` for live-site tests, `devscripts.run_tests <Name>` for a single extractor. — [Makefile#L79-88](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/Makefile#L79-L88) [devscripts/run_tests.py#L39-82](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/devscripts/run_tests.py#L39-L82)
- Trap — extractor order is semantic: the first `suitable()` match wins, YouTube classes are hoisted first and `GenericIE` ('.*') must stay last; reordering `_extractors.py` imports changes routing behavior. — [yt_dlp/extractor/extractors.py#L25-31](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/extractors.py#L25-L31) [yt_dlp/extractor/__init__.py#L17-29](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/__init__.py#L17-L29) [yt_dlp/extractor/generic.py#L53-56](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/generic.py#L53-L56)
- Trap — `lazy_extractors.py` is a generated build artifact (not committed); dev runs use the eager `_extractors` path, and `YTDLP_NO_LAZY_EXTRACTORS` forces eager loading in built binaries. — [yt_dlp/extractor/extractors.py#L7-18](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/extractors.py#L7-L18) [Makefile#L166-170](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/Makefile#L166-L170) [pyproject.toml#L162-173](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/pyproject.toml#L162-L173)
- Trap — plugins are on by default: `plugin_dirs=['default']` searches user config dirs and PYTHONPATH, so local `yt_dlp_plugins` packages can shadow behavior during debugging; use `YTDLP_NO_PLUGINS` or `--no-plugin-dirs` to isolate. — [yt_dlp/globals.py#L24](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/globals.py#L24) [yt_dlp/plugins.py#L194-198](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/plugins.py#L194-L198) [yt_dlp/options.py#L456-458](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/options.py#L456-L458)
- Config contract: CLI options are defined in `options.py` `create_parser`, mapped into the `ydl_opts` params dict in `parse_options` (`__init__.py`), and consumed everywhere via `self.params.get(...)`; new options need edits in both places plus README regeneration via `make README.md`. — [yt_dlp/options.py#L247](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/options.py#L247) [yt_dlp/__init__.py#L742-961](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/__init__.py#L742-L961) [Makefile#L132-133](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/Makefile#L132-L133)
- Public API boundary: `yt_dlp.__all__` exports `YoutubeDL`, `gen_extractors`, `list_extractors`, `main`, `parse_options`; the pot/jsc provider READMEs explicitly mark everything outside the listed modules as internal-only. — [yt_dlp/__init__.py#L1108-1114](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/__init__.py#L1108-L1114) [yt_dlp/extractor/youtube/pot/README.md#L11-22](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/youtube/pot/README.md#L11-L22)
- Debuggability: `-v/--verbose` prints the debug header (`print_debug_header` reports version/python/env/ffmpeg), `write_debug` emits `[debug]` traces, and `debug_printtraffic` makes RequestDirector log handler selection per request. — [yt_dlp/YoutubeDL.py#L4089](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L4089) [yt_dlp/networking/common.py#L64-92](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/networking/common.py#L64-L92)
- Broken-site convention: set `_WORKING = False` on the IE (surfaced as '(CURRENTLY BROKEN)' in --list-extractors and skipped in download tests) rather than deleting the extractor. — [yt_dlp/extractor/common.py#L644-647](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/common.py#L644-L647) [yt_dlp/__init__.py#L110-111](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/__init__.py#L110-L111) [test/test_download.py#L95-97](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/test/test_download.py#L95-L97)

Every claim links to its cited files at the pinned commit. Citations identify source evidence; they do not establish runtime correctness. Independent CodeVetter scan — not reviewed by upstream maintainers.

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
