Skip to content

fix(replay): Keep replays of this process in the startup cleanup - #6343

Open
romtsn wants to merge 3 commits into
mainfrom
fix/replay-startup-cleanup-keeps-current-process
Open

romtsn wants to merge 3 commits into
mainfrom
fix/replay-startup-cleanup-keeps-current-process

Conversation

@romtsn

@romtsn romtsn commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

📜 Description

The startup cleanup of previous replays no longer deletes replays recorded by the current process.

finalizePreviousReplay() runs once, at register(), on the shared options.executorService. That executor can be busy at startup (on an emulator I saw ANR processing hold it for about 10 s), so the task can run after the app has already started, flushed and stopped a replay. Two things then went wrong:

  • Cleanup: cleanupReplays() deleted every replay_* folder except the current replayId and the previous process's unfinished replay. After stop() the current replayId is empty, so it deleted the folder of the replay that had just stopped, including segment videos the transport had not read yet. The segment was sent without video.
  • Finalization: the scope observer persists the replay id to replay.json when a replay is captured. If the task ran after a flush, it read this process's replay id as the "previous" replay and tried to recover it. That either deletes the running replay's folder (no ongoing_segment yet) or sends a recovered segment with the previous-replay flag, which deletes the whole folder after it is serialized.

ReplayIntegration now keeps the ids of the replays it started in this process. cleanupReplays() keeps their folders, and finalizePreviousReplay() skips an id from this process. Their folders still go away as before: the transport deletes each video after sending it, and leftovers are cleaned up on the next launch.

💡 Motivation and Context

Found while verifying #6340 on an emulator: with an early startBuffering() → flush() → stop(), the startup task ran right after stop() and deleted the replay folder before the transport read segment 0.

💚 How did you test it?

Two tests in ReplayIntegrationTest hold back the startup task, record and stop a replay, then run the task:

  • delayed startup cleanup keeps replays recorded by this process — the stopped replay's video survives, an older replay folder is still deleted.
  • delayed startup task does not finalize a replay recorded by this process — with this replay's id in replay.json, nothing is captured and the folder survives.

Both fail without the fix. Not verified on a device: the race needs the startup executor to be blocked, which I could not trigger on demand.

📝 Checklist

  • I added GH Issue ID & Linear ID
  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.
  • Public API changes reviewed by another Mobile SDK team member or implemented according to the develop docs spec.

🔮 Next steps

🤖 Generated with Claude Code

romtsn and others added 2 commits October 21, 2026 13:26
finalizePreviousReplay runs once on the shared executor and can run late, e.g. behind ANR
processing. cleanupReplays then deleted every replay folder except the current replay id, which
is empty after stop(), so it removed the folder of a replay that had just stopped, including
segment videos the transport had not read yet.

It could also read this process's replay id from the persisted scope after a flush and treat
it as a previous replay to recover, which deletes the folder of the running replay.

Track the replay ids started in this process, keep their folders in the cleanup and skip
finalizing them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sentry

sentry Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
SDK Size io.sentry.tests.size 8.61.0 (1) release

⚙️ sentry-android Build Distribution Settings

ConcurrentHashMap.newKeySet() requires API 24 and the min SDK is 21.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@romtsn
romtsn marked this pull request as ready for review October 21, 2026 13:21

@runningcode runningcode left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice find! I wonder if we have any other bugs due to that executor being busy!

private val state = AtomicReference(ReplayState())
// Replays recorded by this process. Their folders hold segment videos the transport may still
// read after the replay stopped, so the startup cleanup of previous replays must keep them.
private val replayIdsOfThisProcess = CopyOnWriteArraySet<String>()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be static? E.g. a second SDK init would create a new ReplayIntegration, right?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants