Skip to content

fix(replay): stop touch tracker when session replay is paused#8455

Closed
tsushanth wants to merge 1 commit into
getsentry:mainfrom
tsushanth:fix/pause-touch-tracking
Closed

fix(replay): stop touch tracker when session replay is paused#8455
tsushanth wants to merge 1 commit into
getsentry:mainfrom
tsushanth:fix/pause-touch-tracking

Conversation

@tsushanth

@tsushanth tsushanth commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Fixes #8409.

Problem

SentrySDK.replay.pause() stops the capture scheduler (no new screenshots) but the touch tracker swizzle keeps firing unconditionally. SentryTouchTracker.trackTouchFrom is called on every sendEvent: regardless of replay pause state, so touches continue to buffer and appear in replay segments produced during or after the pause (e.g. triggered by an error capture).

Fix

  • Add isEnabled: Bool = true to SentryTouchTracker, checked at the top of trackTouchFrom. No thread lock needed — reads/writes happen on the main thread (swizzle always calls on main; pause()/resume() are called on main).
  • SentrySessionReplay.pause() sets touchTracker?.isEnabled = false immediately after stopCaptureScheduler().
  • SentrySessionReplay.resume() restores touchTracker?.isEnabled = true only inside the startCaptureScheduler(expectedGeneration:) success branch, so stale or racing resume calls that don't actually restart capture don't re-enable tracking early.

Tests

Two new tests in SentryTouchTrackerTests:

  • testIsEnabledFalse_DropsAllTouches — verify zero events recorded when isEnabled = false
  • testIsEnabledToggle_OnlyRecordsTouchesWhileEnabled — verify only touches recorded before disable and after re-enable appear; touch during disabled window is absent

#skip-changelog

SentryTouchTracker.trackTouchFrom was called unconditionally from
the sendEvent swizzle regardless of replay pause state. Calling
SentrySDK.replay.pause() stopped the capture scheduler (no new
screenshots) but left the touch tracker running, so touches
continued to buffer and appeared in replay segments produced during
or after the pause (e.g. triggered by an error capture).

Add isEnabled to SentryTouchTracker and gate trackTouchFrom on it.
Set isEnabled = false in SentrySessionReplay.pause() alongside
stopCaptureScheduler(), and restore it to true when the capture
scheduler successfully restarts in resume().

Fixes getsentry#8409

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6633498. Configure here.

state.withLock {
$0.isSessionPaused = true

private func startFullReplay(startedAt: Date?) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Touches stay disabled after restart

High Severity

pause() sets the shared touchTracker.isEnabled to false, but only resume() sets it back to true. start() starts the capture scheduler without re-enabling the tracker. Session restarts and stop()/start() go through stopCurrentReplay()pause() then a new start(), so touch recording can stay off for the rest of the process.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6633498. Configure here.

}

public func trackTouchFrom(event: UIEvent) {
guard isEnabled else { return }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unsynchronized touch enable flag

Medium Severity

isEnabled is read in trackTouchFrom on the main thread and written from pause() without hopping to main. Public SentrySDK.replay.pause()/stop() and rate-limit teardown can reach pause() off the main thread, so the new flag is shared mutable state without synchronization despite the PR assuming main-thread-only access.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6633498. Configure here.

@NinjaLikesCheez

Copy link
Copy Markdown
Member

@tsushanth Thank you for your contribution! Looks like your IDE has indented something resulting in a lot of changes where it's mostly white-space - could you reformat it back so we can review easier? Thanks!

@tsushanth

Copy link
Copy Markdown
Contributor Author

Closing this in favor of a clean rewrite — the whitespace churn was from an Xcode reformat that inflated the diff. New PR with only the 3-line logical change: #8456

@tsushanth

Copy link
Copy Markdown
Contributor Author

Superseded by #8456 — clean 3-line diff with no whitespace churn.

@tsushanth tsushanth closed this Jul 16, 2026
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.

Paused replay still capturing touches

2 participants