Skip to content

Add docstrings to live-runner and media SDK modules#48

Open
rickstaa wants to merge 2 commits into
ja/live-runnerfrom
add-docstrings
Open

Add docstrings to live-runner and media SDK modules#48
rickstaa wants to merge 2 commits into
ja/live-runnerfrom
add-docstrings

Conversation

@rickstaa

@rickstaa rickstaa commented Jul 20, 2026

Copy link
Copy Markdown
Member

Summary

Adds docstrings across the SDK's public API so consumers installing from PyPI get useful help() and IDE hovers:

  • live_runner — registration, session, trickle-channel, proxy, and call APIs
  • media_decode — decoded frame types
  • media_output / media_publish — output/publish classes and stats snapshots
  • selectionrunner_selector, reserve_session, and the selection cursors
  • errors — HTTP and selection-rejection error types

Docstring conventions

PEP 257 + Google style, adapted for statically-typed Python:

  • Types stay in the annotationsArgs: / Attributes: describe parameters and fields but never restate their types; the annotation is the single source of truth.
  • Google sectionsArgs: / Returns: / Raises: on functions; Attributes: on classes and dataclasses; constructor Args: live on __init__, and only where a class is constructed directly (not for exceptions or factory-built types).
  • Summary on the opening line — the one-line summary starts immediately after """ (PEP 257), never on the following line.
  • Exception classes describe what they represent, not the context in which they're raised (Google §3.8.4) — e.g. "A non-success HTTP response", not "Raised when …".
  • Code references use double backticks ( name ) — reStructuredText inline literal; renders as code in IDEs and help() with no docs-tooling dependency.

Docstrings only — no behavior changes.

@rickstaa
rickstaa requested a review from j0sh as a code owner July 20, 2026 13:36
Comment thread src/livepeer_gateway/channel_reader.py Dismissed
Comment thread src/livepeer_gateway/live_runner.py Dismissed
Comment thread src/livepeer_gateway/live_runner.py Dismissed
Comment thread src/livepeer_gateway/media_output.py Dismissed
Comment thread src/livepeer_gateway/channel_reader.py Dismissed
Comment thread src/livepeer_gateway/live_runner.py Dismissed
Comment thread src/livepeer_gateway/media_output.py Dismissed
Document the public API with Google-style docstrings across live_runner,
media_decode, media_output, media_publish, selection, and errors, covering
Attributes, Args, Returns, and Raises for the exported classes and functions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3ed3e4bf-9249-4b15-914a-b0bb6861b021

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

Live runner platform

Layer / File(s) Summary
HTTP, discovery, and payment foundations
src/livepeer_gateway/http.py, src/livepeer_gateway/discovery.py, src/livepeer_gateway/errors.py, src/livepeer_gateway/byoc.py, src/livepeer_gateway/remote_signer.py
Adds shared HTTP helpers, structured errors, orchestrator and runner discovery, BYOC capability payloads, async caching, and signer payment sessions.
Runner registration and session selection
src/livepeer_gateway/live_runner.py, src/livepeer_gateway/selection.py, src/livepeer_gateway/scope.py
Adds runner registration, heartbeats, session reservation, trickle/proxy operations, payment-aware calls, GPU detection, candidate retries, and runner-backed scope startup.
Media callbacks and channel readers
src/livepeer_gateway/channel_reader.py, src/livepeer_gateway/media_output.py, src/livepeer_gateway/lv2v.py, src/livepeer_gateway/media_decode.py, src/livepeer_gateway/media_publish.py
Adds callback consumers, callback lifecycle handling, incremental JSONL decoding, LV2V callback forwarding, and expanded media API documentation.
Runnable runner demonstrations
examples/echo/*, examples/ping-pong/*, examples/text/*
Adds echo video transformation, ping-pong WebSocket, and text streaming runners with clients, configuration, sample content, and usage documentation.
Public API and compatibility wiring
src/livepeer_gateway/__init__.py, src/livepeer_gateway/orchestrator.py, src/livepeer_gateway/orch_info.py, examples/get_orchestrator_info.py, pyproject.toml
Expands package exports, preserves synchronous orchestrator aliases, validates signer material decoding, updates example imports, and requires Python 3.12.

Estimated code review effort: 5 (Critical) | ~90 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: j0sh

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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 docstring-focused portion of the PR and correctly references live-runner/media SDK modules, though it omits other added examples and code changes.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add-docstrings

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.

@rickstaa
rickstaa changed the base branch from main to ja/live-runner July 20, 2026 13:58
@rickstaa
rickstaa marked this pull request as draft July 20, 2026 14:02

@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

🧹 Nitpick comments (4)
src/livepeer_gateway/scope.py (1)

82-91: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add the return type annotation for consistency. This helper returns LiveVideoToVideo; annotating it keeps the type-from-annotations convention (the PR relies on annotations rather than docstring types) and helps static checkers.

♻️ Proposed change
     orch_url: Optional[Sequence[str] | str],
     timeout: float,
-):
+) -> LiveVideoToVideo:
🤖 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 `@src/livepeer_gateway/scope.py` around lines 82 - 91, Add the return type
annotation to the _start_scope_with_runner function, specifying that it returns
LiveVideoToVideo, while preserving its existing parameters and behavior.
src/livepeer_gateway/async_cache.py (1)

17-29: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Async cache does not dedupe in-flight calls and cannot cache None.

Two robustness gaps for a decorator whose stated purpose is memoizing signer network calls:

  • No in-flight dedupe: because await func(...) yields, concurrent callers with the same key all miss and each perform the underlying network fetch (cache stampede). Caching the pending awaitable/task instead of the resolved value would coalesce concurrent callers.
  • if cached is not None treats a legitimately cached None as a miss, so None results are never memoized. A sentinel avoids this. Currently latent (get_signer_info returns a non-None SignerMaterial), but fragile if reused.
🤖 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 `@src/livepeer_gateway/async_cache.py` around lines 17 - 29, Update the async
cache wrapper to deduplicate concurrent calls by storing a shared pending
task/awaitable for each key before yielding to func, then replace it with the
resolved value while preserving the existing LRU eviction behavior. Use a
distinct missing-value sentinel in place of cached is not None so legitimately
cached None results are returned as hits. Anchor both changes in wrapper and
ensure failures remove the pending entry rather than caching the exception.
examples/echo/client.py (1)

78-91: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Unguarded /update call can abort the whole publish.

If post_json (Line 85) raises (e.g., transient network error), the exception propagates out of _publish_video, aborting the entire video publish rather than just skipping one blur update.

🛡️ Proposed fix
                 ):
-                    await post_json(f"{app_url.rstrip('/')}/update", {"mode": "blur", "radius": blur_radius})
+                    try:
+                        await post_json(f"{app_url.rstrip('/')}/update", {"mode": "blur", "radius": blur_radius})
+                    except Exception:
+                        _log("blur update failed; continuing")
                     if blur_radius == MAX_BLUR_RADIUS:
🤖 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 `@examples/echo/client.py` around lines 78 - 91, Update the blur-update loop in
_publish_video around the post_json call so a transient exception from the
/update request is caught and does not propagate to abort video publishing. Skip
the failed blur update while preserving the existing blur radius and
next_update_pts_time progression.
examples/text/runner.py (1)

21-21: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Resolve story.txt relative to the script, not the CWD.

Both handlers open "story.txt" via a bare relative path, which only works if the process is launched with examples/text/ as the working directory. Other examples in this stack (e.g. echo's README) invoke scripts with full paths from the repo root, so this convention is fragile if a reader applies that pattern here.

♻️ Proposed fix
+from pathlib import Path
+
+STORY_PATH = Path(__file__).parent / "story.txt"
+
 async def _handle_sse(request: web.Request) -> web.StreamResponse:
     ...
-    with open("story.txt", encoding="utf-8", errors="replace") as lines:
+    with open(STORY_PATH, encoding="utf-8", errors="replace") as lines:

(apply the same substitution at the _handle_text open call)

Also applies to: 41-41

🤖 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 `@examples/text/runner.py` at line 21, Update both story-file open calls in the
handlers, including `_handle_text`, to build the path relative to `runner.py`
rather than relying on the current working directory. Reuse the script’s
directory path for both opens while preserving the existing encoding and error
handling.
🤖 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 `@examples/echo/README.md`:
- Line 32: Update the command in the README to invoke client.py with the
examples/echo/ path prefix, matching the earlier runner.py and client.py
commands while preserving all existing arguments and piping.
- Line 19: Update the documented runner command in README.md to use the plain
HTTP orchestrator URL, matching the go-livepeer startup configuration and runner
default; retain the existing host, port, and secret arguments.

In `@examples/echo/runner.py`:
- Around line 128-170: Protect the active-session check and initialization in
_handle_echo with a shared asyncio lock so concurrent requests cannot both pass
the state guard while create_trickle_channels yields. Acquire the lock before
checking state and hold it through creation of the channels, output, publisher,
and assignment to state; preserve the existing conflict response for a different
session_id and the existing response for the active session.

In `@examples/text/README.md`:
- Line 3: Correct the product name typo in the README description by replacing
“Livpeeer” with “Livepeer” while preserving the rest of the text.

In `@src/livepeer_gateway/remote_signer.py`:
- Around line 271-274: Update the header construction in the asynchronous
payment POST flow to normalize optional payment.seg_creds to an empty string
when it is None, matching PaymentSession.send_payment, while leaving the
Livepeer-Payment header unchanged.

---

Nitpick comments:
In `@examples/echo/client.py`:
- Around line 78-91: Update the blur-update loop in _publish_video around the
post_json call so a transient exception from the /update request is caught and
does not propagate to abort video publishing. Skip the failed blur update while
preserving the existing blur radius and next_update_pts_time progression.

In `@examples/text/runner.py`:
- Line 21: Update both story-file open calls in the handlers, including
`_handle_text`, to build the path relative to `runner.py` rather than relying on
the current working directory. Reuse the script’s directory path for both opens
while preserving the existing encoding and error handling.

In `@src/livepeer_gateway/async_cache.py`:
- Around line 17-29: Update the async cache wrapper to deduplicate concurrent
calls by storing a shared pending task/awaitable for each key before yielding to
func, then replace it with the resolved value while preserving the existing LRU
eviction behavior. Use a distinct missing-value sentinel in place of cached is
not None so legitimately cached None results are returned as hits. Anchor both
changes in wrapper and ensure failures remove the pending entry rather than
caching the exception.

In `@src/livepeer_gateway/scope.py`:
- Around line 82-91: Add the return type annotation to the
_start_scope_with_runner function, specifying that it returns LiveVideoToVideo,
while preserving its existing parameters and behavior.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5391aa42-6489-4368-b7a3-c45726c063e0

📥 Commits

Reviewing files that changed from the base of the PR and between cc31b56 and 4313f1f.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (31)
  • examples/echo/README.md
  • examples/echo/client.py
  • examples/echo/runner.py
  • examples/get_orchestrator_info.py
  • examples/ping-pong/README.md
  • examples/ping-pong/client.py
  • examples/ping-pong/runner.py
  • examples/text/README.md
  • examples/text/go-livepeer.conf
  • examples/text/runner.py
  • examples/text/runners.json
  • examples/text/story.txt
  • pyproject.toml
  • src/livepeer_gateway/__init__.py
  • src/livepeer_gateway/async_cache.py
  • src/livepeer_gateway/byoc.py
  • src/livepeer_gateway/capabilities.py
  • src/livepeer_gateway/channel_reader.py
  • src/livepeer_gateway/discovery.py
  • src/livepeer_gateway/errors.py
  • src/livepeer_gateway/http.py
  • src/livepeer_gateway/live_runner.py
  • src/livepeer_gateway/lv2v.py
  • src/livepeer_gateway/media_decode.py
  • src/livepeer_gateway/media_output.py
  • src/livepeer_gateway/media_publish.py
  • src/livepeer_gateway/orch_info.py
  • src/livepeer_gateway/orchestrator.py
  • src/livepeer_gateway/remote_signer.py
  • src/livepeer_gateway/scope.py
  • src/livepeer_gateway/selection.py

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 5

🧹 Nitpick comments (4)
src/livepeer_gateway/scope.py (1)

82-91: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add the return type annotation for consistency. This helper returns LiveVideoToVideo; annotating it keeps the type-from-annotations convention (the PR relies on annotations rather than docstring types) and helps static checkers.

♻️ Proposed change
     orch_url: Optional[Sequence[str] | str],
     timeout: float,
-):
+) -> LiveVideoToVideo:
🤖 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 `@src/livepeer_gateway/scope.py` around lines 82 - 91, Add the return type
annotation to the _start_scope_with_runner function, specifying that it returns
LiveVideoToVideo, while preserving its existing parameters and behavior.
src/livepeer_gateway/async_cache.py (1)

17-29: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Async cache does not dedupe in-flight calls and cannot cache None.

Two robustness gaps for a decorator whose stated purpose is memoizing signer network calls:

  • No in-flight dedupe: because await func(...) yields, concurrent callers with the same key all miss and each perform the underlying network fetch (cache stampede). Caching the pending awaitable/task instead of the resolved value would coalesce concurrent callers.
  • if cached is not None treats a legitimately cached None as a miss, so None results are never memoized. A sentinel avoids this. Currently latent (get_signer_info returns a non-None SignerMaterial), but fragile if reused.
🤖 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 `@src/livepeer_gateway/async_cache.py` around lines 17 - 29, Update the async
cache wrapper to deduplicate concurrent calls by storing a shared pending
task/awaitable for each key before yielding to func, then replace it with the
resolved value while preserving the existing LRU eviction behavior. Use a
distinct missing-value sentinel in place of cached is not None so legitimately
cached None results are returned as hits. Anchor both changes in wrapper and
ensure failures remove the pending entry rather than caching the exception.
examples/echo/client.py (1)

78-91: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Unguarded /update call can abort the whole publish.

If post_json (Line 85) raises (e.g., transient network error), the exception propagates out of _publish_video, aborting the entire video publish rather than just skipping one blur update.

🛡️ Proposed fix
                 ):
-                    await post_json(f"{app_url.rstrip('/')}/update", {"mode": "blur", "radius": blur_radius})
+                    try:
+                        await post_json(f"{app_url.rstrip('/')}/update", {"mode": "blur", "radius": blur_radius})
+                    except Exception:
+                        _log("blur update failed; continuing")
                     if blur_radius == MAX_BLUR_RADIUS:
🤖 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 `@examples/echo/client.py` around lines 78 - 91, Update the blur-update loop in
_publish_video around the post_json call so a transient exception from the
/update request is caught and does not propagate to abort video publishing. Skip
the failed blur update while preserving the existing blur radius and
next_update_pts_time progression.
examples/text/runner.py (1)

21-21: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Resolve story.txt relative to the script, not the CWD.

Both handlers open "story.txt" via a bare relative path, which only works if the process is launched with examples/text/ as the working directory. Other examples in this stack (e.g. echo's README) invoke scripts with full paths from the repo root, so this convention is fragile if a reader applies that pattern here.

♻️ Proposed fix
+from pathlib import Path
+
+STORY_PATH = Path(__file__).parent / "story.txt"
+
 async def _handle_sse(request: web.Request) -> web.StreamResponse:
     ...
-    with open("story.txt", encoding="utf-8", errors="replace") as lines:
+    with open(STORY_PATH, encoding="utf-8", errors="replace") as lines:

(apply the same substitution at the _handle_text open call)

Also applies to: 41-41

🤖 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 `@examples/text/runner.py` at line 21, Update both story-file open calls in the
handlers, including `_handle_text`, to build the path relative to `runner.py`
rather than relying on the current working directory. Reuse the script’s
directory path for both opens while preserving the existing encoding and error
handling.
🤖 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 `@examples/echo/README.md`:
- Line 32: Update the command in the README to invoke client.py with the
examples/echo/ path prefix, matching the earlier runner.py and client.py
commands while preserving all existing arguments and piping.
- Line 19: Update the documented runner command in README.md to use the plain
HTTP orchestrator URL, matching the go-livepeer startup configuration and runner
default; retain the existing host, port, and secret arguments.

In `@examples/echo/runner.py`:
- Around line 128-170: Protect the active-session check and initialization in
_handle_echo with a shared asyncio lock so concurrent requests cannot both pass
the state guard while create_trickle_channels yields. Acquire the lock before
checking state and hold it through creation of the channels, output, publisher,
and assignment to state; preserve the existing conflict response for a different
session_id and the existing response for the active session.

In `@examples/text/README.md`:
- Line 3: Correct the product name typo in the README description by replacing
“Livpeeer” with “Livepeer” while preserving the rest of the text.

In `@src/livepeer_gateway/remote_signer.py`:
- Around line 271-274: Update the header construction in the asynchronous
payment POST flow to normalize optional payment.seg_creds to an empty string
when it is None, matching PaymentSession.send_payment, while leaving the
Livepeer-Payment header unchanged.

---

Nitpick comments:
In `@examples/echo/client.py`:
- Around line 78-91: Update the blur-update loop in _publish_video around the
post_json call so a transient exception from the /update request is caught and
does not propagate to abort video publishing. Skip the failed blur update while
preserving the existing blur radius and next_update_pts_time progression.

In `@examples/text/runner.py`:
- Line 21: Update both story-file open calls in the handlers, including
`_handle_text`, to build the path relative to `runner.py` rather than relying on
the current working directory. Reuse the script’s directory path for both opens
while preserving the existing encoding and error handling.

In `@src/livepeer_gateway/async_cache.py`:
- Around line 17-29: Update the async cache wrapper to deduplicate concurrent
calls by storing a shared pending task/awaitable for each key before yielding to
func, then replace it with the resolved value while preserving the existing LRU
eviction behavior. Use a distinct missing-value sentinel in place of cached is
not None so legitimately cached None results are returned as hits. Anchor both
changes in wrapper and ensure failures remove the pending entry rather than
caching the exception.

In `@src/livepeer_gateway/scope.py`:
- Around line 82-91: Add the return type annotation to the
_start_scope_with_runner function, specifying that it returns LiveVideoToVideo,
while preserving its existing parameters and behavior.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5391aa42-6489-4368-b7a3-c45726c063e0

📥 Commits

Reviewing files that changed from the base of the PR and between cc31b56 and 4313f1f.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (31)
  • examples/echo/README.md
  • examples/echo/client.py
  • examples/echo/runner.py
  • examples/get_orchestrator_info.py
  • examples/ping-pong/README.md
  • examples/ping-pong/client.py
  • examples/ping-pong/runner.py
  • examples/text/README.md
  • examples/text/go-livepeer.conf
  • examples/text/runner.py
  • examples/text/runners.json
  • examples/text/story.txt
  • pyproject.toml
  • src/livepeer_gateway/__init__.py
  • src/livepeer_gateway/async_cache.py
  • src/livepeer_gateway/byoc.py
  • src/livepeer_gateway/capabilities.py
  • src/livepeer_gateway/channel_reader.py
  • src/livepeer_gateway/discovery.py
  • src/livepeer_gateway/errors.py
  • src/livepeer_gateway/http.py
  • src/livepeer_gateway/live_runner.py
  • src/livepeer_gateway/lv2v.py
  • src/livepeer_gateway/media_decode.py
  • src/livepeer_gateway/media_output.py
  • src/livepeer_gateway/media_publish.py
  • src/livepeer_gateway/orch_info.py
  • src/livepeer_gateway/orchestrator.py
  • src/livepeer_gateway/remote_signer.py
  • src/livepeer_gateway/scope.py
  • src/livepeer_gateway/selection.py
🛑 Comments failed to post (5)
examples/echo/README.md (2)

19-19: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Protocol mismatch: use http, not https.

Nothing in this demo configures TLS; go-livepeer is started with plain HTTP (Line 13) and the runner's own default matches (http://localhost:8935). The https:// here will fail to connect.

🤖 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 `@examples/echo/README.md` at line 19, Update the documented runner command in
README.md to use the plain HTTP orchestrator URL, matching the go-livepeer
startup configuration and runner default; retain the existing host, port, and
secret arguments.

32-32: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Missing examples/echo/ path prefix.

Earlier commands in this doc use examples/echo/runner.py/examples/echo/client.py (Line 19, 25); this one drops the prefix and will fail to resolve client.py unless the reader has already cd'd into examples/echo.

🤖 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 `@examples/echo/README.md` at line 32, Update the command in the README to
invoke client.py with the examples/echo/ path prefix, matching the earlier
runner.py and client.py commands while preserving all existing arguments and
piping.
examples/echo/runner.py (1)

128-170: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Race condition on global state (TOCTOU).

The active-session guard at Line 132-134 is checked, then await create_trickle_channels(...) (Line 137-143) yields control before state is set (Line 159-166). Two concurrent /echo requests can both pass the state is None/session-id check before either assigns state, resulting in one session's output/publisher being silently overwritten and leaked (never closed).

🔒️ Proposed fix using a guard lock
 state: "EchoSession | None" = None
+_state_lock = asyncio.Lock()
...
 async def _handle_echo(request: web.Request) -> web.Response:
     global state
     session_id = _session_id(request)

-    if state is not None:
-        if state.session_id != session_id:
-            raise web.HTTPConflict(text="echo runner already has an active session")
-        return web.json_response(state.to_json())
-
-    channels = await create_trickle_channels(
-        request,
-        [
-            {"name": "in", "mime_type": "video/mp2t"},
-            {"name": "out", "mime_type": "video/mp2t"},
-        ],
-    )
+    async with _state_lock:
+        if state is not None:
+            if state.session_id != session_id:
+                raise web.HTTPConflict(text="echo runner already has an active session")
+            return web.json_response(state.to_json())
+        # Reserve the slot before doing any awaiting I/O below.
+        state = _RESERVED  # sentinel, replaced once channels are ready
+
+    channels = await create_trickle_channels(
+        request,
+        [
+            {"name": "in", "mime_type": "video/mp2t"},
+            {"name": "out", "mime_type": "video/mp2t"},
+        ],
+    )
🤖 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 `@examples/echo/runner.py` around lines 128 - 170, Protect the active-session
check and initialization in _handle_echo with a shared asyncio lock so
concurrent requests cannot both pass the state guard while
create_trickle_channels yields. Acquire the lock before checking state and hold
it through creation of the channels, output, publisher, and assignment to state;
preserve the existing conflict response for a different session_id and the
existing response for the active session.
examples/text/README.md (1)

3-3: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Typo: "Livpeeer" → "Livepeer".

🧰 Tools
🪛 LanguageTool

[grammar] ~3-~3: Ensure spelling is correct
Context: ...eam Demo Single-shot text streaming on Livpeeer with static configuration. This demo e...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🤖 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 `@examples/text/README.md` at line 3, Correct the product name typo in the
README description by replacing “Livpeeer” with “Livepeer” while preserving the
rest of the text.
src/livepeer_gateway/remote_signer.py (1)

271-274: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Livepeer-Segment header can be None, crashing the payment POST.

payment.seg_creds is optional (GetPaymentResponse(payment="", seg_creds=None) and _payment_request may return seg_creds=None), and aiohttp raises when a header value is None. The synchronous PaymentSession.send_payment guards this with p.seg_creds or ""; this async path does not.

🛡️ Proposed fix
         headers = {
             "Livepeer-Payment": payment.payment,
-            "Livepeer-Segment": payment.seg_creds,
+            "Livepeer-Segment": payment.seg_creds or "",
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        headers = {
            "Livepeer-Payment": payment.payment,
            "Livepeer-Segment": payment.seg_creds or "",
        }
🤖 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 `@src/livepeer_gateway/remote_signer.py` around lines 271 - 274, Update the
header construction in the asynchronous payment POST flow to normalize optional
payment.seg_creds to an empty string when it is None, matching
PaymentSession.send_payment, while leaving the Livepeer-Payment header
unchanged.

- PEP 257: one-line summary on the opening """ line
- Exceptions describe what they represent, not "Raised when …" (Google §3.8.4)
- Code refs use reST double-backticks; drop Sphinx :func:/:class:/:meth: roles
- Add module docstrings; document remaining public classes/methods
  (selection cursors, orchestrator_selector, media_decode workers,
  restored LiveRunnerSessionHeaders lost in an earlier rebase)
- NoOrchestratorAvailableError.__str__ now mirrors NoRunnerAvailableError

Docstrings only — no behavior changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@rickstaa
rickstaa marked this pull request as ready for review July 20, 2026 19:32
@rickstaa

Copy link
Copy Markdown
Member Author

@j0sh just some small docstring updated to ensure that when people import the package the immediately can see what the public methods do.

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