The hourly Plex sync on my VIP account kept missing plays. Took a while to find the cause, so here it is.
The GET requests the sync sends to Plex carry a two-byte JSON body (Content-Type: application/json, Content-Length: 2, presumably {}). Behind my reverse proxy the headers and that body reach Plex as two separate packets, with a small gap in between.
Plex does not cope with that. When the body is not in the same read as the headers, it answers the request and then closes the connection right after. If those two bytes are still sitting unread in the socket, the close turns into a TCP reset and whatever is left of the response is thrown away. Small answers are already out the door by then. The big one is not: the GET /library/metadata/<100 ids> call is about 620 KB and gets cut off partway. The client retries three times and gives up, so the round ends before the history call runs. Next round the lastViewedAt window has moved on and those plays are gone for good.
I checked this in two ways. A kernel tracepoint shows the reset comes from Plex’s own close. By hand it is just as clear: send the headers, wait a millisecond and then send {}. Plex closes every time. Send both in one write and the connection stays open. Leave the body off and everything is fine.
It bit me because Plex sits behind Caddy, which always sends the headers before the body (Traefik uses the same Go transport, so I expect the same there). Talking to Plex directly is probably fine, since the client’s two writes should land back to back. I have patched my proxy to strip the body, so this is mostly a heads-up for people with the same setup. The tidy fix on the Trakt side would be to leave the body off GET requests. Plex ignores it anyway.