Skip to content

fix: support SetNativeDescription on iOS, macOS, and Android via attribute promotion - #146

Closed
bobbyg603 wants to merge 34 commits into
mainfrom
fix/native-description-all-platforms
Closed

fix: support SetNativeDescription on iOS, macOS, and Android via attribute promotion#146
bobbyg603 wants to merge 34 commits into
mainfrom
fix/native-description-all-platforms

Conversation

@bobbyg603

@bobbyg603 bobbyg603 commented Aug 10, 2026

Copy link
Copy Markdown
Member

Note

Stacked PR. This stacks on #123 (feat/windows-native-crash-reporting) and depends on the Android setter interop from #128, which has already merged into that branch (so the base here is feat/windows-native-crash-reporting directly).

Blocked by BugSplat-Git/src-backend#617 (fixes BugSplat-Git/src-backend#616) — the backend change that promotes the BugSplatDescription attribute to the report's description field. Merge that and deploy before (or with) this.

Fixes #139

What

SetNativeDescription was Windows-only (BugSplat_SetUserDescription); on iOS, macOS, and Android it silently no-oped, so bugsplat.Description = ... never reached native crash reports on those platforms.

This implements it on iOS, macOS, and Android via reserved-attribute promotion: the value is set as an attribute named BugSplatDescription using the same per-platform interop the sibling setters already use — the bugsplat-apple attribute call (_setNativeAttributeIos / _setNativeAttributeMac) on iOS/macOS, and BugSplatBridge.setAttribute on Android (the pattern #128 established for BugSplatUser, BugSplatEmail, BugSplatNotes, and BugSplatApplicationKey). The backend then promotes that attribute into the report's description field. The Windows path is unchanged.

Pre-deploy behavior: until the backend change ships, the value still arrives with the crash report — it just shows as a plain visible attribute named BugSplatDescription instead of populating the description field. Harmless, and not silent data loss; once the backend deploys, promotion applies with no client change.

#if permutation verification

Hand-verified every permutation of the touched conditional block:

  • Editor, any active build target (including targets where UNITY_IOS/UNITY_ANDROID/etc. co-define with UNITY_EDITOR): every branch carries !UNITY_EDITOR, so the whole block compiles out and the method reduces to the nativeCrashReportingEnabled guard (which can never be true in the editor — all constructor native-init paths are !UNITY_EDITOR). The matching extern blocks compile out too, so no unresolved symbols.
  • iOS player (UNITY_IOS && !UNITY_EDITOR): calls _setNativeAttributeIos("BugSplatDescription", description); the extern is declared in the same-guarded #if UNITY_IOS && !UNITY_EDITOR block, and System.Runtime.InteropServices is in scope via the guarded using at the top of the file.
  • macOS player (UNITY_STANDALONE_OSX && !UNITY_EDITOR): calls _setNativeAttributeMac("BugSplatDescription", description); extern declared in the matching #elif block.
  • Windows player (UNITY_STANDALONE_WIN && !UNITY_EDITOR): unchanged BugSplat_SetUserDescription(description) P/Invoke; extern declared in the matching #elif block.
  • Android player (UNITY_ANDROID && !UNITY_EDITOR): AndroidJavaClass/CallStatic come from the unconditional using UnityEngine; — identical shape to the sibling Android branches from fix(android): enable native crash reporting flag and wire supported setters #128.
  • WebGL / Linux / other players: no branch matches; guard-only body, and nativeCrashReportingEnabled is false there regardless.

🤖 Generated with Claude Code

bobbyg603 and others added 30 commits June 11, 2026 12:57
Replaces the Unity crash-folder minidump flow with native Windows crash
reporting (closes #120). Unity's crash reporter produced low-quality
reports; the native BugSplat SDK captures crashes at crash time with
full fidelity, matching the macOS (bugsplat-apple) integration pattern.

- Vendor BugSplat.dll (C API, x64 + ARM64 Release) built from
  bugsplat-windows feat/c-api-dynamic-library (BugSplat-Git/bugsplat-windows#155),
  plus matching BugSplatMonitor.exe/BugSplatRc.dll/BugSplatWer.dll under
  Runtime/Plugins/Windows. Swap to official release binaries when the
  upstream PR ships in a release.
- BugSplat.cs: new useNativeLibWin ctor param; Windows player init via
  BugSplat_Init P/Invoke (quiet mode on, hang detection off, Player.log
  attached, unsent crash retry); native setter wiring for
  attributes/user/email/notes/key/description; new
  SetWindowsCrashDialogEnabled and SetWindowsHangDetectionTimeout knobs.
- BugSplatOptions: UseNativeCrashReportingForWindows,
  WindowsShowCrashDialog, WindowsHangDetectionTimeoutMs.
- PostBuild: copy Monitor/Rc/Wer next to the built .exe (arch from PE
  header), copy LineNumberMappings.json for IL2CPP symbol upload.
- BREAKING: WindowsReporter/INativeCrashReporter removed;
  PostAllCrashes/PostCrash/PostMostRecentCrash obsoleted (unsent crashes
  upload automatically at startup); Post(FileInfo) now works on all
  platforms. Works with both Mono and IL2CPP.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Observed in end-to-end testing: when the hang timeout elapses, the SDK
captures and uploads a hang report, then terminates the process.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rebuilt from bugsplat-windows feat/c-api-dynamic-library. Unity does not
call these native exports (managed feedback uses DotNetStandardClient);
re-vendored only to keep the binary in sync with the upstream branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rebuilt from bugsplat-windows feat/c-api-dynamic-library (20 exports).
Unity does not call the added natives; re-vendored to keep the binary in
sync with the upstream branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The sample scene was authored in a URP + Input System project, leaving
two components that show as "missing script" (and break button clicks) in
any project without those packages:
- UniversalAdditionalLightData on the light -> removed (sample needs no URP)
- InputSystemUIInputModule on the EventSystem -> replaced with the built-in
  UGUI StandaloneInputModule, so buttons work with only com.unity.ugui

The missing input module is why the Crash Native button did nothing without
the Input System package installed. Verified: clean Unity 6000.4.10f1 build
loads the scene with no missing-script warnings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Blank button labels are TextMeshPro with no default font asset imported.
Document the Window > TextMeshPro > Import TMP Essential Resources step in
the README and a new sample README.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
In the editor the native crash button intentionally no-ops; the old
message ("not yet implemented on this platform") read like a bug. Now it
explains native crashes are captured in built players only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The previous change hard-coded StandaloneInputModule in the scene, which
receives no input when Active Input Handling is "Input System Package
(New)" - so the sample buttons did nothing on projects using the new
Input System. Replace it with a SampleInputModule script that adds the
correct module at runtime: InputSystemUIInputModule (+ AssignDefaultActions)
when the Input System package is present, otherwise StandaloneInputModule.

The scene no longer serializes any input module, so there is no
package-specific component to show as a missing script either.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per project direction the sample targets the new Input System only, so
the EventSystem uses the stock InputSystemUIInputModule (referencing the
Input System package default actions) as the sample originally shipped.
Removes the backend-agnostic SampleInputModule script. The sample
requires the Input System package.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…te in Unity 6)

Resolves CS0618 warnings; FindAnyObjectByType is the recommended
replacement and is a safe drop-in for these single-instance lookups.
Also updated the README examples.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The sample buttons highlight and select but never invoked their handlers:
the prefab-instance onClick overrides set the target/method/array-size but
not m_CallState, so each call defaulted to UnityEventCallState.Off and was
skipped at runtime. Copied the corrected scene back from an editor session
where the four buttons were re-wired (m_CallState=RuntimeOnly, m_Mode=Void).

The EventSystem uses InputSystemUIInputModule with the Input System
package's default actions (portable, no project-local actions asset).
Note: the directional light's URP UniversalAdditionalLightData component is
present again (re-added by the URP project the scene was fixed in); it is
harmless and only shows a benign "missing script" note in non-URP projects.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The native handler default (exit()) can hang a standalone Windows player on
CRT shutdown after the report uploads, leaving the process running. Opt into
the new BugSplat_SetTerminateAfterCrash on init so the player exits cleanly
once the crash report is sent. Re-vendored BugSplat.dll (x64 + ARM64, 19
exports) built from bugsplat-windows feat/c-api-dynamic-library.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follows the upstream rename from BugSplat_SetTerminateAfterCrash to the 3-way
BugSplat_SetCrashCompletionBehavior. Unity still opts into Terminate
(BUGSPLAT_CRASH_TERMINATE) so the player exits cleanly after a crash uploads.
Re-vendored BugSplat.dll (x64 + ARM64).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace Utils.ForceCrash (faults in engine internals) with a chain of
[MethodImpl(NoInlining)] C# frames ending in Marshal.WriteInt32(IntPtr.Zero, 0),
a raw access violation. BugSplat's native handler captures it, and on IL2CPP the
frames symbolicate to C# names + line numbers via GameAssembly.pdb +
LineNumberMappings.json - so the report shows a game-code call stack instead of
an engine stack. Works on Mono and IL2CPP; no unsafe code required.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Flip WindowsShowCrashDialog to default true and init quiet mode off, so a native
Windows crash shows the BugSplat dialog out of the box (matching classic BugSplat
Windows UX). Set it false for silent reporting. Sample opts in to the new default.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…olication)

symbol-upload skips files with no debug GUID (added in symbol-upload #189/#191 to
avoid uploading Unreal's binary-encoded Linux .sym files as garbage symbols). That
skip also drops LineNumberMappings.json, so IL2CPP crashes never get C# names/lines.
The tool already uploads .zip files as-is via the versions path, so zip the mapping
first (entry at archive root) and upload the .zip instead of the raw .json.

Applied to all three IL2CPP upload paths: Windows + macOS (shared ZipForUpload
helper) and iOS (zip -j in the Xcode build-phase script). Verified end to end on
Windows; Mac/iOS use the identical mechanism but weren't built in this session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Call BugSplat_SetCrashType(15) on init so native crashes upload as
CrashType.UnityNative. That is the crash type the BugSplat backend uses to apply
LineNumberMappings.json, turning IL2CPP native frames into C# names + line numbers.
Pairs with the PostBuild change that uploads the (zipped) mapping. Re-vendored
BugSplat.dll (x64 + ARM64, 20 exports).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Post(FileInfo): poll the upload Task to completion instead of yielding
  it directly, so coroutine callers that `yield return Post(minidump)`
  block until the upload finishes (mirrors PostFeedback). Surface
  faulted/canceled results to the callback.
- PostCrash / PostMostRecentCrash: invoke callback?.Invoke(null) so
  deprecated no-op APIs keep caller control-flow predictable (matches
  PostAllCrashes).
- PostBuild: recognize x86 PE machine type (0x014C) and short-circuit
  native support-file copy with a clear warning, since native crash
  reporting binaries ship only for x64/ARM64.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.0.0 is a breaking major release, so remove the deprecated Unity
crash-folder APIs outright instead of shipping them as no-op [Obsolete]
shims. Unsent native crash reports upload automatically at startup; the
internal BugSplat_PostAllCrashesAsync P/Invoke (called during init) is
retained. README migration note updated from "obsolete" to "removed".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Vendor the Win32 BugSplat.dll and support files (BugSplatMonitor.exe,
BugSplatRc.dll, BugSplatWer.dll) so 32-bit standalone Windows players get
native crash reporting alongside x64 and ARM64.

The bugsplat-windows BugSplatDynamic project already builds Release|Win32;
the 32-bit DLL exports the same 20 undecorated BugSplat_* names, so the
existing P/Invoke (DllImport "BugSplat", Cdecl) resolves on x86 with no
runtime change. PostBuild no longer skips x86 — it copies the Support~/x86
files next to the built .exe (arch from the PE header machine field).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
….0 release

All 12 vendored binaries (BugSplat.dll x86/x64/ARM64 + Monitor/Rc/Wer
support trios) now come from the official BugSplat.zip v8 release:
Authenticode-signed (signer: BugSplat), FileVersion 8.0.0.0, all 21
undecorated BugSplat_* C exports verified present on every arch, and
BugSplatC.h in the release is identical to the header this integration
was built against.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fail-fast terminations - stack buffer overrun (0xC0000409), heap corruption
(0xC0000374), __fastfail - bypass every in-process exception handler, so
BugSplat's crash filter never sees them. BugSplat_Init already registers
BugSplatWer.dll to catch them and PostBuild already ships it next to the
executable, but registration silently no-ops without an HKLM allowlist value
naming that DLL. Nothing wrote it, documented it, or reported it missing, so
that capture path was inert and there was no way to tell.

- BugSplat.WindowsWerEnabled surfaces the SDK's real registration state via
  the new BugSplat_IsWerEnabled export. The call is guarded against
  EntryPointNotFoundException so this works against pre-8.0.1 binaries.
- Init logs what is lost and how to fix it when WER is not armed. Only a
  warning in development builds: the value is absent on virtually every
  end-user machine and a player cannot act on it.
- BugSplat > Windows > Register WER Handler writes the value elevated for a
  built player, and reads it back rather than trusting reg.exe's exit code.
  Both registry views are written because a 32-bit player is handled by the
  SysWOW64 WerFault.
- The sample gains a scrollable scenario menu covering all four capture
  paths. Every trigger is C# plus system-DLL P/Invokes, so no crasher DLL is
  shipped - Unity never unloads a native plugin once loaded in the editor.
  Native rows are inert in the editor behind a compile-time guard, a runtime
  gate, and a disabled button.

The background-thread managed exception scenario is labelled as a known gap:
Unity only raises logMessageReceived for main-thread logs, so those produce
no report today. Tracked separately.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Isolated in its own commit so it can be reverted independently if the scene
regresses. The diff is one GameObject, one MonoBehaviour, and one Transform -
no Button, no m_OnClick, no prefab modification. The menu builds its own UI in
code, which is what keeps the scene free of the persistent UnityEvent wiring
that silently shipped inert buttons before (fb16f58).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a Windows Error Reporting section covering which crashes need it, the
two preconditions, the installer's responsibility for the HKLM value (with
snippets for both add and uninstall), the editor menu item for local builds,
and where to look when a report does not arrive.

Also corrects two stale claims: the crash dialog is shown by default, not
suppressed (changed in ee63c86), and the sample's Windows native crash has
used a C# frame chain writing through a null pointer since a683891, not
Utils.ForceCrash.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Picks up BugSplat_IsWerEnabled (bugsplat-windows#162), which the WER detection
added in 04432a0 P/Invokes. Until now that call was resolving through the
EntryPointNotFoundException fallback against v8.0.0 binaries, so
WindowsWerEnabled always reported false.

All 12 files verified before vendoring: Authenticode signature Valid, signer
BugSplat, FileVersion 8.1.0.0; 22 undecorated BugSplat_* exports on x86, x64 and
ARM64 including BugSplat_IsWerEnabled; and the release BugSplatC.h byte-identical
to the header this integration was built against.

Swapped as a set per architecture - BugSplat.dll and BugSplatMonitor.exe are
coupled through SHARED_MEMORY_LAYOUT_VERSION. No .meta changes, so plugin GUIDs
and import settings are preserved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The export shipped in bugsplat-windows 8.1.0, not 8.0.1. The guard itself is
unchanged; only the comment naming the minimum version was wrong.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The sample had two overlapping UIs: a four-button grid driven by
ErrorGenerator and a Crash Scenarios menu behind a launcher button, whose
rows duplicated the grid. Two sources of truth for the same triggers, and the
grid's buttons were scene-authored prefab instances whose onClick wiring has
silently broken before.

The scenario list is now the whole scene. Sections group scenarios by the
mechanism expected to capture them - MANAGED, NATIVE, FAIL-FAST, HANG,
FEEDBACK - because a report arriving only means something if it arrived by the
path under test. Sections compile per build target, so a Windows player offers
Windows' native, fail-fast, and hang scenarios while macOS, iOS, and Android
each offer theirs; Linux and WebGL get managed and feedback only, and the
status line says so.

The per-platform native triggers move from ErrorGenerator into CrashScenarios
unchanged, including the Android UI-thread dispatch needed for an ANR and the
iOS/macOS __Internal externs.

Native access violations now fault inside RtlMoveMemory rather than
dereferencing null from C#. On the Mono backend a null write from managed code
is not a crash at all: Mono's vectored handler claims faults whose instruction
pointer is in JIT'd code and rethrows them as a managed NullReferenceException,
so the player caught an exception and kept running while the native handler saw
nothing. Faulting inside ntdll puts the exception where Mono has no unwind info,
so it declines and BugSplat's filter gets it - the same reason the custom SEH
scenario already worked. Stack overflow has no such workaround; Mono guards the
stack and raises a managed StackOverflowException, so that row now says what to
expect for the backend it was built with.

FAIL-FAST rows grey out when the WER handler is not registered, replacing their
description with how to register it, since running them would terminate the
player and report nothing.

Deleted: ErrorGenerator, BugSplatLayoutButtons, PlatformDependentObject, the
button prefab, the four grid instances, the corner feedback button, and two
inactive objects that carried no behaviour. The scene shrinks from 2062 lines
to 929 and now holds only the menu component and its two button sprites.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bobbyg603 and others added 4 commits August 10, 2026 18:50
Post yielded a Task to Unity's coroutine scheduler, which treats unknown
yield values as a single-frame wait, so the coroutine completed before
the upload finished and the report could be lost if the app quit right
after. The Task.Run body also parsed the response, logged, and invoked
the user callback on a threadpool thread, where Unity APIs are unsafe.

Keep only the network I/O inside the task, poll IsCompleted like the
feedback and minidump coroutines in BugSplat.cs, and run response
parsing, logging, and callbacks on the coroutine after the task
completes, routing faulted tasks through the existing error path.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…etters (#128)

* fix(android): enable native crash reporting flag and wire supported setters

The Android init branch never set nativeCrashReportingEnabled, so every
native setter early-returned and silently no-oped on Android. Set the
flag after initBugSplat and route SetNativeAttribute through
BugSplatBridge.setAttribute, the only post-init mutator the bridge
exposes. Document that user, email, notes, and log-file attachment
remain no-ops on Android.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(android): route user, email, notes, and key through reserved attributes

The backend's SetAttributeOverrides promotes the reserved attribute
names BugSplatUser, BugSplatEmail, BugSplatNotes, and
BugSplatApplicationKey to first-class report fields for any
minidump-based report, so the Android bridge's setAttribute can carry
values the bridge has no dedicated setters for. Wire SetNativeUser,
SetNativeEmail, SetNativeNotes, and SetNativeKey through those names
and drop the no-op doc wording.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* ci: add a workflow that runs the test suite

The suite had never run anywhere: it failed to compile on every build target
(audit A1/A2), and there was no CI to notice (audit J1).

The workflow runs the suite on StandaloneLinux64 and WebGL. This repo is a UPM
package rather than a Unity project, so it generates a host project that
embeds the package and lists it under testables. Requires a UNITY_LICENSE or
UNITY_EMAIL/PASSWORD/SERIAL secret.

Carries the A1/A2 compile fix as well, because without it there is no target
on which the suite builds and the workflow would have nothing to run. That fix
is identical to #129; whichever lands first makes the other a no-op.

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

* ci: add a timeout, concurrency, and a fork guard to the test workflow

A hung Unity batchmode run would otherwise burn the job's full six hours,
and superseded pushes kept running. Fork PRs cannot see the license secret,
so they would always report a red check the contributor has no way to fix;
skip them instead.

Reference the artifact path directly rather than an action output.

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

* ci: accept either a personal or a Pro Unity license

Wiring only UNITY_LICENSE forced a workflow edit for anyone activating with
a serial. Pass both sets of credentials and let GameCI pick.

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

* ci: keep Unity Pro activations bounded to one at a time

A Pro serial allows only a couple of concurrent activations, and each matrix
job consumes one. Run the targets serially so a workflow run needs a single
activation, and stop cancelling superseded runs: cancellation can skip
GameCI's license return step, leaking activations until the seat is
exhausted and every later run fails to activate.

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

* ci: always return the Unity license

Every container is a new machine to Unity, so each run consumes a serial
activation. A run that dies before returning it burns that slot against a
machine that no longer exists, and it has to be revoked by hand.

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

* ci: move off Node 20 actions

checkout, cache, and upload-artifact were all pinned to majors that still
declare node20. Bump to the current majors, which run on node24.

game-ci/unity-test-runner@v4 also declares node20 and stays as-is: v4.3.1 is
the latest release, so there is no newer tag to move to. Their main branch
has already switched to node24, so this resolves when they cut a release.

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

* ci: run the suite against the Windows and macOS targets

DotNetStandardExceptionReporter guards its log-capture paths on
UNITY_STANDALONE_WIN and UNITY_STANDALONE_OSX without excluding the editor,
so those branches only compile when the matching target is active. Linux and
WebGL alone left them unbuilt.

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

* ci: compile the player scripts for iOS and Android

Tests run inside the editor, so every path behind `&& !UNITY_EDITOR` — the
whole native crash surface, plus the iOS and Android blocks in PostBuild.cs —
is excluded from the assemblies they link against. No test leg can catch a
break there.

Compiling the player scripts does build that code. Verified locally by
introducing an error inside `#elif UNITY_ANDROID && !UNITY_EDITOR`: the check
exits 1 and reports it, and passes once reverted.

PlayerBuildInterface.CompilePlayerScripts rather than a full player build:
same compile coverage without needing scenes, build settings, or the time.

Runs after the test job because only one Unity activation is available.

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

* ci: only return the license explicitly when a run is interrupted

The explicit return took ~70s a job to repeat what GameCI's own post step had
already done: that post step succeeded on the clean run and only failed on the
cancelled one, matching unity-builder#538, which is specifically about failed
or hung runs. Restrict it to that case.

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

* test: cover the CreateFromOptions mapping

CreateFromOptions copies ten fields from BugSplatOptions onto BugSplat. A
dropped line there discards a user's configuration silently: nothing throws,
nothing logs, and the report simply goes out without the setting. Nothing
covered it.

Every value in the mapping test is set away from its default, since asserting
a field that already holds the value it would have had anyway passes whether
or not the mapping exists.

Description, Email, Key, Notes, and User were set-only, so none of them could
be asserted. Added getters mirroring LogFileMaxSizeMB, which already had one
- the asymmetry looks like an oversight rather than a decision, and reading
back a value you set is reasonable for callers too.

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

* fix: apply the attributes configured on BugSplatOptions (#136)

Closes #134

BugSplatOptions.Attributes had no effect. CreateFromOptions never read it, so
attributes authored on the asset never reached a report, silently.

It could not have worked anyway: the field was a Dictionary<string, string>,
which Unity cannot serialize, so it was unset from the inspector regardless.
Unity warned about this on every build (UAC1009).

Authored as a list of name/value pairs instead, which does serialize, and
copied onto the client in CreateFromOptions. Entries with no name are skipped
and a null value becomes an empty string, so a half-filled inspector row
cannot produce a malformed attribute.

BREAKING: BugSplatOptions.Attributes is now List<BugSplatAttribute> rather
than Dictionary<string, string>. Code assigning a dictionary to it will not
compile - though that code could never have had any effect.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

* test: cover the log tail truncation, and fix its null guard (#137)

CopyLogTailToTempFile implements the log truncation from #110 and had no
coverage. It decides how much of a log to upload, so getting the offset wrong
silently ships the start of a log instead of the end - the part that actually
describes the crash.

The tests use position-dependent bytes rather than a repeated pattern, so a
copy taken from the wrong offset fails rather than passing by coincidence.

Fixed a null guard that could not fire: the null branch dereferenced the same
argument it was checking, so a null FileInfo threw NullReferenceException
instead of returning null.

Made the method internal to test it; the test assembly already has
InternalsVisibleTo.

Refs #135

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…ibute promotion

The BugSplat backend promotes an attribute named BugSplatDescription to
the report's description field, so platforms without a dedicated native
description API route through the same attribute interop the sibling
setters use: the bugsplat-apple attribute call on iOS and macOS, and
BugSplatBridge.setAttribute on Android. The Windows path is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bobbyg603

Copy link
Copy Markdown
Member Author

Superseded by #219 — same commit, cherry-picked onto main.

This branch was cut from feat/windows-native-crash-reporting while #123 was open. #123 was squash-merged, so main carries that work as a single commit with no shared history with this branch, and this PR's diff against main is now 57 files — all of #123 replayed.

It also no longer merges. A test merge produces a rename/rename conflict on ErrorGenerator.cs.meta, and the content conflicts would revert #126's sample changes: this branch predates them, so its CrashScenarios.cs still says KNOWN GAP: appears in Player.log but produces no report and would overwrite what is on main today.

#219 has the identical change (9 insertions, 2 deletions in Runtime/BugSplat.cs), compiled clean under each of UNITY_IOS, UNITY_STANDALONE_OSX, UNITY_ANDROID, and UNITY_STANDALONE_WIN. Safe to close this one in favour of it.

@bobbyg603

Copy link
Copy Markdown
Member Author

Superseded by #219

@bobbyg603 bobbyg603 closed this Aug 11, 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.

SetNativeDescription is a no-op everywhere except Windows

1 participant