Skip to content

Merge staging: Twitch VOD support, dependency bumps, bug fixes#557

Merged
biodrone merged 45 commits into
masterfrom
staging
Apr 14, 2026
Merged

Merge staging: Twitch VOD support, dependency bumps, bug fixes#557
biodrone merged 45 commits into
masterfrom
staging

Conversation

@biodrone

@biodrone biodrone commented Apr 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

New Feature: Twitch VOD Download Support

  • VOD download implementation with gRPC integration, SQLite tracking DB, config fields (vod, vod_limit), and graceful shutdown
  • Multiple bug fixes: atomic ClaimVOD, lazy DB init, RowsAffected checks, server logging on failure paths
  • Integration test coverage with dedicated test channel, stale DB cleanup, file match tightening
  • README documentation for VOD usage

Dependency Bumps (13 packages)

  • Python: urllib3, pytest, requests, pygments, curl-cffi, streamlink, yt-dlp, grpcio, wheel, protobuf
  • Node: lodash (security fix for prototype pollution in baseUnset)
  • GitHub Actions: checkout, setup-go, setup-node, codeql-action, docker/login-action, docker/setup-buildx-action, docker/setup-qemu-action, WyriHaximus/github-action-get-previous-tag

Misc

  • CodeRabbit config for staging PR reviews
  • Fix for yt-dlp .get() on format fields to avoid KeyError

Stats

  • 35 commits, 46 files changed, +2011 / -622 lines

Test plan

  • All staging PRs passed CI before merge
  • CI on this PR (software tests, Docker builds)

Summary by CodeRabbit

  • New Features

    • Twitch VOD downloads with per-channel limits, claiming/retry logic, and graceful shutdown that waits for in-flight VODs
    • gRPC support to list available VODs
  • Documentation

    • README updated with VOD CLI flags, per-channel config, and Docker persistence guidance
    • Added CHANGELOG entry for v3.6.0
  • Tests

    • New VOD DB tests, config parser test, and integration test additions for VOD workflows
  • Chores

    • CI action version bumps, dependency/version updates, and repo tooling/config additions

dependabot Bot and others added 30 commits April 13, 2026 17:37
Bumps [WyriHaximus/github-action-get-previous-tag](https://github.com/wyrihaximus/github-action-get-previous-tag) from 1 to 2.
- [Release notes](https://github.com/wyrihaximus/github-action-get-previous-tag/releases)
- [Commits](WyriHaximus/github-action-get-previous-tag@v1...v2)

---
updated-dependencies:
- dependency-name: WyriHaximus/github-action-get-previous-tag
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [docker/login-action](https://github.com/docker/login-action) from 3 to 4.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](docker/login-action@v3...v4)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 6.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](actions/setup-node@v4...v6)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](docker/setup-qemu-action@v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](docker/setup-buildx-action@v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@v3...v4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [grpcio](https://github.com/grpc/grpc) from 1.76.0 to 1.80.0.
- [Release notes](https://github.com/grpc/grpc/releases)
- [Commits](grpc/grpc@v1.76.0...v1.80.0)

---
updated-dependencies:
- dependency-name: grpcio
  dependency-version: 1.80.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5 to 6.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](actions/setup-go@v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [yt-dlp](https://github.com/yt-dlp/yt-dlp) from 2025.10.22 to 2026.3.17.
- [Release notes](https://github.com/yt-dlp/yt-dlp/releases)
- [Changelog](https://github.com/yt-dlp/yt-dlp/blob/master/Changelog.md)
- [Commits](yt-dlp/yt-dlp@2025.10.22...2026.03.17)

---
updated-dependencies:
- dependency-name: yt-dlp
  dependency-version: 2026.3.17
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [streamlink](https://github.com/streamlink/streamlink) from 7.6.0 to 8.3.0.
- [Release notes](https://github.com/streamlink/streamlink/releases)
- [Changelog](https://github.com/streamlink/streamlink/blob/master/CHANGELOG.md)
- [Commits](streamlink/streamlink@7.6.0...8.3.0)

---
updated-dependencies:
- dependency-name: streamlink
  dependency-version: 8.3.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [curl-cffi](https://github.com/lexiforest/curl_cffi) from 0.13.0 to 0.15.0.
- [Release notes](https://github.com/lexiforest/curl_cffi/releases)
- [Changelog](https://github.com/lexiforest/curl_cffi/blob/main/docs/changelog.rst)
- [Commits](lexiforest/curl_cffi@v0.13.0...v0.15.0)

---
updated-dependencies:
- dependency-name: curl-cffi
  dependency-version: 0.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore(release): update dependencies and bump version to 3.5.0

* Revert "chore(release): update dependencies and bump version to 3.5.0"

This reverts commit 847aca3.

* chore(release)🤖: v3.5.1 [skip ci] [skip ci]

* chore(release)🤖: v3.6.0 [skip ci] [skip ci]

* chore(deps): bump pygments from 2.18.0 to 2.20.0

Bumps [pygments](https://github.com/pygments/pygments) from 2.18.0 to 2.20.0.
- [Release notes](https://github.com/pygments/pygments/releases)
- [Changelog](https://github.com/pygments/pygments/blob/master/CHANGES)
- [Commits](pygments/pygments@2.18.0...2.20.0)

---
updated-dependencies:
- dependency-name: pygments
  dependency-version: 2.20.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Josh Jacobs <josh.jacobs@datasolace.com>
Co-authored-by: Conventional Changelog Action <conventional.changelog.action@github.com>
Co-authored-by: Josh J <josh@joshjacobs.net>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore(release): update dependencies and bump version to 3.5.0

* Revert "chore(release): update dependencies and bump version to 3.5.0"

This reverts commit 847aca3.

* chore(release)🤖: v3.5.1 [skip ci] [skip ci]

* chore(release)🤖: v3.6.0 [skip ci] [skip ci]

* chore(deps): bump requests from 2.32.3 to 2.33.0

Bumps [requests](https://github.com/psf/requests) from 2.32.3 to 2.33.0.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](psf/requests@v2.32.3...v2.33.0)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.33.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Josh Jacobs <josh.jacobs@datasolace.com>
Co-authored-by: Conventional Changelog Action <conventional.changelog.action@github.com>
Co-authored-by: Josh J <josh@joshjacobs.net>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore(release): update dependencies and bump version to 3.5.0

* Revert "chore(release): update dependencies and bump version to 3.5.0"

This reverts commit 847aca3.

* chore(release)🤖: v3.5.1 [skip ci] [skip ci]

* chore(release)🤖: v3.6.0 [skip ci] [skip ci]

* chore(deps): bump pytest from 8.3.3 to 9.0.3

Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.3.3 to 9.0.3.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](pytest-dev/pytest@8.3.3...9.0.3)

---
updated-dependencies:
- dependency-name: pytest
  dependency-version: 9.0.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Josh Jacobs <josh.jacobs@datasolace.com>
Co-authored-by: Conventional Changelog Action <conventional.changelog.action@github.com>
Co-authored-by: Josh J <josh@joshjacobs.net>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore(release): update dependencies and bump version to 3.5.0

* Revert "chore(release): update dependencies and bump version to 3.5.0"

This reverts commit 847aca3.

* chore(release)🤖: v3.5.1 [skip ci] [skip ci]

* chore(release)🤖: v3.6.0 [skip ci] [skip ci]

* chore(deps): bump urllib3 from 2.2.3 to 2.6.3

Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.2.3 to 2.6.3.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](urllib3/urllib3@2.2.3...2.6.3)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.6.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Josh Jacobs <josh.jacobs@datasolace.com>
Co-authored-by: Conventional Changelog Action <conventional.changelog.action@github.com>
Co-authored-by: Josh J <josh@joshjacobs.net>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…g/docker/setup-buildx-action-4' into staging
…g/WyriHaximus/github-action-get-previous-tag-2' into staging
Add ability to download past broadcasts (VODs) from Twitch channels.
Per-channel `vod: true` config toggle switches from live stream to VOD
mode, with a SQLite database tracking download state to avoid
re-downloading and handle crash recovery via stale threshold detection.

- Add GetVods gRPC RPC using yt-dlp for VOD enumeration
- Add SQLite VOD tracking with status-based lifecycle (downloading/completed/failed)
- Add downloadVOD function with stream copy and VOD-specific filenames
- Add -data, -vod-out, -vod-move flags for configurable paths
- Wire VOD branch into main tick loop with ShouldDownloadVOD gating
- Add VOD phase to integration test
- Update README and example config
VOD downloads now use a WaitGroup so the shutdown path waits for
all in-progress VOD jobs before exiting, not just live streams.
Eliminates TOCTOU race by combining the check-and-claim into a single
SQL operation that uses ON CONFLICT with a WHERE clause and checks
RowsAffected. Also fixes dropped errors in tests.
Return an error when the VOD is not found in the database instead of
silently succeeding with zero rows affected.
Only create the SQLite database when a channel with vod:true is
encountered, avoiding failures on unwritable default paths when
VOD is not configured. Handles config reloads that add VOD channels.
biodrone and others added 13 commits April 14, 2026 08:26
Removes the data directory so the SQLite DB is fresh, preventing
previously tracked VODs from causing the test to skip downloads.
Use a known high-profile channel (kaicenat) that reliably has VODs
instead of reusing the live stream channel. Overridable via VOD_CHANNEL
env var.
Bump minimum protobuf version to match generated code requirements.
Add stream_pb2*.py to CodeRabbit path filters since they are
auto-generated and should not be reviewed.
Accept in-progress downloads (>1KB in /incomplete) as proof the
pipeline works, not just completed files. Makes the test faster and
less flaky for long VODs. VOD_TIMEOUT is now configurable via env var.
Iterate a prioritized list of channels (teampgp first) and probe for
published VODs using yt-dlp before running Phase 5. Skip gracefully
if no candidates have VODs. Overridable via VOD_CHANNEL env var.
Include server logs alongside client logs when VOD download times out.
Fail the test if all VOD probes errored (connectivity issue) rather
than skipping gracefully, which is reserved for when probes succeed
but no channels have VODs.
Bumps [wheel](https://github.com/pypa/wheel) from 0.46.1 to 0.46.3.
- [Release notes](https://github.com/pypa/wheel/releases)
- [Changelog](https://github.com/pypa/wheel/blob/main/docs/news.rst)
- [Commits](pypa/wheel@0.46.1...0.46.3)

---
updated-dependencies:
- dependency-name: wheel
  dependency-version: 0.46.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Fixes prototype pollution vulnerability in baseUnset function.
@coderabbitai

coderabbitai Bot commented Apr 14, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 209097ab-3757-4ca2-913c-83fbf6d7e747

📥 Commits

Reviewing files that changed from the base of the PR and between a90bb2f and 17a7856.

📒 Files selected for processing (2)
  • download_stream.go
  • streamdl.go
✅ Files skipped from review due to trivial changes (1)
  • download_stream.go

📝 Walkthrough

Walkthrough

Adds Twitch VOD support end-to-end: new GetVods gRPC/proto, server-side yt-dlp discovery, client-side getVods/VodResult, FFmpeg-based downloadVOD with sanitized filenames, a SQLite VodDB for claiming/tracking, CLI flags and data dir handling, tests, docs, and CI/dependency bumps.

Changes

Cohort / File(s) Summary
VOD Core Implementation
download_stream.go, vod_db.go, vod_db_test.go
Added sanitizeFilename and downloadVOD (FFmpeg-based VOD downloads); introduced VodDB (SQLite) with InitVodDB, ClaimVOD, MarkVODCompleted, MarkVODFailed; added unit tests covering lifecycle, retries, staleness, and isolation.
gRPC Protocol & Client
protos/stream.proto, grpc_client.go
Added GetVods RPC and messages (VodRequest, VodInfo, VodResponse); implemented Go getVods() with timeout, error-code mapping, and VodResult struct.
Python Server Implementation
streamdl_proto_srv.py
Implemented StreamServicer.GetVods and helper get_vods() using yt_dlp to extract VOD entries; added error classification (429/404/500) and safe response construction.
Main Application Logic
streamdl.go
Added CLI flags -data, -vod-out, -vod-move; lazy VodDB init/close; VOD polling via getVods using VODLimit; claim/dispatch logic spawning downloadVOD goroutines with vodWg; adjusted live-path retry timing and graceful shutdown waiting for VOD goroutines.
Configuration & Tests
config.go, config/config.yml.example, config_reader_test.go, README.md, CHANGELOG.md
Added VOD and VODLimit fields to Streamer; updated example config and config parsing test; documented VOD flags/behavior and Docker guidance in README; added CHANGELOG v3.6.0 entry.
Entrypoint & Integration
entrypoint_client.sh, tests/integration/docker-compose.integration.yml, tests/integration/run.sh
Ensure /app/data dir exists and is chown'd; mount /app/data in integration compose; extended integration script with a VOD download phase (channel probing, timeout, validation).
Workflows & Dependencies
.github/workflows/*.yml, go.mod, pyproject.toml, .coderabbit.yaml
Bumped multiple GitHub Actions versions; updated Go indirect modules; tightened/raised Python dependencies, added grpcio-tools, bumped Python project version to 3.6.0; added .coderabbit.yaml to enable auto-review excluding proto-generated files.
Misc Docs / Changelog
CHANGELOG.md, README.md
Added v3.6.0 changelog notes and README sections for VOD usage, flags, and examples.

Sequence Diagram

sequenceDiagram
    participant App as StreamDL App
    participant GRPC as gRPC Server
    participant YtDlp as yt-dlp
    participant DB as SQLite VodDB
    participant FFmpeg as FFmpeg

    loop VOD discovery
        App->>GRPC: GetVods(site,user,limit)
        GRPC->>YtDlp: extract_info(site/user/videos)
        YtDlp-->>GRPC: vod metadata list
        GRPC-->>App: VodResult[]
    end

    loop per VOD
        App->>DB: ClaimVOD(vodID,user,site,title,staleThreshold)
        DB-->>App: claimed true/false

        alt claimed
            App->>GRPC: GetStream(videos/vodID)
            GRPC-->>App: download URL
            App->>FFmpeg: exec(download URL → outPath)
            FFmpeg-->>App: exit success/failure
            App->>DB: MarkVODCompleted / MarkVODFailed
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

dependencies

🐰 I hop with glee through code and log,
VODs now queued, each filename snug.
SQLite guards each claimed delight,
FFmpeg hums through day and night,
Fetch, save, and cheer — a rabbit's hop of delight! 🎥🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description is comprehensive and well-structured, covering new features, dependency updates, testing, and stats; however, it does not follow the required template structure with 'Fixes #' or a bullet-point 'Changes proposed' section. Reformat the description to match the repository template: start with 'Fixes #' and organize changes under a 'Changes proposed in this pull request:' section with bullet points.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: Twitch VOD support (primary feature), dependency bumps, and bug fixes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch staging

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

@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: 5

Caution

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

⚠️ Outside diff range comments (1)
tests/integration/run.sh (1)

233-382: ⚠️ Potential issue | 🟠 Major

Don't let the live-phase skip short-circuit the VOD phase.

Phase 5 is appended after the existing exit 0 at Line 119, so off-peak runs still skip the new VOD coverage entirely even though this phase has its own candidate probing. The === PASS banner at Line 233 is also premature for the same reason. Split the live and VOD phases so a live skip doesn't exit before the VOD path is exercised.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integration/run.sh` around lines 233 - 382, The script currently exits
early when the live-phase prints "=== PASS: Live stream integration test
succeeded ===" or when the live-phase skip path does exit 0, preventing the
subsequent VOD phase (the "=== VOD Download Test ===" block and its VOD_CHANNEL
probing logic using CANDIDATE_VOD_CHANNELS, ANY_PROBE_OK, and VOD_CHANNEL) from
running; remove or change that early exit so the VOD phase always runs (or only
skip the live-specific success banner), move or rename the premature "=== PASS:
Live stream integration test succeeded ===" banner so final success is printed
only after both live and VOD checks complete, and ensure the skip branch that
currently does exit 0 continues to the VOD probing logic instead of terminating
the script.
🧹 Nitpick comments (2)
vod_db_test.go (1)

160-162: Consider checking errors from ClaimVOD.

These calls discard the error with _. While the test intent is clear (checking the boolean), silently ignoring errors could mask unexpected failures.

🔧 Suggested improvement
-	d1, _ := db.ClaimVOD("111", "user1", "twitch.tv", "Title A", staleThreshold)
-	d2, _ := db.ClaimVOD("222", "user2", "twitch.tv", "Title B", staleThreshold)
-	d3, _ := db.ClaimVOD("333", "user3", "twitch.tv", "Title C", staleThreshold)
+	d1, err := db.ClaimVOD("111", "user1", "twitch.tv", "Title A", staleThreshold)
+	if err != nil {
+		t.Fatalf("ClaimVOD 111 failed: %v", err)
+	}
+	d2, err = db.ClaimVOD("222", "user2", "twitch.tv", "Title B", staleThreshold)
+	if err != nil {
+		t.Fatalf("ClaimVOD 222 failed: %v", err)
+	}
+	d3, err := db.ClaimVOD("333", "user3", "twitch.tv", "Title C", staleThreshold)
+	if err != nil {
+		t.Fatalf("ClaimVOD 333 failed: %v", err)
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vod_db_test.go` around lines 160 - 162, The test is discarding errors from
ClaimVOD calls (d1, d2, d3) which can hide failures; update the test to capture
and assert the returned error for each ClaimVOD invocation (or call
t.Fatalf/t.Errorf if err != nil) so failures surface rather than being
ignored—locate the ClaimVOD(...) calls that assign to d1/d2/d3 and add error
checks immediately after each call.
grpc_client.go (1)

26-84: Extract the shared gRPC setup before this drifts further.

This repeats the same addr/port lookup, grpc.NewClient, close, and timeout plumbing as getStream, and the two paths are already diverging on logging/error translation. A small shared helper would keep live/VOD transport behavior in sync.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grpc_client.go` around lines 26 - 84, Refactor the duplicated gRPC setup in
getVods to a shared helper (e.g., newGRPCConn or dialStreamGRPC) that
encapsulates reading STREAMDL_GRPC_ADDR/PORT, calling grpc.NewClient with
grpc.WithTransportCredentials(insecure.NewCredentials()), returning the
grpc.ClientConn (or client interface), a context with timeout (and its cancel
func) and an io.Closer-style cleanup (or ensure conn.Close is called by caller);
then update getVods (and getStream) to call this helper, use the returned ctx
for RPCs, and remove the duplicated addr/port/dial/close/timeout plumbing so
both functions share connection and timeout behavior while keeping their own
logging/error translation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CHANGELOG.md`:
- Around line 1-83: Add a concise bullet under the 3.6.0 changelog (near the New
Features or top-level summary) documenting the Twitch VOD support and the new
config/CLI surface: mention the feature name (Twitch VOD support) and list the
new config/flags (vod, vod_limit, -data, -vod-out, -vod-move) plus a short note
on what each does (e.g., enable VOD downloads, limit VOD count, data dir, VOD
output path, atomically move completed VODs). Ensure this appears alongside
other headline changes in the 3.6.0 notes so users scanning the changelog see
the functional impact.

In `@download_stream.go`:
- Around line 540-541: The code currently calls both cmd.Process.Wait() and
cmd.Wait(), which duplicate reaping the child and can cause errors; remove the
redundant call and use cmd.Wait() to both wait for and reap the process (or, if
you intentionally need low-level Process.Wait(), then remove the subsequent
cmd.Wait() and propagate/handle that error accordingly), updating any error
handling around cmd.Wait()/cmd.Process.Wait() to reflect the single wait path.

In `@streamdl.go`:
- Around line 70-74: The process is exiting with os.Exit(0) before the deferred
vodDB.Close() in main() can run; change the early termination in main() to
return instead of calling os.Exit so the defer that checks vodDB and calls
vodDB.Close() executes, and ensure the defer that wraps vodDB.Close() (the func
that checks if vodDB != nil then vodDB.Close()) remains in main() so resources
are closed on return.
- Around line 191-203: The nested retry shadows err and therefore the "thrice"
branch never sees the second retry result; in the block that calls getStream
after sleeping, replace the short declaration (url, err := getStream(...)) with
an assignment that reuses the outer err (url, err = getStream(...)) or use a new
variable name and then check that variable, then change the subsequent else-if
to inspect the second-retry error (the reused err or the new name) so that the
"rate limited thrice" branch in the getStream / downloadStream retry logic for
streamer.User can be reached and other error paths are not silently dropped;
ensure you still lock/update urls via urlsMu and call downloadStream when the
second retry succeeds.

---

Outside diff comments:
In `@tests/integration/run.sh`:
- Around line 233-382: The script currently exits early when the live-phase
prints "=== PASS: Live stream integration test succeeded ===" or when the
live-phase skip path does exit 0, preventing the subsequent VOD phase (the "===
VOD Download Test ===" block and its VOD_CHANNEL probing logic using
CANDIDATE_VOD_CHANNELS, ANY_PROBE_OK, and VOD_CHANNEL) from running; remove or
change that early exit so the VOD phase always runs (or only skip the
live-specific success banner), move or rename the premature "=== PASS: Live
stream integration test succeeded ===" banner so final success is printed only
after both live and VOD checks complete, and ensure the skip branch that
currently does exit 0 continues to the VOD probing logic instead of terminating
the script.

---

Nitpick comments:
In `@grpc_client.go`:
- Around line 26-84: Refactor the duplicated gRPC setup in getVods to a shared
helper (e.g., newGRPCConn or dialStreamGRPC) that encapsulates reading
STREAMDL_GRPC_ADDR/PORT, calling grpc.NewClient with
grpc.WithTransportCredentials(insecure.NewCredentials()), returning the
grpc.ClientConn (or client interface), a context with timeout (and its cancel
func) and an io.Closer-style cleanup (or ensure conn.Close is called by caller);
then update getVods (and getStream) to call this helper, use the returned ctx
for RPCs, and remove the duplicated addr/port/dial/close/timeout plumbing so
both functions share connection and timeout behavior while keeping their own
logging/error translation.

In `@vod_db_test.go`:
- Around line 160-162: The test is discarding errors from ClaimVOD calls (d1,
d2, d3) which can hide failures; update the test to capture and assert the
returned error for each ClaimVOD invocation (or call t.Fatalf/t.Errorf if err !=
nil) so failures surface rather than being ignored—locate the ClaimVOD(...)
calls that assign to d1/d2/d3 and add error checks immediately after each call.
🪄 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

Run ID: 482d0821-9fab-49a2-9fd3-2ae2d57f31bd

📥 Commits

Reviewing files that changed from the base of the PR and between 9f841c0 and e07ee5d.

⛔ Files ignored due to path filters (25)
  • go.sum is excluded by !**/*.sum
  • node_modules/.package-lock.json is excluded by !**/node_modules/**
  • node_modules/lodash/README.md is excluded by !**/node_modules/**
  • node_modules/lodash/_baseOrderBy.js is excluded by !**/node_modules/**
  • node_modules/lodash/_baseUnset.js is excluded by !**/node_modules/**
  • node_modules/lodash/_setCacheHas.js is excluded by !**/node_modules/**
  • node_modules/lodash/compact.js is excluded by !**/node_modules/**
  • node_modules/lodash/core.js is excluded by !**/node_modules/**
  • node_modules/lodash/core.min.js is excluded by !**/node_modules/**, !**/*.min.js
  • node_modules/lodash/flake.lock is excluded by !**/node_modules/**, !**/*.lock
  • node_modules/lodash/flake.nix is excluded by !**/node_modules/**
  • node_modules/lodash/fromPairs.js is excluded by !**/node_modules/**
  • node_modules/lodash/lodash.js is excluded by !**/node_modules/**
  • node_modules/lodash/lodash.min.js is excluded by !**/node_modules/**, !**/*.min.js
  • node_modules/lodash/package.json is excluded by !**/node_modules/**
  • node_modules/lodash/random.js is excluded by !**/node_modules/**
  • node_modules/lodash/release.md is excluded by !**/node_modules/**
  • node_modules/lodash/template.js is excluded by !**/node_modules/**
  • node_modules/lodash/templateSettings.js is excluded by !**/node_modules/**
  • package-lock.json is excluded by !**/package-lock.json
  • protos/stream.pb.go is excluded by !**/*.pb.go
  • protos/stream_grpc.pb.go is excluded by !**/*.pb.go
  • stream_pb2.py is excluded by !stream_pb2.py
  • stream_pb2_grpc.py is excluded by !stream_pb2_grpc.py
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (22)
  • .coderabbit.yaml
  • .github/workflows/deploy_master.yml
  • .github/workflows/deploy_staging.yml
  • .github/workflows/test.yml
  • .github/workflows/update-actions.yml
  • CHANGELOG.md
  • README.md
  • config.go
  • config/config.yml.example
  • config_reader_test.go
  • download_stream.go
  • entrypoint_client.sh
  • go.mod
  • grpc_client.go
  • protos/stream.proto
  • pyproject.toml
  • streamdl.go
  • streamdl_proto_srv.py
  • tests/integration/docker-compose.integration.yml
  • tests/integration/run.sh
  • vod_db.go
  • vod_db_test.go

Comment thread CHANGELOG.md
Comment on lines +1 to +83
## [3.6.0](https://github.com/dangeroustech/StreamDL/compare/v3.5.1...v3.6.0) (2026-04-13)


### 🏭 Build

* **deps:** bump golang.org/x/net from 0.37.0 to 0.38.0 ([dfe5251](https://github.com/dangeroustech/StreamDL/commit/dfe5251a7feb8d90eab675a173bbf6ea12ca2085))


### 🎉 New Features

* add unit tests for config reader and improve error handling ([9b9601f](https://github.com/dangeroustech/StreamDL/commit/9b9601f41bab1f368598a018663899ab735ce95d))
* add unit tests for moveFile function and enhance cross-device handling ([2046aaa](https://github.com/dangeroustech/StreamDL/commit/2046aaaaf618439e65ac3692002a00f53917e099))
* enhance downloadStream function with retry logic and network resilience ([8947e7f](https://github.com/dangeroustech/StreamDL/commit/8947e7f1c2c62af3f5faa266c33fec4fa669f951))


### 📃 Refactor

* enhance entrypoint scripts to handle user/group creation and permissions based on root status ([7d2cf50](https://github.com/dangeroustech/StreamDL/commit/7d2cf505f34516b0625818411f3851cf45e0ce3e))
* enhance error handling and logging in downloadStream, moveFile, and gRPC connection management ([e4f66f5](https://github.com/dangeroustech/StreamDL/commit/e4f66f55788c11fb84a18e42d80a47dcb0be3b53))
* improve code readability and consistency in configuration and file handling tests ([30d79a5](https://github.com/dangeroustech/StreamDL/commit/30d79a558f9a18ff733e23f16194ed7901afd66b))
* improve entrypoint scripts with debug information and adjust permissions for .pdm-build directory ([fbfbff2](https://github.com/dangeroustech/StreamDL/commit/fbfbff299e2bcf3ab52f73d117296a9c5bcec24d))
* improve logging format and streamline code readability in streamdl_proto_srv.py ([d45c340](https://github.com/dangeroustech/StreamDL/commit/d45c340036484bdf0ed262e4331d0d1ceb047333))
* simplify entrypoint scripts by removing debug output and consolidating user/group creation steps ([f67994f](https://github.com/dangeroustech/StreamDL/commit/f67994f5b81802d7934e8471e2d45c782fdd144b))
* streamline downloadStream function and enhance logging ([4f70983](https://github.com/dangeroustech/StreamDL/commit/4f70983afddd132efd0e296025e580585ae2af79))
* streamline entrypoint script by removing redundant ownership changes and clarifying permissions for .venv ([88bcbee](https://github.com/dangeroustech/StreamDL/commit/88bcbee921d6e4d7e53cc867115741d8f7f14e9d))


### 🧪 Tests

* add end-to-end integration test for live stream download ([8f817b8](https://github.com/dangeroustech/StreamDL/commit/8f817b89d81e5034ee8d3d8873949c6d1b35fb93))


### 📚 Documentation

* add docstrings to Python server functions and classes ([98eea44](https://github.com/dangeroustech/StreamDL/commit/98eea44884464491245b29a308351f452b60e507))
* add FFmpeg resilience settings to README ([8a28341](https://github.com/dangeroustech/StreamDL/commit/8a283414f5b871fc2cf44dbe90230322f6c33f5f))


### ✍ Chore

* add --sarif option to Snyk scan parameters in deploy workflows ([627c6b3](https://github.com/dangeroustech/StreamDL/commit/627c6b31fe1aae39f5ce4cb55b905b555e272fe7))
* add category parameter for Snyk SARIF file in deploy workflows ([71d9730](https://github.com/dangeroustech/StreamDL/commit/71d973012182ec1509300d88f1e5118bf23d0f01))
* add docs/ to gitignore ([9ad1ade](https://github.com/dangeroustech/StreamDL/commit/9ad1adedad4b315eda746cf5ad8ecbcd52592f4f))
* add go.work file for Go module management ([8aa3642](https://github.com/dangeroustech/StreamDL/commit/8aa3642ddde6e8a79925f1cb505129106f300458))
* add go.work.sum file for dependency management ([a14c088](https://github.com/dangeroustech/StreamDL/commit/a14c088c8e30cb6dc99965a1ee0aa76d0ffa9119))
* add health checks and curl installation in Dockerfiles, implement health server in streamdl_proto_srv.py ([ba5199a](https://github.com/dangeroustech/StreamDL/commit/ba5199a478d8ddfb6be4f27c1ccd88edb4286462))
* **ci:** update action versions, Go, Python, and Node versions ([fc86f54](https://github.com/dangeroustech/StreamDL/commit/fc86f5496da0905d9b16d350ad7234b735da8d46))
* implement non-root user and directory permissions in Dockerfiles ([ab7fb75](https://github.com/dangeroustech/StreamDL/commit/ab7fb750d81fcf17d566c2206da30f197e2ef772))
* permissions debug ([624be49](https://github.com/dangeroustech/StreamDL/commit/624be491182092d47e8a83ab902f36f8dbaaa8a6))
* pin su-exec version in Dockerfile.client to ensure compatibility ([2879e47](https://github.com/dangeroustech/StreamDL/commit/2879e474cd5bc43b5007312cb63cca40b3bab2ed))
* remove exclusion of /usr/local/go/** from Snyk scan parameters in deploy workflows ([edd43a3](https://github.com/dangeroustech/StreamDL/commit/edd43a3dcf3c77f30e8fde9f078aeb216adca53e))
* remove go.work and go.work.sum files as they are no longer needed ([1a1c85d](https://github.com/dangeroustech/StreamDL/commit/1a1c85df5b15be988924aba68ccb32e044e7508c))
* rename Docker Scan to Snyk Scan and update scan parameters in deploy_staging.yml ([4e38833](https://github.com/dangeroustech/StreamDL/commit/4e38833ac66e1d275bcdbb0f038c8c2a1153f839))
* update category parameter for Snyk SARIF file in deploy workflows to include event name ([27c0b55](https://github.com/dangeroustech/StreamDL/commit/27c0b55a907dbac49f9a2cfc15d41270940f3b2d))
* update category parameter for Snyk SARIF file in deploy workflows to remove redundant prefixes ([1dafbcd](https://github.com/dangeroustech/StreamDL/commit/1dafbcdbaacfe25f6a276490ceb89517f0fed5aa))
* update ffmpeg version in Dockerfile.client from 7.1.1 to 8.0 ([9ac837d](https://github.com/dangeroustech/StreamDL/commit/9ac837de6f18e3226674e9d2bd5149097c95fa17))
* update Go version and dependencies in Dockerfile and go.mod ([8cd053e](https://github.com/dangeroustech/StreamDL/commit/8cd053e0de91f7355e152c6859271ce8eeff2dfe))
* update Snyk scan parameters in deploy workflows to include app vulnerabilities and exclude specific paths ([bc00136](https://github.com/dangeroustech/StreamDL/commit/bc001368ad67ce6f8eca6626cc99a02176f23937))


### 🐛 Bug Fixes

* --twitch--disable-ads is now the default ([c645c01](https://github.com/dangeroustech/StreamDL/commit/c645c0194e7acf6265b8f95d03cfca1a51f7f31c))
* add sync.RWMutex for urls map, protect delete in downloadStream ([f2c8567](https://github.com/dangeroustech/StreamDL/commit/f2c8567aba37d432bc346e4e962ba4b81009aa43))
* address CodeRabbit PR [#507](https://github.com/dangeroustech/StreamDL/issues/507) review items [#4](https://github.com/dangeroustech/StreamDL/issues/4),7-12 ([d96852d](https://github.com/dangeroustech/StreamDL/commit/d96852d325660391ff7a22cc28854206571718f2))
* address remaining CodeRabbit PR [#507](https://github.com/dangeroustech/StreamDL/issues/507) review items [#13](https://github.com/dangeroustech/StreamDL/issues/13)-18 ([5269d40](https://github.com/dangeroustech/StreamDL/commit/5269d40ebb1b95d92ea42db7bda44eb8009f5c40)), closes [#13-18](https://github.com/dangeroustech/StreamDL/issues/13-18)
* address second round of CodeRabbit review comments ([ab2db99](https://github.com/dangeroustech/StreamDL/commit/ab2db99df40b7d3f481f5e16240e14b76d316793))
* **ci:** drop Python 3.14 from test matrix (lxml lacks 3.14 wheels) ([1bd5ae1](https://github.com/dangeroustech/StreamDL/commit/1bd5ae1c3e413f652fac7f12da342d921048c221))
* **ci:** exclude validator's own grep line from action version check ([e314026](https://github.com/dangeroustech/StreamDL/commit/e314026deb66e7c0123671ba4a001d6f6c9211bf))
* **ci:** pin Snyk action to v1, lower severity threshold, quote GITHUB_STEP_SUMMARY ([52aa178](https://github.com/dangeroustech/StreamDL/commit/52aa178e16f2d47442c5f1bdeb477be2c463edc3))
* **ci:** use setup-uv@v7 (v8 major tag not yet available) ([03412e6](https://github.com/dangeroustech/StreamDL/commit/03412e6855d61414927d3e3dd61d6c8151946a17))
* **deps:** pin go-jose/v4 to v4.1.4 to resolve CVE-2026-34986 ([35ab99a](https://github.com/dangeroustech/StreamDL/commit/35ab99a3488fc21383f5748b69089b1aec6c4dc7))
* **deps:** update grpc and transitive deps to resolve Snyk vulnerabilities ([e31c62a](https://github.com/dangeroustech/StreamDL/commit/e31c62a92a7a19046695a03500404c798186374d))
* ensure user cleanup in downloadStream goroutine ([d9998f8](https://github.com/dangeroustech/StreamDL/commit/d9998f8e00a157817ed12499f2e393983630e2d8))
* increase default FFMPEG reconnect delay from 5 to 15 seconds ([047e290](https://github.com/dangeroustech/StreamDL/commit/047e29072be2ed4d0f601da716fe0989058f7792))
* protect urls map reads in ticker loop with urlsMu RLock ([a2b8955](https://github.com/dangeroustech/StreamDL/commit/a2b8955eae068c3f7d68260d6cdda1c0884f5f15))
* protect urls map writes in ticker loop with urlsMu ([b36a499](https://github.com/dangeroustech/StreamDL/commit/b36a499a255f9768b008e1446aa793ce5761bd3a))
* update curl package version in Dockerfile.server ([31f74d5](https://github.com/dangeroustech/StreamDL/commit/31f74d5692c14c1cb98a6be7d771f4a01405b4cd))
* update curl version in Dockerfile.client from 8.14.1-r1 to 8.14.1-r2 ([296b3cb](https://github.com/dangeroustech/StreamDL/commit/296b3cb9915d798c339030ff24d60e8a5c9e3117))
* update gRPC client connection method and increase timeout ([e9cdc75](https://github.com/dangeroustech/StreamDL/commit/e9cdc75cbc966c0a0916de65851accfb9d0e95f7))
* update ownership and permissions for app directory and .venv in entrypoint script ([2fdc5db](https://github.com/dangeroustech/StreamDL/commit/2fdc5dba973dcf84c128eb796c5861f73b00e92e))
* yt_dlp error handling bug that caused some plugins to always fail ([d816e98](https://github.com/dangeroustech/StreamDL/commit/d816e986ea4d601d9b4f84854de6c1132622e24a))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Document the VOD feature in the 3.6.0 notes.

The new entry reads mostly like dependency/test churn, but this release's headline change is Twitch VOD support and its new config/CLI surface. Without a bullet for vod, vod_limit, -data, -vod-out, and -vod-move, users scanning the changelog won't discover the biggest functional change in 3.6.0.

🧰 Tools
🪛 markdownlint-cli2 (0.22.0)

[warning] 1-1: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CHANGELOG.md` around lines 1 - 83, Add a concise bullet under the 3.6.0
changelog (near the New Features or top-level summary) documenting the Twitch
VOD support and the new config/CLI surface: mention the feature name (Twitch VOD
support) and list the new config/flags (vod, vod_limit, -data, -vod-out,
-vod-move) plus a short note on what each does (e.g., enable VOD downloads,
limit VOD count, data dir, VOD output path, atomically move completed VODs).
Ensure this appears alongside other headline changes in the 3.6.0 notes so users
scanning the changelog see the functional impact.

Comment thread download_stream.go Outdated
Comment thread streamdl.go
Comment thread streamdl.go
Comment thread streamdl.go
- Remove response channel from downloadVOD to prevent blocking on
  the live-stream-only drain loop (VODs use vodWg for shutdown)
- Fix shadowed err in rate-limit retry so the "thrice" branch is
  reachable
- Replace double Wait() with single cmd.Wait() after SIGINT
- Use return instead of os.Exit(0) so deferred vodDB.Close() runs

@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: 2

♻️ Duplicate comments (1)
download_stream.go (1)

449-450: ⚠️ Potential issue | 🔴 Critical

Eliminate double call to cmd.Wait() which causes a race condition.

The code calls cmd.Wait() twice concurrently—once in a goroutine at line 526 and again in the shutdown branch at line 536. Go's os/exec.Cmd.Wait() cannot be called more than once or concurrently from multiple goroutines. The second call will fail with "exec: Wait was already called" or cause a data race, and the control-listener goroutine never exits after a natural completion.

Consolidate to a single cmd.Wait() call by removing the unnecessary sigint channel and control-listener goroutine, then listen for <-control directly in the select. In the shutdown case, read from <-naturalFinish instead of calling cmd.Wait() again, and call MarkVODFailed() immediately to allow a restart to reclaim the VOD right away.

Suggested fix
-	sigint := make(chan bool)
-
 	// Always ensure base directories have correct permissions first
 	if err := createDirWithUmask(outLoc); err != nil {
 		log.Errorf("Failed to create output directory %s: %v", outLoc, err)
@@
-	// Single control listener
-	go func() {
-		for {
-			_, more := <-control
-			if !more {
-				sigint <- true
-				return
-			}
-		}
-	}()
-
 	buf := &bytes.Buffer{}
 	cmd := fluentffmpeg.
@@
 	select {
-	case <-sigint:
+	case <-control:
 		log.Tracef("Sending SIGINT to VOD %s process", vod.ID)
 		if err := cmd.Process.Signal(syscall.SIGINT); err != nil {
 			log.Errorf("Failed to send SIGINT to VOD %s: %v", vod.ID, err)
 		}
-		// Use only cmd.Wait() which handles process reaping internally
-		if err := cmd.Wait(); err != nil {
+		if err := <-naturalFinish; err != nil {
 			log.Tracef("VOD %s process exited after SIGINT: %v", vod.ID, err)
 		}
-		// Interrupted — leave as 'downloading'; stale threshold will handle retry
+		if vodDB != nil {
+			if err := vodDB.MarkVODFailed(vod.ID); err != nil {
+				log.Errorf("Failed to mark VOD %s as failed: %v", vod.ID, err)
+			}
+		}
 		return
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@download_stream.go` around lines 449 - 450, The code currently calls
cmd.Wait() from two places causing races; remove the control-listener goroutine
and the sigint channel, stop calling cmd.Wait() twice, and centralize waiting on
the process through the existing naturalFinish channel: have the control branch
in the select read from the control channel directly (instead of signaling
sigint), and in the shutdown handling read the error from <-naturalFinish (do
not call cmd.Wait() again) and call MarkVODFailed() immediately so the VOD lock
can be reclaimed; update any logic referencing sigint or the removed goroutine
to use naturalFinish and control and ensure only the goroutine that sends to
naturalFinish invokes cmd.Wait() once.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@download_stream.go`:
- Around line 452-470: The setup block that creates directories
(createDirWithUmask) can return early while the VOD is already claimed
(outLoc/moveLoc/subfolder/user), so mirror the cmd.Start() error-path cleanup:
before each early return on directory/permission errors call the same
VOD-release/cleanup function used in the cmd.Start() failure handling (the
function invoked in that branch to release the VOD claim) so the database row
isn't left in `downloading` state; locate the cmd.Start() failure branch to copy
the exact release call and insert it before each return in this setup block.

In `@streamdl.go`:
- Around line 154-159: ClaimVOD moved the VOD into the in-progress/downloading
state before calling getStream, so if getStream(site.Site, "videos/"+vod.ID,
streamer.Quality) returns an error you must release that claim so the VOD can be
retried; on getStream error call the counterpart that reverts the claim (the
function that unclaims/releases a VOD—e.g., ReleaseVOD or the existing "release"
function used elsewhere for ClaimVOD) with the vod ID, handle/log any error from
that release call, then continue without leaving the row stuck in downloading;
ensure this change is made in the same block where getStream is called (around
resolvedURL, err := getStream(...)) and that no downloader goroutine is started
when releasing the claim.

---

Duplicate comments:
In `@download_stream.go`:
- Around line 449-450: The code currently calls cmd.Wait() from two places
causing races; remove the control-listener goroutine and the sigint channel,
stop calling cmd.Wait() twice, and centralize waiting on the process through the
existing naturalFinish channel: have the control branch in the select read from
the control channel directly (instead of signaling sigint), and in the shutdown
handling read the error from <-naturalFinish (do not call cmd.Wait() again) and
call MarkVODFailed() immediately so the VOD lock can be reclaimed; update any
logic referencing sigint or the removed goroutine to use naturalFinish and
control and ensure only the goroutine that sends to naturalFinish invokes
cmd.Wait() once.
🪄 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

Run ID: 0e3c8962-3488-411f-83ea-8a30f2ba6683

📥 Commits

Reviewing files that changed from the base of the PR and between e07ee5d and a90bb2f.

📒 Files selected for processing (2)
  • download_stream.go
  • streamdl.go

Comment thread download_stream.go
Comment thread streamdl.go
Mark VODs as failed when directory creation or URL resolution errors
occur after ClaimVOD, so they can be retried on the next tick instead
of staying stuck as 'downloading' until the stale threshold expires.
@biodrone biodrone merged commit 7934dc9 into master Apr 14, 2026
14 checks passed
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.

1 participant