Skip to content

Planned channel-pipeline commit returns 502 with zero writes when a stream's logo URL already exists as a Dispatcharr logo row #1013

Description

@lukeeexd

Summary

A single duplicate-logo 400 from Dispatcharr aborts an entire channel-pipeline commit (POST /api/channel-pipeline/run/commit) with zero writes landed. The failing write is a create_logo that ECM's create_channel path adds for the matched stream's tvg-logo URL, even when the rule has no logo action. This has repeatedly killed a scheduled auto-creation rule in production (~15 event channels per run) and re-arms itself every cycle.

Root cause

  • Dispatcharr's POST /api/channels/logos/ returns 400 when a logo row with the same url already exists.
  • Logo rows outlive their channels: deleting a channel does not delete its logo row, so a row left over from a previous event cycle collides with the next run.
  • ActionExecutor._resolve_logo_id creates first and only falls back to find_logo_by_url when the create raises. Under PlanningDispatcharrClient the create never reaches Dispatcharr, so the plan records a create_logo write with a temp id for every channel whose logo already exists.
  • replay_write_plan is fail-fast: the real create_logo raises on the 400, the replay raises PartialReplayError: pipeline replay failed at write 0, compensation runs, and the endpoint returns 502. Every planned channel create, stream merge and EPG assignment behind it is lost.
  • The generic _request log line says only [DISPATCHARR] API request failed: POST - status: 400; the body that names the duplicate URL is not logged anywhere.

Evidence (2026-09-16, UTC)

  • 20:09–20:30: five commit attempts (including a minimal 3-write probe rule) all returned 502, zero completed writes, empty journal.
  • Container log at the failure:
    POST http://<dispatcharr>/api/channels/logos/ "HTTP/1.1 400 Bad Request"
    [DISPATCHARR] API request failed: POST - status: 400
    POST /api/channel-pipeline/run/commit -> 502 in 1542.1ms
    
  • UI: PartialReplayError: pipeline replay failed at write 0.
  • Every write primitive succeeds individually (single/bulk channel create, stream merge, EPG assign); only the replay dies, always on the logo POST.
  • Hand-POSTing /api/channels/logos/ with the streams' exact tvg-logo URL returns 400 because a row with that URL exists.
  • Workaround that restored service: deleting the orphan logo row (an April leftover, channel_count=0, URL identical to the streams' logo) → the next commit succeeded (15 channels created, 15 merges). That run created a new logo row with the same URL, so the next rollover collides again.

Repro

  1. Ensure a Dispatcharr logo row exists whose url equals a matched stream's tvg-logo (or POST the same logo twice; the second call returns 400).
  2. Run any pipeline rule with create_channel for that stream: prepare → 200; commit → 502, zero writes, PartialReplayError at write 0.

Proposed fix

  1. Executor (channel_pipeline_executor.py::_resolve_logo_id): look the URL up before creating, so a known logo produces no create_logo write in the plan and the created channel references the existing row.
  2. Client (dispatcharr_client.py::create_logo): idempotent on URL. Resolve an existing row before the POST and again after a 400 (row created in between); include the response body in the failure log.
  3. Replay (services/pipeline_write_plan.py::replay_write_plan): treat create_logo as a soft-fail write classified by recorded method, not HTTP status. Log, skip, map its temp id to None, continue. Structural writes (channel create, stream merge, EPG, profile membership) keep the fail-fast abort and compensation.

Acceptance criteria

  • Commit succeeds end-to-end with a pre-existing colliding logo row; the created channel references the existing logo id.
  • No behaviour change when logos are new or unique.
  • Replay abort semantics preserved for non-logo writes.

Out of scope

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions