{"record":{"id":"0c10ec1f7f5c4fb7","repo":"AlexxIT/go2rtc","slug":"ring-close","errorCode":null,"errorMessage":"ring: close","messagePattern":"ring: close","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/ring/client.go","lineNumber":301,"sourceCode":"\t\t\tif err := json.Unmarshal(rawMsg, &msg); err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\t// Skip empty candidates\n\t\t\tif msg.Body.Ice == \"\" {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tif err := prod.AddCandidate(msg.Body.Ice); err != nil {\n\t\t\t\tc.Stop()\n\t\t\t\tc.connected.Done(err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\tcase \"close\":\n\t\tc.Stop()\n\t\tc.connected.Done(errors.New(\"ring: close\"))\n\n\tcase \"pong\":\n\t\t// Ignore\n\t}\n}\n\nfunc (c *Client) GetMedias() []*core.Media {\n\treturn c.prod.GetMedias()\n}\n\nfunc (c *Client) GetTrack(media *core.Media, codec *core.Codec) (*core.Receiver, error) {\n\treturn c.prod.GetTrack(media, codec)\n}\n\nfunc (c *Client) AddTrack(media *core.Media, codec *core.Codec, track *core.Receiver) error {\n\tif webrtcProd, ok := c.prod.(*webrtc.Conn); ok {\n\t\tif media.Kind == core.KindAudio {\n\t\t\t// Enable speaker","sourceCodeStart":283,"sourceCodeEnd":319,"githubUrl":"https://github.com/AlexxIT/go2rtc/blob/c245815e75e2a5fd60b4290f12bfc04e55a984d3/pkg/ring/client.go#L283-L319","documentation":"When the ring WebSocket receives a \"close\" message from the server, the client stops itself and completes the pending connection with this \"ring: close\" error, indicating the remote side intentionally closed the session.","triggerScenarios":"Ring's server sends a WebSocket message of type \"close\" — e.g. session rejected, token expired, concurrent session limit, or server-side teardown of the stream.","commonSituations":"Expired refresh token invalidating the live session; opening a second stream for the same camera; Ring server maintenance or rate limiting.","solutions":["Refresh the Ring refresh_token and reconnect","Check for other active sessions using the same camera","Add automatic reconnect logic with fresh credentials","Retry after a delay in case of server-side throttling"],"exampleFix":"// before\nclient, err := Dial(ctx, query) // fails with \"ring: close\"\n// after\nif err != nil && errors.Is(err, errRingClose) {\n    query.Set(\"refresh_token\", getFreshToken())\n    client, err = Dial(ctx, query)\n}","handlingStrategy":"retry","validationCode":"if time.Since(tokenRefreshedAt) > maxTokenAge {\n    query.Set(\"refresh_token\", refreshRingToken()) // stale tokens cause server-initiated close\n}","typeGuard":null,"tryCatchPattern":"client, err := Dial(ctx, query)\nif err != nil && err.Error() == \"ring: close\" {\n    query.Set(\"refresh_token\", getFreshToken())\n    client, err = Dial(ctx, query)\n}","preventionTips":["Keep the refresh token fresh; server closes sessions on auth expiry","Avoid concurrent streams for the same camera","Add a delay before reconnecting to respect server-side throttling"],"tags":["websocket","server-close","ring"],"backgroundTag":"websocket-closed-by-server","analyzedSha":"c245815e75e2a5fd60b4290f12bfc04e55a984d3","analyzedAt":"2026-09-07T11:47:02.965Z","contentChangedAt":"2026-09-07T11:47:02.965Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}