Skip to content

feat(switch2): Nintendo Switch 2 support, and stop the offline transport raising system dialogs - #2831

Closed
bitsandfoxes wants to merge 7 commits into
mainfrom
fix/switch-offline-transport-backoff
Closed

feat(switch2): Nintendo Switch 2 support, and stop the offline transport raising system dialogs#2831
bitsandfoxes wants to merge 7 commits into
mainfrom
fix/switch-offline-transport-backoff

Conversation

@bitsandfoxes

Copy link
Copy Markdown
Contributor

Brings up Nintendo Switch 2, and fixes three defects found while getting it working on a devkit. Each commit stands alone.

The reported problem

A Switch game that is offline showed the system "connect to the internet" dialog every couple of seconds, indefinitely.

UnityWebRequestTransport opens a connection per envelope, and on Switch the attempt itself is what raises the dialog. With logs and metrics both flushing on a 5s timer, that is a dialog every ~2.5s. A connection error only logged a warning and returned null, which also skipped the rate-limit handling in HttpTransportBase — so nothing anywhere recorded that the network was down, and the next envelope tried again immediately.

What's here

fix(switch): transport backoff — skip sending while the platform reports no network, and back off exponentially (1s→60s) after a connection error, resetting on the first success. Both paths record DiscardReason.NetworkError, so drops still show up in client reports instead of vanishing.

fix(switch): diagnostic logger no longer throws while logging — this one bit hard. With DisableFileWrite, resolving the installation ID falls through to NetworkInterface.GetAllNetworkInterfaces(), whose static initializer throws on Switch. Hub.ConfigureScope caught it and logged it — but stringifying that exception threw again, escaping the handler and aborting SDK init partway through registering integrations. Every Unity integration after GlobalRootScopeIntegration was left unregistered, so nothing was captured and no envelope was ever produced. A logger must never turn a handled error into a fatal one.

fix(switch2) + feat(switch2): Switch 2 supportUNITY_SWITCH2 selects SentryNativeSwitch, both asmdefs include the platform, and it gets its own plugin directory with no-op stubs. IsNativeSupportEnabled had no Switch2 case, so it fell through to the default and returned false — native support could never initialize regardless of configuration. Switch 2 shares SwitchNativeSupportEnabled and the existing Switch assembly, which is shipped to it via plugin metadata rather than by building a second assembly.

fix(switch): ask the platform for network availability — see below.

Why reachability isn't enough

Application.internetReachability reports a Switch console as reachable while it is offline. From the device:

Failed to send request: No Internet Connection.
Reachability reported as ReachableViaLocalAreaNetwork.

So the reachability gate was inert on the one platform it was written for, and the backoff absorbed everything — which reduced the dialogs to one per minute but could not remove them, because each retry is still an attempt.

sentry-switch now exposes sentry_switch_utils_is_network_available(), which asks the console's network interface manager without submitting a network request — so asking costs nothing and shows no dialog. SentryNativeSwitch supplies it as NetworkAvailabilityProbe and the transport prefers it, falling back to reachability when no probe is set, leaving WebGL and the unknown-platform path unchanged.

Verified on a Switch 2 devkit

Offline, with the probe in place:

Sentry: (Debug) Using the native SDK to determine network availability.
Sentry: (Debug) No network available. Dropping envelope instead of attempting to send.

No Failed to send request, no backoff, no dialogs — no UnityWebRequest is ever constructed, so nothing can raise one. Native init completes and scope sync is syncing breadcrumbs, user and trace.

Merge order

Depends on getsentry/sentry-switch#188, which must merge and ship first. The P/Invoke goes through __Internal against a statically linked archive, so it resolves at link time — a Switch player built against an older libsentry.a fails to link rather than falling back. The bundled stubs only cover the no-native-library case.

Not covered here

Two related issues left deliberately out of scope: SentryNativeBridge never sets network_connect_func, so the native transport does not start and native crash reports depend on Nintendo's upload path; and Hub's integration loop has no per-integration try/catch, which is why a single throwing Register could decapitate the SDK.

🤖 Generated with Claude Code

https://claude.ai/code/session_01ABHTqM37qGAc9iQZcdbdY8

bitsandfoxes and others added 5 commits September 4, 2026 12:33
UnityWebRequestTransport opens a connection per envelope. On Nintendo Switch
every attempt made while the console is offline raises the system "connect to the
internet" dialog, and because logs and metrics each flush on a 5s timer that
turned into a prompt every couple of seconds. A connection error previously only
logged a warning and returned null, which also skipped the rate-limit handling in
HttpTransportBase, so nothing anywhere recorded that the network was down.

Add two guards to SendEnvelopeAsync: skip sending entirely while the platform
reports NotReachable, and back off exponentially (1s up to 60s) after a
connection error, resetting on the first success. The backoff is what covers
platforms whose reachability cannot be trusted, and the case where the network
drops after startup. Both paths record DiscardReason.NetworkError so client
reports still account for the drops.

Reachability goes through IApplication rather than UnityEngine.Application so the
behaviour is testable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ABHTqM37qGAc9iQZcdbdY8
Callers log from inside catch blocks that have already handled the failure.
UnityLogger interpolated the exception directly, so anything that threw while
being stringified escaped from within the handler and turned a handled error
into a fatal one.

On Nintendo Switch that is what happened: with DisableFileWrite set, resolving
the installation ID falls through to NetworkInterface.GetAllNetworkInterfaces(),
whose static initializer throws. Hub.ConfigureScope caught it and logged it, but
stringifying that exception threw again, escaping the handler and aborting SDK
initialization partway through registering integrations. Every Unity integration
after GlobalRootScopeIntegration was left unregistered, so nothing was captured
and no envelope was ever produced.

Format the message and render the exception defensively, falling back to the
type and message when the stack trace cannot be read, and never let the write
to Unity's logger propagate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ABHTqM37qGAc9iQZcdbdY8
Switch 2 reached SentryNativeSwitch.Configure but bailed out of it with
"Native support is disabled for 'Switch2'": IsNativeSupportEnabled had no case
for the platform, so it fell through to the default and returned false. Native
support could never initialize, no matter how the project was configured.

Switch 2 shares the Switch implementation, so it now follows
SwitchNativeSupportEnabled, and the existing Switch assembly is shipped to it by
enabling that platform on the assembly's plugin metadata - which is what decides
where an assembly goes, so no separate Switch 2 assembly is needed.

RuntimePlatform.Switch2 only exists in Unity 6000.3 and newer while the SDK still
supports 2021.3, and these assemblies compile against a single Unity version, so
the platform is matched by name instead of by enum member - referencing the
member directly would stop the SDK building against the editors that predate it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ABHTqM37qGAc9iQZcdbdY8
…chability

Application.internetReachability reports a Switch console as reachable while it
is offline - the device logs show "Reachability reported as
ReachableViaLocalAreaNetwork" next to every failed send. The reachability gate in
the transport was therefore inert on the one platform it was meant to protect,
leaving the connection-error backoff to absorb everything. Since attempting the
connection is itself what raises the system "connect to the internet" dialog,
that still meant a dialog per attempt, just at the backoff's cadence.

Let the platform answer instead. sentry-switch now exposes
sentry_switch_utils_is_network_available(), which asks the console's network
interface manager without submitting a network request, so asking costs nothing
and shows no dialog. SentryNativeSwitch hands it to the options as
NetworkAvailabilityProbe and the transport prefers it, falling back to
reachability where no probe is set - which keeps WebGL and the unknown-platform
path behaving as before.

The stub returns 1 so builds without the native library keep today's behaviour
and rely on the backoff.

Requires a sentry-switch build that exports the new function: the P/Invoke goes
through __Internal against a statically linked archive, so it resolves at link
time rather than falling back at runtime.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ABHTqM37qGAc9iQZcdbdY8
Recognise the platform and route it through the existing Switch implementation:
UNITY_SWITCH2 defines SENTRY_NATIVE_SWITCH2, which selects SentryNativeSwitch as
the platform configuration, and both assembly definitions now include Switch 2 so
the runtime code ships to it. Switch 2 gets its own native plugin directory with
no-op stubs, matching the Switch layout, so a project without the native library
still links.

Switch 2 shares the SwitchNativeSupportEnabled option rather than introducing a
second one, since it shares the implementation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ABHTqM37qGAc9iQZcdbdY8
Comment on lines 85 to +90
return;
}

// Wired up before the storage and native SDK setup below: the probe only needs the native
// library to be linked, so the transport keeps the benefit even if either of those fails.
Logger?.LogDebug("Using the native SDK to determine network availability.");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: An early return in SentryNativeSwitch.Configure prevents setting the NetworkAvailabilityProbe on Switch when native support is disabled, causing an unreliable network check.
Severity: MEDIUM

Suggested Fix

Move the options.NetworkAvailabilityProbe = IsNetworkAvailable; assignment to before the if (!options.IsNativeSupportEnabled(platform)) check. This will ensure the reliable network probe is always configured for the Switch platform, regardless of the native support setting.

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: src/Sentry.Unity.Native/SentryNativeSwitch.cs#L85-L90

Potential issue: In `SentryNativeSwitch.Configure`, an early `return` statement is
executed if `options.IsNativeSupportEnabled(platform)` is false. This prevents the
`options.NetworkAvailabilityProbe` from being set to the custom `IsNetworkAvailable`
function for the Nintendo Switch platform. Consequently, the transport layer falls back
to using `Application.internetReachability`, which is known to be unreliable on the
Switch and can incorrectly report the device as online. This defeats the purpose of a
fix designed to prevent offline error dialogs from appearing when the console is not
connected to the internet.

Did we get this right? 👍 / 👎 to inform future reviews.

@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 1 potential issue.

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 1ef9065. Configure here.

Comment thread package-dev/Plugins/Switch2/sentry_native_stubs.c Outdated
bitsandfoxes and others added 2 commits September 4, 2026 13:01
The Switch 2 stub was a byte-for-byte copy of the Switch one, and it had already
drifted: the network availability function added in this branch went into the
Switch stub only. Which platforms a stub ships to is decided by its plugin
metadata, exactly as it is for the assembly, so one file can serve both.

It also could not have worked as it stood. The stub is enabled or disabled at
build time depending on whether the user supplied the native libraries, and
SwitchNativePluginBuildPreProcess returned early for anything that was not
BuildTarget.Switch - so on Switch 2 the stub was never toggled at all, leaving a
project without the native libraries unable to link. And because package-dev
metadata is gitignored except for named exceptions, the copy's .meta was not
tracked, so the duplicate shipped without the import settings that gate it.

The preprocessor now runs for both targets, resolves the required libraries per
platform, and toggles compatibility for whichever target is being built - the
importer tracks that per target, so the two do not interfere. BuildTarget.Switch2
only exists in Unity 6000.3 and newer, so it is matched by name and the value
taken from the build report, which keeps this compiling against older editors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ABHTqM37qGAc9iQZcdbdY8
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ABHTqM37qGAc9iQZcdbdY8
@bitsandfoxes

Copy link
Copy Markdown
Contributor Author

Split into three focused PRs, since these are independent changes that happened to be found together:

Nothing is dropped; the combined branch is superseded by those three.

@bitsandfoxes
bitsandfoxes deleted the fix/switch-offline-transport-backoff branch September 4, 2026 13:07
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.

1 participant