Skip to content

[Bug]: Teamarr refreshes EPG before Dispatcharr associates - Channels DVR #855

Description

@justinglock40

Tag / Commit

latest

Dispatcharr version

0.31.0

Browser

No response

Affected area

Generation / EPG output

What happened?

Using Claude to review how the code works it determined that Teamarr if forcing action before Dispatcher has written what it requires to be accurate.

Creating this bug report to have dev review for accuracy of code operation and suggested fix.

Expected behavior

I'll pull down the repo and look at the Dispatcharr sync and Channels DVR refresh paths.Let me check Dispatcharr's own behavior for these endpoints, since the race depends on it. I found a concrete cause, and it's a race between Teamarr, Dispatcharr's async program parsing, and the Channels DVR trigger. It explains exactly why the channel appears but the guide is empty, and why a later manual refresh in Channels fixes it.

What the code does

In teamarr/consumers/generation.py, the order inside a run is:

  1. Write the merged XMLTV (line ~450)
  2. Refresh Dispatcharr's EPG source and wait for itepg_manager.wait_for_refresh(...) (line 482)
  3. Then associate EPG data with channels — associate_epg_with_channels(...) (line 494)
  4. Deletions, reconciliation, stream audit, cleanup
  5. Fire the Channels DVR M3U + lineup refresh in a background thread — _start_media_server_refresh(...) (line 577)

Step 2 happening before step 3 is the problem, because of how Dispatcharr behaves.

Why that breaks new channels

Dispatcharr's EPG import deliberately only parses programme data for EPG entries that are already mapped to a channel. From apps/epg/tasks.py:2167:

Only process EPG entries that are actually mapped to channels… skips programmes for unmapped channels entirely during parsing

create_channel in teamarr/dispatcharr/managers/channels.py:273 never sets epg_data, so a brand-new Teamarr channel is unmapped when the import at step 2 runs. Dispatcharr creates the EPGData row for teamarr-event-<id> but stores zero programmes for it.

Then step 3 calls POST /api/channels/channels/<id>/set-epg/ per channel. On the Dispatcharr side that only queues the work — apps/channels/signals.py:183 fires parse_programs_for_tvg_id as a Celery task, and set_epg itself returns immediately with "EPG refresh queued". Teamarr doesn't wait for any of it (syncer.py:606 fires and forgets).

Worse, two things stretch that queue out:

  • Every managed channel gets a redundant set-epg call every run. associate_epg_with_channels doesn't compare against the channel's current epg_data_id, and Dispatcharr's Channel post_save signal doesn't check whether the value actually changed either (unlike the ChannelOverride handler right below it, which does). So each run queues one full-XMLTV-file parse task per channel, and the parse your new channel needs sits behind all of them.
  • Those tasks defer if the source refresh lock is still held, 15 seconds at a time (_EPG_REFRESH_DEFER_SECONDS = 15, tasks.py:70 and tasks.py:1668).

So when step 5 fires, Channels DVR pulls /output/epg from Dispatcharr and gets your channel with no <programme> elements. Teamarr's log verification still passes, because "[DVR] Fetched guide data" and "Indexed N airings" refer to the whole lineup, not your specific channel. Channels then keeps that empty guide until its next scheduled or manual refresh — which is why manually refreshing works.

It also explains why a second generation run fixes it: by then the channel is mapped, so the import at step 2 includes it.

How to confirm in about a minute

Right after a Teamarr run that creates a channel, in Dispatcharr's logs look for the relative timing of:

  • Refreshing program data for tvg_id: teamarr-event-...
  • Deferring parse_programs_for_tvg_id(...) for 15s: source refresh in progress

and compare against Teamarr's [CHANNELSDVR] Triggered EPG refresh for lineup '...'. If the Dispatcharr line lands after the Teamarr line, that's it. You can also curl Dispatcharr's /output/epg immediately after a run and grep for your tvg-id — you'll see the <channel> element with no programmes.

Fixes worth raising upstream

The minimal fix is to re-import after association, before the media-server trigger: do associate_epg_with_channels first, and only then call wait_for_refresh (or call it a second time when result.epg_association["associated"] included any new mappings). That way the import itself sees the mapping and parses programmes synchronously, and the existing wait_for_refresh gating already blocks until Dispatcharr reports success.

Two supporting changes:

  • Switch associate_epg_with_channels to Dispatcharr's POST /api/channels/channels/batch-set-epg/ (api_views.py:2477). It skips unchanged associations and only dispatches parses for EPG ids that actually changed, and it returns channels_updated / programs_refreshed — so Teamarr gets a clean signal for whether it needs to wait at all. Right now Teamarr is queuing dozens of redundant whole-file parses per run.
  • Before firing the Channels DVR refresh, verify programme data exists for the newly associated tvg_ids. Teamarr already has the tool for this: EPGManager.search_programs(tvg_id=...). Polling that for the new ids would make the CDVR trigger evidence-based on the Dispatcharr side, the same way it already is on the CDVR side.

As a stopgap until it's patched, the reliable workaround is to have something refresh the Channels lineup a couple of minutes after the Teamarr run finishes — a cron hitting PUT /dvr/lineups/XMLTV-<source> on your DVR — or to run generation on a tighter schedule so the follow-up run repairs the guide sooner.

Steps to reproduce

Run a generation and check Channels DVR Guide.

Logs

Screenshots

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions