Skip to content

Commit 678a5b6

Browse files
devhimsclaude
andcommitted
Split CI: build/typecheck always, live YouTube tests opt-in
GitHub Actions runner IPs are in datacenter ranges that YouTube gates with their "Sign in to confirm you're not a bot" bot challenge. No InnerTube client version bypasses this — it's IP-based filtering, not client-based. So the previous CI design (run live tests on every push) was fundamentally unable to validate the library; it failed for reasons unrelated to code correctness. New layout: - ci.yml — always runs: install, build, surface-level tests, tarball check. Live tests in src/index.test.ts are gated on YOUTUBE_LIVE=1 and auto-skip when CI=true (which GitHub sets automatically). - live-check.yml — separate nightly workflow that *attempts* the live tests with continue-on-error: true and opens a tracking issue on failure. The issue template explicitly notes that GHA IP blocking is the most common cause, so spurious failures don't cause panic. Pre-release verification still happens locally on a residential IP, where the full live test suite runs by default. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 26eba68 commit 678a5b6

3 files changed

Lines changed: 98 additions & 44 deletions

File tree

.github/workflows/ci.yml

Lines changed: 10 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,15 @@ on:
44
push:
55
branches: [main, master]
66
pull_request:
7-
# Nightly canary: catches YouTube tightening client requirements before users do.
8-
# If this fails, the client versions in src/index.ts CLIENT_PROFILES probably
9-
# need bumping. Track yt-dlp's youtube extractor for known-good versions.
10-
schedule:
11-
- cron: '0 6 * * *' # 06:00 UTC daily
127
workflow_dispatch:
138

149
permissions:
1510
contents: read
16-
issues: write
1711

1812
jobs:
19-
test:
13+
# Always-run: build + type-check + surface-level tests + tarball validation.
14+
# No network calls to YouTube — works on any runner.
15+
build:
2016
runs-on: ubuntu-latest
2117
strategy:
2218
fail-fast: false
@@ -34,37 +30,12 @@ jobs:
3430

3531
- run: npm run build
3632

37-
# --retry=2 absorbs transient YouTube hiccups (rate limits, brief 5xx).
38-
# Persistent failure across all retries indicates real drift.
39-
- name: Test
40-
run: npx vitest run --retry=2
33+
# Tests are split: surface-level tests run here; live-network tests
34+
# are gated by YOUTUBE_LIVE=1 (see live-check.yml).
35+
- run: npm test
4136

42-
# Open (or comment on) a tracking issue when the *scheduled* run fails.
43-
# Run on one matrix leg only to avoid duplicate issues.
44-
- name: Report scheduled-run failure
45-
if: failure() && github.event_name == 'schedule' && matrix.node == 22
46-
env:
47-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
48-
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
37+
- name: Verify published tarball contents
4938
run: |
50-
TITLE="Nightly YouTube CI check failed — likely client-version drift"
51-
EXISTING=$(gh issue list --state open --search "$TITLE in:title" --json number --jq '.[0].number // empty')
52-
BODY=$(cat <<EOF
53-
The scheduled CI run against YouTube failed. This typically means YouTube has tightened
54-
enforcement on one of the InnerTube clients used by the library, and the \`CLIENT_PROFILES\`
55-
array in [\`src/index.ts\`](../blob/main/src/index.ts) needs new client versions.
56-
57-
**Failed run:** $RUN_URL
58-
59-
**Update procedure:**
60-
1. Check recent commits to [yt-dlp's youtube extractor](https://github.com/yt-dlp/yt-dlp/blob/master/yt_dlp/extractor/youtube/_base.py) for current \`clientVersion\` values.
61-
2. Bump \`clientVersion\` (and \`userAgent\` / \`osVersion\` if needed) in the relevant \`CLIENT_PROFILES\` entry.
62-
3. Run \`npm test\` locally to confirm.
63-
4. Release as a patch version (e.g. 1.10.x).
64-
EOF
65-
)
66-
if [ -n "$EXISTING" ]; then
67-
gh issue comment "$EXISTING" --body "Failed again: $RUN_URL"
68-
else
69-
gh issue create --title "$TITLE" --body "$BODY"
70-
fi
39+
npm pack --dry-run 2>&1 | tee pack-output.txt
40+
grep -q "dist/index.js" pack-output.txt
41+
grep -q "dist/index.d.ts" pack-output.txt

.github/workflows/live-check.yml

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
name: Live YouTube canary
2+
3+
# Best-effort nightly probe against real YouTube. NOTE: YouTube blocks most
4+
# datacenter IPs (including GitHub Actions) with bot challenges, so this
5+
# job is *expected* to fail intermittently from CI. It exists to surface
6+
# the rare days when YouTube changes something AND the GHA IP range is
7+
# briefly unblocked — in which case we want to know.
8+
#
9+
# This workflow does NOT fail the build. It opens / comments on a tracking
10+
# issue if a run fails, so maintainers can investigate.
11+
12+
on:
13+
schedule:
14+
- cron: '0 6 * * *' # 06:00 UTC daily
15+
workflow_dispatch:
16+
17+
permissions:
18+
contents: read
19+
issues: write
20+
21+
jobs:
22+
live:
23+
runs-on: ubuntu-latest
24+
continue-on-error: true
25+
steps:
26+
- uses: actions/checkout@v4
27+
- uses: actions/setup-node@v4
28+
with:
29+
node-version: 22
30+
cache: 'npm'
31+
- run: npm ci
32+
- run: npm run build
33+
34+
- name: Run live YouTube tests
35+
id: live
36+
env:
37+
YOUTUBE_LIVE: '1'
38+
run: npx vitest run --retry=2
39+
40+
- name: Open / update tracking issue on failure
41+
if: failure() && steps.live.outcome == 'failure'
42+
env:
43+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
44+
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
45+
run: |
46+
TITLE="Nightly YouTube canary failed"
47+
EXISTING=$(gh issue list --state open --search "$TITLE in:title" --json number --jq '.[0].number // empty')
48+
BODY=$(cat <<EOF
49+
The nightly live-network check against YouTube failed.
50+
51+
**Most common cause:** YouTube's bot-detection blocked the GitHub
52+
Actions runner IP (this happens routinely — datacenter IP ranges
53+
are aggressively filtered). The library itself is likely fine for
54+
end users on residential IPs.
55+
56+
**When to act:** if this issue keeps reopening for many days *and*
57+
you can reproduce the failure locally on a residential connection,
58+
then YouTube has likely changed something. The fix is to bump
59+
\`clientVersion\` values in \`CLIENT_PROFILES\` in
60+
[\`src/index.ts\`](../blob/main/src/index.ts) — track recent commits
61+
to yt-dlp's youtube extractor for known-good versions.
62+
63+
**Failed run:** $RUN_URL
64+
EOF
65+
)
66+
if [ -n "$EXISTING" ]; then
67+
gh issue comment "$EXISTING" --body "Failed again: $RUN_URL"
68+
else
69+
gh issue create --title "$TITLE" --body "$BODY"
70+
fi

src/index.test.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,22 @@ import {
66
VideoDetails,
77
} from './index';
88

9-
// Real network calls — these tests hit YouTube's InnerTube API.
10-
// They will fail offline or when YouTube changes their client-version
11-
// requirements (in which case bump CLIENT_PROFILES in src/index.ts).
9+
// Surface-level sanity tests — always run, no network.
10+
describe('public API surface', () => {
11+
test('exports the documented functions and types', () => {
12+
expect(typeof getSubtitles).toBe('function');
13+
expect(typeof getVideoDetails).toBe('function');
14+
});
15+
});
16+
17+
// Live-network integration tests. YouTube blocks most datacenter IP ranges
18+
// (including GitHub Actions, AWS, etc.) with a "Sign in to confirm you're
19+
// not a bot" challenge that no client version bypasses. So these tests are
20+
// gated behind YOUTUBE_LIVE=1 and only run by default in local development
21+
// (where the requester IP is residential).
22+
const liveTestsEnabled =
23+
!process.env.CI || process.env.YOUTUBE_LIVE === '1';
24+
const describeLive = liveTestsEnabled ? describe : describe.skip;
1225

1326
const TEST_VIDEOS = {
1427
// "I Stopped Building MCP Servers. Here's Why." — ASR English only
@@ -19,7 +32,7 @@ const TEST_VIDEOS = {
1932

2033
const NETWORK_TIMEOUT = 20_000;
2134

22-
describe('getSubtitles', () => {
35+
describeLive('getSubtitles (live)', () => {
2336
test(
2437
'extracts auto-generated English captions',
2538
async () => {
@@ -55,7 +68,7 @@ describe('getSubtitles', () => {
5568
);
5669
});
5770

58-
describe('getVideoDetails', () => {
71+
describeLive('getVideoDetails (live)', () => {
5972
let videoDetails: VideoDetails;
6073

6174
beforeAll(async () => {

0 commit comments

Comments
 (0)