Skip to content

fix(now-playing): match tracks when equipment embeds "(ft. …)" in the title#501

Merged
thewrz merged 2 commits into
mainfrom
fix/bridge-nowplaying-matching
Jun 19, 2026
Merged

fix(now-playing): match tracks when equipment embeds "(ft. …)" in the title#501
thewrz merged 2 commits into
mainfrom
fix/bridge-nowplaying-matching

Conversation

@thewrz

@thewrz thewrz commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

Why

On production event RMLNDZ, the DJ played "Get Low" and the bridge reported it correctly, but the accepted request stayed in the queue and was never marked played. The DJ also saw the reconnect-but-still-unmatched behaviour.

Ground truth from prod (event id 16):

What

The fuzzy matcher's title normalizer only stripped generic mix suffixes, leaving featured-artist credits like (ft. Ying Yang Twins) in the title. That collapsed SequenceMatcher to ~0.39, dropping the combined score below the 0.8 threshold so the request never auto-matched (and reconnecting just re-ran the same failing match). The artist side also used the raw ratio instead of the multi-artist-aware scorer.

  • normalize_track_title now strips featured-artist credits — parenthetical, bracketed, and trailing feat./ft./featuring …. Named remixes/versions are preserved; with is excluded so it doesn't eat real title words ("Dancing With Myself").
  • fuzzy_match_pending_request uses the multi-artist-aware artist_match_score, so equipment reporting only Lil Jon still matches a multi-artist request.

Bridge-app side (collection-code-vs-join-code display) is a separate, independent fix and will be its own PR.

Testing

🤖 Co-authored by Claude Opus 4.8. Closes #500.

Summary by CodeRabbit

  • Bug Fixes

    • Improved track matching accuracy by better handling featured artist credits in track titles.
    • Enhanced multi-artist matching to provide more reliable results when multiple collaborators are involved.
  • Tests

    • Added regression tests for multi-artist matching scenarios.

… title

DJ equipment (StageLinQ/Denon, streaming metadata) reports featured artists
inside the track title (e.g. "Get Low (ft. Ying Yang Twins)") and often only
the primary artist, while guests request the plain title listing every
collaborator. The fuzzy matcher left "(ft. …)" in the normalized title,
collapsing the score below the 0.8 threshold, so accepted requests were never
auto-matched and thus never marked played. Reconnecting re-POSTed the same
verbose title and re-ran the same failing match.

- normalize_track_title strips featured-artist credits (parenthetical,
  bracketed, and trailing "feat./ft./featuring …"). Named remixes/versions are
  preserved; "with" is excluded to avoid eating real title words.
- fuzzy_match_pending_request uses the multi-artist-aware artist_match_score so
  "Lil Jon" matches "Lil Jon, The EastSide Boyz, Ying Yang Twins".

Regression tests pin the production case (event RMLNDZ / request #491).

Closes #500

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

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@thewrz, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 2 hours, 55 minutes, and 27 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

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

🚦 How do rate limits work?

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

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, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8741147f-bf5e-4be4-a9b2-2397d9536342

📥 Commits

Reviewing files that changed from the base of the PR and between 3b8396f and ba85e84.

📒 Files selected for processing (2)
  • server/app/services/track_normalizer.py
  • server/tests/test_track_normalizer.py
📝 Walkthrough

Walkthrough

Adds two regex patterns (FEAT_PAREN_RE, FEAT_TRAILING_RE) to track_normalizer.py and applies them in normalize_track_title to strip featured-artist credits from track titles. Switches fuzzy_match_pending_request in now_playing.py to use artist_match_score for multi-artist-aware scoring and re-exports it. Adds regression tests for both fix paths.

Changes

Featured-artist credit stripping and multi-artist matching fix

Layer / File(s) Summary
Feat-credit regex patterns and normalize_track_title update
server/app/services/track_normalizer.py, server/tests/test_track_normalizer.py
Defines FEAT_PAREN_RE and FEAT_TRAILING_RE at module level to match parenthetical and trailing feat/ft/featuring credits, then extends normalize_track_title() to apply both substitutions (replacing parenthetical matches with a space) before existing dash-suffix and whitespace cleanup. Test cases cover parenthetical, bracket, trailing, remix-preservation, and with-word guard scenarios.
artist_match_score re-export and fuzzy match artist scoring update
server/app/services/now_playing.py, server/tests/test_now_playing.py
Re-exports artist_match_score from now_playing.py for backward compatibility and replaces the fuzzy_match_score call with artist_match_score in the artist-similarity component of fuzzy_match_pending_request, adding inline comments about multi-artist-aware matching. Two regression tests verify matching when a ft. credit is embedded in the bridge-reported title and when the bridge reports only the primary artist against a multi-artist accepted request.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.15% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: enabling track matching when equipment embeds featured artist credits in parentheses.
Linked Issues check ✅ Passed All objectives from issue #500 are met: featured-artist title normalization, multi-artist-aware artist matching, regression tests, and remix preservation.
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #500 objectives: track title normalization, artist matching logic, and corresponding tests.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/bridge-nowplaying-matching

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

Comment thread server/app/services/track_normalizer.py Fixed
Comment thread server/app/services/track_normalizer.py Fixed

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

🧹 Nitpick comments (1)
server/app/services/track_normalizer.py (1)

28-39: Regex patterns have theoretical ReDoS vulnerability, but input constraints mitigate practical risk.

Both FEAT_PAREN_RE and FEAT_TRAILING_RE exhibit backtracking under pathological input. However, all user-facing endpoints enforce max_length=255 on track titles via Pydantic validation, and testing confirms realistic payloads complete in <1ms:

  • 255-char payload: 0.0006s
  • 300-char payload: 0.0008s

The vulnerability only manifests with inputs >> 255 characters, which cannot be submitted through the API. While the patterns could be improved for defense-in-depth, this is not a practical security issue given the existing constraints.

Suggested improvement (optional): Constrain whitespace repetitions and use lazy quantifiers:

-FEAT_PAREN_RE = re.compile(
-    r"[\(\[]\s*(?:featuring|feat|ft)\.?\s+[^\)\]]*[\)\]]",
-    re.IGNORECASE,
-)
-FEAT_TRAILING_RE = re.compile(
-    r"\s+(?:featuring|feat|ft)\.?\s+.*$",
-    re.IGNORECASE,
-)
+FEAT_PAREN_RE = re.compile(
+    r"[\(\[]\s{0,3}(?:featuring|feat|ft)\.?\s{1,3}[^\)\]]{0,100}?[\)\]]",
+    re.IGNORECASE,
+)
+FEAT_TRAILING_RE = re.compile(
+    r"\s+(?:featuring|feat|ft)\.?\s{1,3}.{0,100}?$",
+    re.IGNORECASE,
+)
🤖 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 `@server/app/services/track_normalizer.py` around lines 28 - 39, The regex
patterns FEAT_PAREN_RE and FEAT_TRAILING_RE use greedy quantifiers and unbounded
whitespace matching that can cause excessive backtracking under pathological
input. To improve regex performance and follow defense-in-depth principles,
constrain the whitespace repetitions by limiting `\s+` occurrences and convert
greedy quantifiers like `.*` in FEAT_TRAILING_RE to lazy quantifiers using `.*?`
to reduce backtracking. Additionally, in FEAT_PAREN_RE, consider constraining
the `[^\)\]]*` pattern to avoid unbounded matching. These changes will make the
patterns more efficient while maintaining the same matching behavior for normal
inputs.

Source: Linters/SAST tools

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

Nitpick comments:
In `@server/app/services/track_normalizer.py`:
- Around line 28-39: The regex patterns FEAT_PAREN_RE and FEAT_TRAILING_RE use
greedy quantifiers and unbounded whitespace matching that can cause excessive
backtracking under pathological input. To improve regex performance and follow
defense-in-depth principles, constrain the whitespace repetitions by limiting
`\s+` occurrences and convert greedy quantifiers like `.*` in FEAT_TRAILING_RE
to lazy quantifiers using `.*?` to reduce backtracking. Additionally, in
FEAT_PAREN_RE, consider constraining the `[^\)\]]*` pattern to avoid unbounded
matching. These changes will make the patterns more efficient while maintaining
the same matching behavior for normal inputs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 59daa100-5e77-4586-9a2f-969fe31880f4

📥 Commits

Reviewing files that changed from the base of the PR and between f533c00 and 3b8396f.

📒 Files selected for processing (4)
  • server/app/services/now_playing.py
  • server/app/services/track_normalizer.py
  • server/tests/test_now_playing.py
  • server/tests/test_track_normalizer.py

…tracking

CodeQL flagged FEAT_PAREN_RE / FEAT_TRAILING_RE as polynomial-regex (ReDoS):
an unbounded `\s+` adjacent to a class that also matches spaces backtracks
O(n^2) on long runs of whitespace. Track titles are capped at 255 chars by
Pydantic so it is not practically exploitable, but bound every quantifier
({0,3}/{1,3}/{0,100}/{0,150}) for defense in depth. Behavior is unchanged for
realistic titles. Adds a linear-time regression guard on the two patterns.

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

thewrz commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator Author

CodeRabbit nitpick addressed (ReDoS in FEAT_PAREN_RE/FEAT_TRAILING_RE): bounded all quantifiers in ba85e84, matching the suggested approach, plus a linear-time regression guard. CodeQL flagged the same two patterns and they are now resolved.

Note: the pre-existing GENERIC_SUFFIX_* regexes share the same leading-\s*-before-a-literal shape. CodeQL did not flag them and they are out of scope for this matching bugfix, so I left them untouched to keep the diff focused.

@thewrz thewrz merged commit 7409e31 into main Jun 19, 2026
10 checks passed
@thewrz thewrz deleted the fix/bridge-nowplaying-matching branch June 19, 2026 09:12
thewrz added a commit that referenced this pull request Jun 20, 2026
…#11 (#517)

* fix(security): bound _SPLIT_RE quantifiers to clear ReDoS alert (#516)

_SPLIT_RE used unbounded \s+/\s* around literal delimiters, which CodeQL
py/polynomial-redos flagged (alert #11): a long run of spaces backtracks
quadratically. Not exploitable in prod (every caller caps artist at 255
chars and the public path collapses whitespace), but the regex itself was
the last unbounded one in this file — the sibling FEAT_* regexes were
already bounded in #501 for the same rule.

Bound the whitespace quantifiers to \s{0,3}/\s{1,3} so the pattern is
linear-time regardless of input length or caller. Add a ReDoS guard test
mirroring the existing feat-regex guard.

Closes #516

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

* test: loosen ReDoS-guard wall-clock budget to 2.0s for slow CI runners

CodeRabbit flagged the 0.2s threshold as potentially flaky on shared
runners. A quadratic regression takes tens of seconds, so 2.0s still
catches it with wide margin while removing any flake risk. Applied to
both ReDoS guards in this file to keep them consistent.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

Now-playing never marks accepted requests played when DJ equipment embeds "(ft. …)" in the title

2 participants