Skip to content

fix(music): stop an unreachable YTM Companion flooding the log - #266

Merged
ChuckBuilds merged 5 commits into
mainfrom
fix/ytm-reconnect-backoff
Aug 11, 2026
Merged

fix(music): stop an unreachable YTM Companion flooding the log#266
ChuckBuilds merged 5 commits into
mainfrom
fix/ytm-reconnect-backoff

Conversation

@ChuckBuilds

@ChuckBuilds ChuckBuilds commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Found while sweeping a live device for errors. This plugin alone produced 13,298 ERROR lines in 12 hours — 99.8% of everything the device logged. Finding the two real problems underneath meant filtering it out first, which is exactly how a regression goes unnoticed.

07:37:15  INFO   Attempting to connect to YTM Socket.IO server: http://10.0.10.133:9863
07:37:19  ERROR  YTM Companion Socket.IO connection failed for namespace /api/v1/realtime
07:37:19  ERROR  YTM Socket.IO connection error: Connection error
07:37:19  WARNING [ledmatrix-music] YTM failed to reconnect during poll cycle.

Roughly 24 lines a minute, ~34,000 a day, for a companion app that simply wasn't running.

Three faults stacked

No backoff. The poll loop asks for a reconnect every cycle (~2s) and the client always tried. An unreachable server was retried forever at full rate. (socketio.Client was configured with its own backoff, but the explicit connect_client() call each cycle bypasses it entirely.)

Wrong severity. "YouTube Music isn't open" is an ordinary state, not a fault — and it emitted two ERROR records per attempt, one from connect_error and one from the exception handler.

Root logger. ytm_client.py called bare logging.error(...), so records landed on the root logger. That's why they read as - root - in the journal rather than plugin.ledmatrix-music. The practical cost is that they sit outside the plugin's logger, so a user cannot lower the level to quiet themCLAUDE.md calls this out ("BasePlugin uses get_logger()… not standard logging.getLogger()").

The fix

Reconnects back off 5s → 5-minute cap. The first failure is a single WARNING naming the endpoint and what to check; repeats are debug. The client takes the plugin's logger.

Measured over the same 12-hour window, at the same 2s poll interval:

before after
ERROR lines 13,298 0
WARNING lines ~10,800 1
connection attempts 21,600 149

Reconnection behaviour is otherwise unchanged: a companion that comes back is picked up within five minutes, the backoff resets, and recovery is logged (YTM Companion reachable again after N failed attempt(s)).

Verification

  • New test_ytm_reconnect_backoff.py: 14 checks against a fake clock — attempts stay far below poll cycles, zero ERROR records, exactly one WARNING that names the endpoint and mentions the companion, delays increase and stay capped, the cap is finite so it always retries eventually, success clears the backoff, and an AST-ish source check that no bare logging.<level>( calls remain.
  • Plugin tests: 3 passed, 0 failed. Safety harness: 8 renders, exit 0. Module collisions clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5

Summary by CodeRabbit

  • New Features

    • Added automatic reconnect handling for YTM Companion, with exponential backoff capped at five minutes.
    • Improved recovery tracking so successful reconnections reset retry delays and provide clear recovery feedback.
  • Bug Fixes

    • Reduced repetitive connection-failure warnings during temporary outages.
    • Improved connection and authentication diagnostics through consistent plugin logging.
  • Tests

    • Added regression coverage for retry timing, failure handling, recovery, and logging behavior.

Found on a live device: 13,298 ERROR lines in 12 hours from this plugin
alone -- 99.8% of everything the device logged. Finding the two real
problems underneath it meant filtering this out first, which is exactly
how a regression goes unnoticed.

The cause was three faults stacked. The poll loop asked for a reconnect
every cycle (~2s) and the client always tried, with no backoff, so an
unreachable companion was retried forever at full rate. Each attempt
logged two ERROR records, though "YouTube Music is not open" is an
ordinary state rather than a fault. And they went out through bare
logging.error(), landing on the root logger -- which is why they read as
"root" in the journal, outside the plugin's logger and so beyond any
log level a user could lower.

Reconnects now back off from 5s to a 5-minute cap. The first failure
gets a single WARNING naming the endpoint and what to check; repeats are
debug. The client takes the plugin's logger.

Over the same 12 hours: one warning instead of 13,298 errors, and 149
attempts instead of 21,600. A companion that comes back is still picked
up within five minutes, and recovery is logged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@ChuckBuilds, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 52 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: eec0321c-11de-42a4-af36-2724ad8b80d8

📥 Commits

Reviewing files that changed from the base of the PR and between 3d0a99c and 0169597.

📒 Files selected for processing (4)
  • plugins.json
  • plugins/ledmatrix-music/manifest.json
  • plugins/ledmatrix-music/test_ytm_reconnect_backoff.py
  • plugins/ledmatrix-music/ytm_client.py
📝 Walkthrough

Walkthrough

The Music Player plugin adds YTM Companion reconnect backoff, client-scoped logging, manager integration, regression coverage, and version 1.3.0 release metadata.

Changes

YTM reconnect behavior

Layer / File(s) Summary
YTMClient backoff and logging
plugins/ledmatrix-music/ytm_client.py
YTMClient accepts an optional logger, tracks reconnect failures, applies capped exponential backoff, suppresses scheduled retries, and routes diagnostics through the client logger.
Manager wiring and regression coverage
plugins/ledmatrix-music/manager.py, plugins/ledmatrix-music/test_ytm_reconnect_backoff.py
The manager injects its logger and lowers repeated polling reconnect failures to debug level. The regression test verifies retry suppression, logging, recovery, and logger injection.
Plugin release metadata
plugins.json, plugins/ledmatrix-music/manifest.json
The registry and manifest update the Music Player plugin to version 1.3.0 and record the release details.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Manager
  participant YTMClient
  participant SocketIO
  participant Logger
  Manager->>YTMClient: Poll for YTM Companion connection
  YTMClient->>YTMClient: Check scheduled retry time
  YTMClient->>SocketIO: Attempt connection
  SocketIO-->>YTMClient: Return failure or success
  YTMClient->>Logger: Write scoped reconnect status
  YTMClient-->>Manager: Continue polling or synchronize after recovery
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: reducing excessive logging when YTM Companion is unreachable.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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 fix/ytm-reconnect-backoff

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.

@codacy-production

codacy-production Bot commented Aug 11, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 37 complexity

Metric Results
Complexity 37

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 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 `@plugins/ledmatrix-music/manifest.json`:
- Line 4: Use PATCH version 1.2.1 for the reconnect and logging fixes: update
the top-level version and first release record in
plugins/ledmatrix-music/manifest.json. Regenerate plugins.json with the
repository’s update_registry.py/pre-commit hook so latest_version becomes 1.2.1;
do not hand-edit the generated file.
- Line 72: Update the manifest release notes to distinguish the observed 13,298
log-line measurement from the theoretical 43,200 records produced by 21,600
attempts at two records each, and change the plugin version from 1.3.0 to 1.2.1.

In `@plugins/ledmatrix-music/ytm_client.py`:
- Around line 196-200: Update YTMClient.connect_client so the missing-token
warning is emitted only once per client instance, logging subsequent attempts at
debug level instead; add and maintain a per-instance warning state, and reset it
in load_config when a token is successfully loaded.
- Around line 103-113: Update the socket event handling around the diagnostic
extraction before the external_update_callback submission: initialize title and
author before the try block, validate the nested video value before calling
.get(), and keep malformed-payload logging at debug level. Ensure extraction
failures leave safe diagnostic defaults and never prevent
self.external_update_callback from being submitted with data.
- Around line 206-215: Disable Socket.IO’s automatic reconnection in the client
initialization by setting its reconnection option to false, leaving
connect_client() and _next_retry_at responsible for all retry scheduling. Add a
regression test covering a connection that succeeds and then disconnects,
verifying no independent reconnect loop starts.
🪄 Autofix

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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a33a018a-4907-47f8-b0bc-5871773eaa4d

📥 Commits

Reviewing files that changed from the base of the PR and between cf3dbd1 and 3d0a99c.

📒 Files selected for processing (5)
  • plugins.json
  • plugins/ledmatrix-music/manager.py
  • plugins/ledmatrix-music/manifest.json
  • plugins/ledmatrix-music/test_ytm_reconnect_backoff.py
  • plugins/ledmatrix-music/ytm_client.py

Comment thread plugins/ledmatrix-music/manifest.json Outdated
Comment thread plugins/ledmatrix-music/manifest.json Outdated
Comment thread plugins/ledmatrix-music/ytm_client.py
Comment thread plugins/ledmatrix-music/ytm_client.py
Comment thread plugins/ledmatrix-music/ytm_client.py
Two imports left over from an earlier draft, and a literal assigned to
`ytm_token`, which reads as a hardcoded credential to a scanner. It is
neither a credential nor real, so name it rather than suppress the
warning.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5
@ChuckBuilds
ChuckBuilds force-pushed the fix/ytm-reconnect-backoff branch from 4fd962c to 291a0a6 Compare August 11, 2026 12:30
claude added 3 commits August 11, 2026 12:40
…g a track

Five review findings.

The missing-token branch sits ahead of the backoff gate, so it warned on
every poll cycle -- the same flood this change removes, wearing "not
authenticated" instead of "not reachable". It now warns once per client,
debug thereafter, and rearms if a token appears and later goes away.

socketio.Client was constructed with reconnection=True and infinite
attempts, so after an established connection dropped it ran its own
1-10s retry loop that knew nothing about the backoff. Disabled;
connect_client() owns retry timing, and the poll loop already drives it.

An event carrying "video": null made the nested .get() raise inside the
diagnostic logging. The bare except swallowed it, `title` was left
unbound, and the line below raised UnboundLocalError -- so the update
callback was never submitted and the track change was silently lost.
Diagnostics must not gate delivery: the fields are assigned before the
try and nested values validated. Pre-existing, but in the path this
change is about.

Version corrected to a PATCH, 1.2.1: these are fixes, with no new
feature, option or display mode. The release note no longer mixes a
measurement with a model -- 13,298 error lines is what the device
logged; 21,600 was poll cycles, not attempts, and implied a figure that
was never observed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5
update_registry.py only moves versions forward, so it skipped the
1.3.0 -> 1.2.1 correction and left the registry disagreeing with the
manifest. Reset the file to main's state and regenerated, which takes it
1.2.0 -> 1.2.1 as a normal bump rather than hand-editing generated
output.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5
A string literal assigned to something ending in _token reads as a
hardcoded credential to a scanner. The constant already exists for
exactly this; use it rather than adding a second literal.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5
@ChuckBuilds
ChuckBuilds merged commit f766969 into main Aug 11, 2026
4 checks passed
@ChuckBuilds
ChuckBuilds deleted the fix/ytm-reconnect-backoff branch August 11, 2026 17:30
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.

2 participants