feat(core): Add Sentry.appLoaded() API to signal app start end#5940
feat(core): Add Sentry.appLoaded() API to signal app start end#5940
Conversation
Adds a public `Sentry.appLoaded()` function that users can call to
explicitly signal when their app is fully ready for user interaction.
This provides a more accurate app start end timestamp for apps that
do significant async work after the root component mounts (e.g.
remote config fetching, session restore, splash screen dismissal).
When appLoaded() is called it:
- Records the current timestamp as the manual app start end
- Fetches native frames for frame data attachment
- Triggers standalone app start capture when in standalone mode
Priority / race condition handling:
- appLoaded() before ReactNativeProfiler.componentDidMount: the auto
capture in _captureAppStart({ isManual: false }) is skipped entirely
- appLoaded() after componentDidMount: the existing appStartEndData
timestamp is overwritten with the manual (later) value; since the
app start spans are attached to the first navigation transaction
(which hasn't been flushed yet), the correct timestamp is used
Existing behaviour is unchanged when appLoaded() is not called.
The three-tier fallback (wrap -> bundle start -> warn) still applies.
Adds _appLoaded() internal function, _clearAppStartEndData() testing
helper, and five new unit tests covering: manual timestamp, duplicate
call guard, post-auto-capture override, pre-auto-capture guard, and
no-op before Sentry.init().
Semver Impact of This PR⚪ None (no version bump detected) 📋 Changelog PreviewThis is how your changes will appear in the changelog.
Plus 8 more 🤖 This preview updates automatically when you update the PR. |
b12e8e8 to
d7e01e6
Compare
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| const client = getClient(); | ||
| if (!client) { | ||
| debug.warn('[AppStart] appLoaded() was called before Sentry.init(). App start end will not be recorded.'); | ||
| return; |
There was a problem hiding this comment.
Early flag set disables app start tracking permanently
Medium Severity
isAppLoadedManuallyInvoked is set to true on line 86 before the getClient() check on line 88. If appLoaded() is called before Sentry.init(), the flag stays true permanently. This silently blocks all subsequent auto-capture in _captureAppStart (which checks this flag and returns early for non-manual calls). The result: calling appLoaded() before init() permanently disables app start tracking for the entire session — not just the manual call, but also the automatic ReactNativeProfiler.componentDidMount path.
Additional Locations (1)
| isAppLoadedManuallyInvoked = true; | ||
|
|
||
| const client = getClient(); | ||
| if (!client) { | ||
| debug.warn('[AppStart] appLoaded() was called before Sentry.init(). App start end will not be recorded.'); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Bug: The isAppLoadedManuallyInvoked flag is set before client validation. If appLoaded() is called before init(), subsequent automatic app start captures are incorrectly skipped.
Severity: MEDIUM
Suggested Fix
Move the isAppLoadedManuallyInvoked = true; assignment to after the if (!client) check succeeds. This ensures the flag is only set when the manual appLoaded() call can be successfully processed.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: packages/core/src/js/tracing/integrations/appStart.ts#L86-L92
Potential issue: In the `_appLoaded` function, the `isAppLoadedManuallyInvoked` flag is
set to `true` unconditionally before checking if a Sentry client exists. If a user calls
`Sentry.appLoaded()` before `Sentry.init()`, the function logs a warning and returns
early, but the flag remains `true`. This prevents the subsequent automatic app start
capture from running, as it incorrectly assumes a manual capture was successful. As a
result, no app start end timestamp is recorded for the entire session, silently
degrading app start tracking.
Did we get this right? 👍 / 👎 to inform future reviews.
| * Records the application start end. | ||
| * Used automatically by `Sentry.wrap` and `Sentry.ReactNativeProfiler`. | ||
| */ | ||
| export function captureAppStart(): Promise<void> { |
There was a problem hiding this comment.
Q: would it make sense to deprecate this method?
| }); | ||
|
|
||
| function makeIntegration(): AppStartIntegrationTest { | ||
| const integration = appStartIntegration({ standalone: false }) as AppStartIntegrationTest; |
There was a problem hiding this comment.
Q: Should we also test with standalone: true?
| * Sentry.appLoaded(); | ||
| * ``` | ||
| */ | ||
| export function appLoaded(): void { |
There was a problem hiding this comment.
I have no strong opinion on this but wdyt of marking as experimental since it's a new api?
| if (NATIVE.enableNative) { | ||
| try { | ||
| const endFrames = await NATIVE.fetchNativeFrames(); | ||
| debug.log('[AppStart] Captured end frames for app start.', endFrames); | ||
| _updateAppStartEndFrames(endFrames); | ||
| } catch (error) { | ||
| debug.log('[AppStart] Failed to capture end frames for app start.', error); | ||
| } | ||
| } | ||
|
|
||
| await client.getIntegrationByName<AppStartIntegration>(INTEGRATION_NAME)?.captureStandaloneAppStart(); |
There was a problem hiding this comment.
nit: we could extract this part from _captureAppStart and reuse it


📢 Type of change
📜 Description
Fixes #5935
Adds a public
Sentry.appLoaded()function that users can call to explicitly signal when their app is fully ready for user interaction. This provides a more accurate app start end timestamp for apps that do significant async work after the root component mounts.When
appLoaded()is called it:manual.app.startExisting behaviour is unchanged when
appLoaded()is not called.Usage
💚 How did you test it?
Tests were added :)
📝 Checklist
sendDefaultPIIis enabled