Quick answer: Epic Online Services crashes cluster around three areas: the session and lobby lifecycle, P2P connections over the EOS relay, and authentication where tokens expire mid session. To fix them, capture the local ProductUserId, the session or lobby id, the P2P connection state, and the auth token validity at crash time. Group by that signature and EOS lifecycle and auth crashes become reproducible.

Epic Online Services gives indie developers a free, cross platform backend for sessions, lobbies, P2P, and authentication, but it asks you to manage a lot of asynchronous state correctly. Most EOS crashes are not in your gameplay; they sit at the boundary where an EOS callback returns a result your code did not expect, a P2P connection routes through the relay differently than on your LAN, or an auth token quietly expires partway through a long session. These conditions barely appear in short local tests but dominate real player sessions. This post covers what crashes EOS backed games and how to capture the session, P2P, and auth context that makes each one reproducible.

The session and lobby lifecycle

EOS sessions and lobbies move through create, join, update, and destroy operations, each completing asynchronously with a result code. Crashes happen when your code assumes an operation succeeded, ignores a non success result, or acts on a session that was modified or destroyed by another member between your calls. The stack trace shows where you dereferenced a null session handle, but not that a join failed three callbacks earlier. Capturing the session or lobby id and the last operation result reframes the crash entirely.

Lobby membership churn is a particularly common trigger. EOS notifies you of member joins and leaves via callbacks, and acting on a stale member, such as sending P2P data to someone who just left, throws. Recording the lobby id, the member count, and the local ProductUserId at crash time exposes these stale reference bugs. In short local tests nobody leaves unexpectedly, so the lifecycle stays on its happy path; in production the lobby is constantly changing underneath you, which is where the crashes live.

P2P connections and the EOS relay

EOS P2P, like Steam, attempts direct connections and falls back to a relay when NAT prevents them. The same sending and receiving code runs over both, with different latency and timing, so a crash tied to relay timing appears only for players whose networks force the relay. Capturing whether the connection was relayed or direct, and the P2P connection state, tells you which transport produced the failure rather than leaving you to guess from an identical stack trace across every player.

EOS P2P also keys connections by ProductUserId and a socket name, and crashes arise when a packet arrives for a connection your code already closed, or before you opened the receiving socket. The accept and close handshake races against incoming data exactly like other P2P stacks. Recording the remote ProductUserId, the socket name, and the connection state at crash time lets you place a crash in the handshake versus your message handling, which determines whether you harden connection setup or fix a deserialization assumption in your gameplay protocol.

Auth tokens that expire mid session

The EOS specific failure that catches the most teams is token expiry. The Connect interface issues auth tokens with a limited lifetime, and they must be refreshed before they expire. In a five minute local test the token never lapses, so the refresh path is untested. In a multi hour player session the token expires, a subsequent EOS call fails with an invalid auth result, and code that assumed the call would succeed crashes. Capturing the token validity and time until expiry at crash time makes this otherwise invisible cause obvious.

Auth crashes are insidious because they manifest far from their cause: the token lapses silently, and the crash happens later in whatever EOS operation runs next. Without the auth context, you see a session or P2P failure and debug the wrong subsystem entirely. Recording whether the local user was still authenticated, and when the token was last refreshed, separates a genuine session bug from a cascade triggered by an expired token. That single field has saved teams from days of chasing phantom networking bugs that were really auth lifecycle gaps.

Reproducing EOS edge cases

EOS crashes hide because short tests on a good network never exercise relay fallback, lobby churn, or token expiry. Reproduction means forcing those conditions: run a session long enough for a token to expire, or shorten the refresh window in a debug build to trigger expiry quickly. Once your reports show a crash correlates with an expired token, you reproduce it deliberately and verify your refresh logic actually recovers rather than crashing.

For P2P and lobby crashes, script the churn the reports indicate: have a member join and leave abruptly, or force a network configuration that pushes the connection onto the relay. With the captured connection state, transport, and ProductUserIds, you know precisely which transition to provoke. This turns the diffuse, hard to reproduce EOS crashes that only ever appeared in player reports into focused scenarios you can recreate and step through, because the captured context tells you exactly which EOS subsystem and state to target.

Setting it up with Bugnet

Bugnet captures EOS crashes with their full stack trace and device context, and the in game report button snapshots game state automatically, so a session or P2P failure arrives with the moment it happened in. To make EOS tractable, use custom fields to attach the local ProductUserId, the session or lobby id and member count, the P2P transport and connection state, and crucially the auth token validity and time to expiry. Those fields convert an opaque EOS result failure into a clear statement of which subsystem and which lifecycle state broke, all in one dashboard.

Bugnet folds duplicate reports into a single issue with an occurrence count, which suits EOS crashes that recur across long sessions and varied networks. You can see that an invalid auth crash hit 160 times, all after roughly the token lifetime had elapsed, and immediately recognize a refresh bug rather than a networking one. Filtering by transport, lobby state, or auth validity confirms the cause and verifies a fix, so you prioritize the EOS lifecycle gap that is actually ending the most player sessions.

A durable EOS crash workflow

Add the ProductUserId, session and lobby ids, transport, and token validity to your reporting once, near where you handle EOS callbacks, and every crash inherits the context. The auth token field alone is worth the effort, because token expiry is the single most misdiagnosed EOS failure and it becomes obvious the moment validity rides along with the crash. After that, read every EOS crash through the lens of which lifecycle and which auth state it hit rather than trusting the stack trace alone.

Test long sessions, lobby churn, and relay fallback deliberately before each release, since those are where EOS concentrates its crashes and short happy path tests never go. When grouped reports point at expired tokens or relay timing, you reproduce it directly. Over releases your token refresh, session lifecycle handling, and P2P teardown grow robust, and the diffuse EOS crashes that frustrate cross platform indie launches turn into a small, well understood set with clear causes attached to each report.

The most misdiagnosed EOS crash is an expired auth token surfacing later. Capture token validity with every report and a phantom networking bug becomes an obvious refresh gap.