/api/streams omits "url" for pushed producers, making the whole response undecodable for strict clients
Maintainers usually reply within 2 days
@AlexxIT is already working on this.
Since Oct 1, 2026.
Assessment
This issue has not been assessed yet.
Description
Summary
/api/streams omits "url" for pushed producers, making the whole response undecodable for strict clients
/api/streams can contain a producer object with no url key. This is not specific to
receivers — it happens for any producer that was pushed into a stream rather than dialled.
Clients that deserialize the response strictly then fail on the entire response, including
for unrelated streams.
Home Assistant's go2rtc-client is one such client: Producer.url is declared url: str with
no default, so one pushed producer anywhere makes streams.list() raise
mashumaro.exceptions.MissingField: Field "url" of type str is missing in Producer instance,
which in HA breaks every go2rtc-backed camera (snapshot and WebRTC offer paths).
Root cause
internal/streams/producer.go:
func (p *Producer) MarshalJSON() ([]byte, error) {
if conn := p.conn; conn != nil {
return json.Marshal(conn) // p.url is discarded here
}
info := map[string]string{"url": p.url}
return json.Marshal(info)
}
Once a producer is connected, the wrapper delegates serialization to the connection object and
drops p.url entirely. pkg/core/connection.go:42 then decides whether the key appears at all:
URL string `json:"url,omitempty"`
So a connection that never had SetURL() called emits no url key at all, while one that did
(rtsp, nest, webrtc dial) still shows it. That is why this looks intermittent and producer-specific
rather than systematic.
I think the intent was for it to always be reported. internal/streams/stream.go:
func (s *Stream) AddProducer(prod core.Producer) {
producer := &Producer{conn: prod, state: stateExternal, url: "external"}
url: "external" is set so pushed producers can be identified — but per the MarshalJSON above it
is never emitted, because conn is always non-nil for these. It is currently a dead value.
Reproduction
streams:
cam1:
- tcp://127.0.0.1:65535
- Have an external process open a TCP connection to
127.0.0.1:65535and write a raw H.264
elementary stream. go2rtc accepts it as an external producer on that stream. - Attach any client, e.g.
ffmpeg -rtsp_transport tcp -i rtsp://127.0.0.1:8554/cam1
(this is what adds thereceiversarray, but it is not required for the bug). GET /api/streams
Observed producer entry, no url key:
{
"id": 1,
"format_name": "h264",
"medias": ["video, recvonly, H264"],
"receivers": [
{ "id": 2, "codec": { "...": "..." }, "childs": [4],
"bytes": 243052660, "packets": 19833 }
],
"bytes_recv": 245298084
}
The sibling entry for the configured tcp:// source, in the same response, does have url —
because at that moment its conn is nil (the socket is not connected), so the fallback branch runs.
The same thing happens for WHIP/WebRTC push producers, which is the case reported on the Python
client side.
Note I did not build a minimal isolated repro for this — it was observed on a live go2rtc 1.9.14
instance running as a Home Assistant add-on, where a third-party integration publishes camera video
into a tcp:// stream.
Impact
The blast radius is wider than the affected stream. Any client that strictly deserializes
/api/streams gets nothing back, so a single pushed producer disables all streams, including
ones with no relation to it. In Home Assistant that means every go2rtc camera in the instance fails
while a publisher is connected — and because publishers connect and disconnect, it presents as an
intermittent failure that is hard to attribute.
Suggested fix
Merge the wrapper's URL into the marshaled connection, so the field is always present:
func (p *Producer) MarshalJSON() ([]byte, error) {
if conn := p.conn; conn != nil {
data, err := json.Marshal(conn)
if err != nil {
return nil, err
}
var m map[string]any
if err = json.Unmarshal(data, &m); err != nil {
return nil, err
}
if _, ok := m["url"]; !ok {
m["url"] = p.url
}
return json.Marshal(m)
}
info := map[string]string{"url": p.url}
return json.Marshal(info)
}
This also makes url: "external" in AddProducer meaningful again.
Dropping omitempty from core.Connection.URL would be a smaller change, but it would report
"url": "" rather than the actual source for pushed producers, and it would not fix conn
implementations that define their own MarshalJSON.
Making the field optional on the client side is a valid mitigation for a specific client, but it
leaves the API contract inconsistent and requires every strict client to harden independently.
Related
- https://github.com/home-assistant-libs/python-go2rtc-client/issues/344 — same failure from the
client side, reported for WHIP producers. Currently stale-closed; asking for a reopen. - https://github.com/home-assistant-libs/python-go2rtc-client/pull/345 — proposed client-side fix
(makeProducer.urloptional). Also stale-closed.
- Dominant language
- Go
- Stars
- 14.3k
- Forks
- 1.4k
- Avg merge
- 1m
- Merged PRs (30d)
- 1
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from AlexxIT/go2rtc
-
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
Maintainers usually reply within 2 days
-
Blue Iris HLS segment URL contains trailing CR: invalid control character in URLPossibly taken @AlexxIT claimed this 12 days ago. Openenhancement module/hls
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
AlexxIT/go2rtc#2569 · 1 assignee ·
Maintainers usually reply within 2 days
-
HomeKit client: `SetupEndpoints` always advertises `IPVersion=0`, silently breaking every IPv6 accessoryPossibly taken @SeoFood claimed this 13 days ago. Openenhancement module/homekit
Difficulty 1/5 Under an hour Newbie friendliness 84/100
Maintainers usually reply within 2 days
-
BUG: ONVIF client returns 404 on cameras that serve all services from a single endpoint (e.g. Axis)Openenhancement module/onvif
Difficulty 1/5 Under an hour Newbie friendliness 85/100
AlexxIT/go2rtc#2200 · 4 comments ·
Maintainers usually reply within 2 days
-
Bad media/imaging URL construction in onvif client since 1.9.12Possibly taken @monarchsofchaos004-gif claimed this 4 days ago. Openbug module/onvif
Difficulty 2/5 1-3 hours Newbie friendliness 64/100
Maintainers usually reply within 2 days
Similar issues
-
Project submission:Open
Difficulty 1/5 Under an hour Newbie friendliness 72/100
slavakurilyak/awesome-ai-agents#845 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 3 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
-
[BUG] deploy.restart_policy without condition drops the restart policyPossibly taken @akshat-lakhera claimed this 1 day ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
docker/compose#14383 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 72/100
Azure/azure-storage-fuse#2429 ·
Maintainers usually reply within 1 day