From 95cd6c26a32bbfc55ae9c861b774820c04c562c0 Mon Sep 17 00:00:00 2001 From: hatayama Date: Wed, 29 Jul 2026 00:25:37 +0900 Subject: [PATCH 1/3] feat: add ErrorCode and RecommendedNextAction to pause point failure responses Enable and clear validation failures previously returned only Success=false and an English Message, so callers had to substring-match prose and RecommendedNextAction stayed empty. Populate machine-readable ErrorCode and RecommendedNextAction on all seven failure paths so agents can branch on codes (especially PAUSE_POINT_RELEASE_CODE_OPTIMIZATION after launch -r). Refs #2045. Co-authored-by: Cursor --- .../PausePointEnableFailureErrorCodeTests.cs | 168 ++++++++++++++++++ ...sePointEnableFailureErrorCodeTests.cs.meta | 11 ++ .../PausePoint/PausePointTools.cs | 41 ++++- .../PausePoint/SourcePausePointConstants.cs | 26 +++ 4 files changed, 238 insertions(+), 8 deletions(-) create mode 100644 Assets/Tests/Editor/PausePointEnableFailureErrorCodeTests.cs create mode 100644 Assets/Tests/Editor/PausePointEnableFailureErrorCodeTests.cs.meta diff --git a/Assets/Tests/Editor/PausePointEnableFailureErrorCodeTests.cs b/Assets/Tests/Editor/PausePointEnableFailureErrorCodeTests.cs new file mode 100644 index 000000000..940559ac7 --- /dev/null +++ b/Assets/Tests/Editor/PausePointEnableFailureErrorCodeTests.cs @@ -0,0 +1,168 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using NUnit.Framework; + +using io.github.hatayama.UnityCliLoop.FirstPartyTools; +using io.github.hatayama.UnityCliLoop.Runtime; + +namespace io.github.hatayama.UnityCliLoop.Tests.Editor +{ + /// + /// Verifies validation-failure responses carry machine-readable ErrorCode and RecommendedNextAction. + /// + [TestFixture] + public sealed class PausePointEnableFailureErrorCodeTests + { + private FakePausePointPauseController _pauseController; + + [SetUp] + public void SetUp() + { + _pauseController = new FakePausePointPauseController(); + UloopPausePointRegistry.ConfigureForTests(_pauseController, () => DateTime.UtcNow); + } + + [TearDown] + public void TearDown() + { + UloopPausePointRegistry.ResetForTests(); + } + + /// + /// Verifies a non-positive timeout returns INVALID_ARGUMENT with a non-empty next action. + /// + [Test] + public async Task Enable_WhenTimeoutSecondsIsZero_ReturnsInvalidArgumentErrorCode() + { + EnablePausePointTool tool = new(); + JObject parameters = new() + { + ["id"] = "jump", + ["timeoutSeconds"] = 0 + }; + + PausePointResponse response = (PausePointResponse)await tool.ExecuteAsync(parameters, CancellationToken.None); + + Assert.That(response.Success, Is.False); + Assert.That(response.ErrorCode, Is.EqualTo(SourcePausePointConstants.ErrorCodeInvalidArgument)); + Assert.That(response.RecommendedNextAction, Is.Not.Empty); + } + + /// + /// Verifies specifying both id and file:line returns INVALID_ARGUMENT with a non-empty next action. + /// + [Test] + public async Task Enable_WhenIdAndFileLineAreBothSpecified_ReturnsInvalidArgumentErrorCode() + { + EnablePausePointTool tool = new(); + JObject parameters = new() + { + ["id"] = "jump", + ["file"] = "Assets/Tests/Editor/PausePointToolModeTests.cs", + ["line"] = 10, + ["timeoutSeconds"] = 30 + }; + + PausePointResponse response = (PausePointResponse)await tool.ExecuteAsync(parameters, CancellationToken.None); + + Assert.That(response.Success, Is.False); + Assert.That(response.ErrorCode, Is.EqualTo(SourcePausePointConstants.ErrorCodeInvalidArgument)); + Assert.That(response.RecommendedNextAction, Is.Not.Empty); + } + + /// + /// Verifies an unknown capture mode returns INVALID_ARGUMENT with a non-empty next action. + /// + [Test] + public async Task Enable_WhenModeIsUnknown_ReturnsInvalidArgumentErrorCode() + { + EnablePausePointTool tool = new(); + JObject parameters = new() + { + ["id"] = "jump", + ["mode"] = "bogus-mode", + ["timeoutSeconds"] = 30 + }; + + PausePointResponse response = (PausePointResponse)await tool.ExecuteAsync(parameters, CancellationToken.None); + + Assert.That(response.Success, Is.False); + Assert.That(response.ErrorCode, Is.EqualTo(SourcePausePointConstants.ErrorCodeInvalidArgument)); + Assert.That(response.RecommendedNextAction, Is.Not.Empty); + } + + /// + /// Verifies clear without id or --all returns INVALID_ARGUMENT with a non-empty next action. + /// + [Test] + public async Task Clear_WhenIdIsEmptyAndAllIsFalse_ReturnsInvalidArgumentErrorCode() + { + ClearPausePointTool tool = new(); + JObject parameters = new() + { + ["id"] = "", + ["all"] = false + }; + + PausePointResponse response = (PausePointResponse)await tool.ExecuteAsync(parameters, CancellationToken.None); + + Assert.That(response.Success, Is.False); + Assert.That(response.ErrorCode, Is.EqualTo(SourcePausePointConstants.ErrorCodeInvalidArgument)); + Assert.That(response.RecommendedNextAction, Is.Not.Empty); + } + + /// + /// Verifies an unresolvable file:line returns PAUSE_POINT_RESOLVE_FAILED with a non-empty next action. + /// + [Test] + public async Task Enable_WhenFileDoesNotExist_ReturnsResolveFailedErrorCode() + { + EnablePausePointTool tool = new(); + JObject parameters = new() + { + ["file"] = "Assets/DoesNotExist/NoSuchScript.cs", + ["line"] = 10, + ["timeoutSeconds"] = 30 + }; + + PausePointResponse response = (PausePointResponse)await tool.ExecuteAsync(parameters, CancellationToken.None); + + Assert.That(response.Success, Is.False); + Assert.That(response.ErrorCode, Is.EqualTo(SourcePausePointConstants.ErrorCodeResolveFailed)); + Assert.That(response.RecommendedNextAction, Is.Not.Empty); + } + + /// + /// Verifies ErrorCode serializes under the exact wire name callers will match. + /// + [Test] + public void PausePointResponse_WhenErrorCodeIsSet_SerializesErrorCodeWireName() + { + string json = JsonConvert.SerializeObject(new PausePointResponse { Success = false, ErrorCode = "X" }); + + Assert.That(json, Does.Contain("\"ErrorCode\":\"X\"")); + } + + private sealed class FakePausePointPauseController : IUloopPausePointPauseController + { + public int PauseCount { get; private set; } + public bool IsPlaying => true; + public bool IsPaused => PauseCount > 0; + + public void Pause() + { + PauseCount++; + } + + public void Resume() + { + // Why zero: Unity's isPaused is a bool; Option B Resume must fully clear pause. + PauseCount = 0; + } + } + } +} diff --git a/Assets/Tests/Editor/PausePointEnableFailureErrorCodeTests.cs.meta b/Assets/Tests/Editor/PausePointEnableFailureErrorCodeTests.cs.meta new file mode 100644 index 000000000..c4ba0c547 --- /dev/null +++ b/Assets/Tests/Editor/PausePointEnableFailureErrorCodeTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2a3ff0a1e6e074600bb85254065d84dc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/src/Editor/FirstPartyTools/PausePoint/PausePointTools.cs b/Packages/src/Editor/FirstPartyTools/PausePoint/PausePointTools.cs index ecc42d65a..f782e945b 100644 --- a/Packages/src/Editor/FirstPartyTools/PausePoint/PausePointTools.cs +++ b/Packages/src/Editor/FirstPartyTools/PausePoint/PausePointTools.cs @@ -79,6 +79,7 @@ public class PausePointResponse : UnityCliLoopToolResponse public int ClearedCount { get; set; } public string Message { get; set; } = string.Empty; public string RecommendedNextAction { get; set; } = string.Empty; + public string ErrorCode { get; set; } = string.Empty; public string Warning { get; set; } = string.Empty; public string ClearedReason { get; set; } = string.Empty; public string StatusBeforeClear { get; set; } = string.Empty; @@ -313,18 +314,27 @@ public PausePointResponse Enable(EnablePausePointSchema parameters) string captureSettingsError = ValidateCaptureSettings(parameters); if (captureSettingsError != null) { - return CreateValidationFailure(captureSettingsError); + return CreateValidationFailure( + captureSettingsError, + SourcePausePointConstants.ErrorCodeInvalidArgument, + "Fix the rejected capture argument described in Message and re-run; uloop enable-pause-point --help lists the accepted values."); } string modeError = ValidateEnableMode(parameters); if (modeError != null) { - return CreateValidationFailure(modeError); + return CreateValidationFailure( + modeError, + SourcePausePointConstants.ErrorCodeInvalidArgument, + "Re-run with either --id alone, or --file and --line together."); } if (parameters.TimeoutSeconds <= 0) { - return CreateValidationFailure("TimeoutSeconds must be greater than zero."); + return CreateValidationFailure( + "TimeoutSeconds must be greater than zero.", + SourcePausePointConstants.ErrorCodeInvalidArgument, + "Re-run with --timeout-seconds set to a positive integer."); } if (!string.IsNullOrWhiteSpace(parameters.File)) @@ -384,7 +394,10 @@ public PausePointResponse Clear(ClearPausePointSchema parameters) string idError = ValidateId(parameters.Id); if (idError != null) { - return CreateValidationFailure(idError); + return CreateValidationFailure( + idError, + SourcePausePointConstants.ErrorCodeInvalidArgument, + "Pass --id with the id returned by enable-pause-point, or use --all to clear every marker."); } (UloopPausePointSnapshot snapshot, bool resumedFromPause) = UloopPausePointRegistry.Clear(parameters.Id); @@ -437,13 +450,19 @@ private static PausePointResponse EnableBySourceLocation(EnablePausePointSchema { if (CompilationPipeline.codeOptimization == CodeOptimization.Release) { - return CreateValidationFailure(SourcePausePointConstants.ReleaseCodeOptimizationRejectionMessage); + return CreateValidationFailure( + SourcePausePointConstants.ReleaseCodeOptimizationRejectionMessage, + SourcePausePointConstants.ErrorCodeReleaseCodeOptimization, + SourcePausePointConstants.ReleaseCodeOptimizationRecommendedNextAction); } SourcePausePointResolveResult resolveResult = SourcePausePointResolver.Resolve(parameters.File, parameters.Line); if (!resolveResult.Success) { - return CreateValidationFailure(resolveResult.ErrorMessage); + return CreateValidationFailure( + resolveResult.ErrorMessage, + SourcePausePointConstants.ErrorCodeResolveFailed, + SourcePausePointConstants.ResolveFailedRecommendedNextAction); } string id = BuildSourcePausePointId(parameters.File, parameters.Line); @@ -453,6 +472,7 @@ private static PausePointResponse EnableBySourceLocation(EnablePausePointSchema return new PausePointResponse { Success = false, + ErrorCode = SourcePausePointConstants.ErrorCodePatchFailed, Message = patchResult.ErrorMessage, RecommendedNextAction = patchResult.Hint }; @@ -627,12 +647,17 @@ private static string ValidateId(string id) return null; } - private static PausePointResponse CreateValidationFailure(string message) + private static PausePointResponse CreateValidationFailure( + string message, + string errorCode, + string recommendedNextAction) { return new PausePointResponse { Success = false, - Message = message + Message = message, + ErrorCode = errorCode, + RecommendedNextAction = recommendedNextAction }; } diff --git a/Packages/src/Editor/FirstPartyTools/PausePoint/SourcePausePointConstants.cs b/Packages/src/Editor/FirstPartyTools/PausePoint/SourcePausePointConstants.cs index 532bd28bc..e7f085af4 100644 --- a/Packages/src/Editor/FirstPartyTools/PausePoint/SourcePausePointConstants.cs +++ b/Packages/src/Editor/FirstPartyTools/PausePoint/SourcePausePointConstants.cs @@ -135,5 +135,31 @@ internal static class SourcePausePointConstants "Enabling a pause point by file and line requires Debug code optimization. The project " + "is currently set to Release; switch the Editor's Code Optimization mode to Debug " + "(the bug icon in the main toolbar) and recompile, then retry."; + + // Machine-readable failure codes for enable/clear validation responses. Callers branch on + // these instead of English Message substrings; names follow the existing PAUSE_POINT_* + // vocabulary used by the CLI error envelope. + public const string ErrorCodeInvalidArgument = "INVALID_ARGUMENT"; + public const string ErrorCodeReleaseCodeOptimization = "PAUSE_POINT_RELEASE_CODE_OPTIMIZATION"; + public const string ErrorCodeResolveFailed = "PAUSE_POINT_RESOLVE_FAILED"; + public const string ErrorCodePatchFailed = "PAUSE_POINT_PATCH_FAILED"; + + // Why: Debug mode is lost on every Editor restart (including uloop launch -r), so the + // recovery steps must remind callers to re-switch after restart rather than only once. + public const string ReleaseCodeOptimizationRecommendedNextAction = + "Switch the Editor to Debug code optimization, recompile, then re-run the same enable " + + "command: (1) uloop execute-dynamic-code --code \"UnityEditor.Compilation.CompilationPipeline.codeOptimization " + + "= UnityEditor.Compilation.CodeOptimization.Debug; return UnityEditor.Compilation.CompilationPipeline" + + ".codeOptimization.ToString();\" (2) uloop compile. Note: the Debug setting reverts to the " + + "'Code Optimization On Startup' preference whenever the Editor restarts, including uloop launch -r."; + + // Why: resolve failures have several distinct root causes (wrong path form, non-executable + // line, stale PDBs after a Code Optimization switch); the skill troubleshooting reference + // covers the patterns so this next-action stays short and stable. + public const string ResolveFailedRecommendedNextAction = + "Check that --file is the project-relative path Unity shows (Assets/... or Packages//...) " + + "and that --line is on or after an executable statement inside a method body. After a code edit " + + "or a Code Optimization switch, run uloop compile and retry. See the pause-point skill's " + + "troubleshooting reference for specific failure patterns."; } } From 069ec01e2cdad8a3b8e054aaca729d15b9596157 Mon Sep 17 00:00:00 2001 From: hatayama Date: Wed, 29 Jul 2026 00:25:44 +0900 Subject: [PATCH 2/3] docs: document pause point failure error codes and Debug-mode loss on editor restart Teach agents to branch on ErrorCode instead of Message prose, and warn that Debug code optimization reverts on every Editor restart including launch -r. Co-authored-by: Cursor --- .agents/skills/uloop-pause-point/SKILL.md | 4 ++-- .claude/skills/uloop-pause-point/SKILL.md | 4 ++-- Packages/src/Editor/CliOnlyTools~/PausePoint/Skill/SKILL.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.agents/skills/uloop-pause-point/SKILL.md b/.agents/skills/uloop-pause-point/SKILL.md index d68d90dfd..178a352a1 100644 --- a/.agents/skills/uloop-pause-point/SKILL.md +++ b/.agents/skills/uloop-pause-point/SKILL.md @@ -197,8 +197,8 @@ For `ResumePlayResult` semantics, why `Time.timeScale = 0` is not a substitute f ## Requirements & Safety -- **Debug code optimization is required.** When the Editor's Code Optimization mode is Release, enable is rejected with instructions; switch to Debug via the bug icon in the main toolbar, recompile, then retry. +- **Debug code optimization is required.** When the Editor's Code Optimization mode is Release, enable is rejected with `ErrorCode: PAUSE_POINT_RELEASE_CODE_OPTIMIZATION`; follow `RecommendedNextAction` to switch to Debug and recompile. The Debug setting does not survive an Editor restart, including `uloop launch -r` — it reverts to the Preferences > General > Code Optimization On Startup value, so after any restart expect to switch and recompile again before arming pause points. - **Patches do not survive compiles or domain reloads.** Any script compile or domain reload removes every source pause point together with its marker, leaving the code exactly as compiled. Re-enable after the reload finishes. This is also why an interrupted CLI session never leaves stale patches behind. - **`uloop compile` while PlayMode is running triggers this same domain reload.** The reload also resets the running PlayMode session itself, so the game state you had arranged (scene, spawned objects, progress) is gone too. Re-enter PlayMode and re-enable the pause point; do not assume the paused scenario is still intact. -- If `enable-pause-point` fails, read the failure `Message` and `RecommendedNextAction`: they name the exact next step, for example waiting for a reload to finish, re-resolving after a recompile, or what to do when the method cannot be patched. Enable-failure specifics (for example "No sequence point found") are covered in [references/troubleshooting.md](references/troubleshooting.md). +- If `enable-pause-point` fails, branch on the failure `ErrorCode` and follow `RecommendedNextAction`; `Message` explains the rejection in prose. Codes: `INVALID_ARGUMENT` (fix the rejected argument and re-run), `PAUSE_POINT_RELEASE_CODE_OPTIMIZATION` (switch to Debug and recompile), `PAUSE_POINT_RESOLVE_FAILED` (the file:line could not be mapped to a patch location), `PAUSE_POINT_PATCH_FAILED` (the resolved method cannot be patched). Enable-failure specifics (for example "No sequence point found") are covered in [references/troubleshooting.md](references/troubleshooting.md). - For scripts under `Packages/`, pass the package-id form of the path — `Packages//...`, exactly as the Unity Project window and console stack traces show it. The physical checkout path of an embedded package does not resolve. diff --git a/.claude/skills/uloop-pause-point/SKILL.md b/.claude/skills/uloop-pause-point/SKILL.md index d68d90dfd..178a352a1 100644 --- a/.claude/skills/uloop-pause-point/SKILL.md +++ b/.claude/skills/uloop-pause-point/SKILL.md @@ -197,8 +197,8 @@ For `ResumePlayResult` semantics, why `Time.timeScale = 0` is not a substitute f ## Requirements & Safety -- **Debug code optimization is required.** When the Editor's Code Optimization mode is Release, enable is rejected with instructions; switch to Debug via the bug icon in the main toolbar, recompile, then retry. +- **Debug code optimization is required.** When the Editor's Code Optimization mode is Release, enable is rejected with `ErrorCode: PAUSE_POINT_RELEASE_CODE_OPTIMIZATION`; follow `RecommendedNextAction` to switch to Debug and recompile. The Debug setting does not survive an Editor restart, including `uloop launch -r` — it reverts to the Preferences > General > Code Optimization On Startup value, so after any restart expect to switch and recompile again before arming pause points. - **Patches do not survive compiles or domain reloads.** Any script compile or domain reload removes every source pause point together with its marker, leaving the code exactly as compiled. Re-enable after the reload finishes. This is also why an interrupted CLI session never leaves stale patches behind. - **`uloop compile` while PlayMode is running triggers this same domain reload.** The reload also resets the running PlayMode session itself, so the game state you had arranged (scene, spawned objects, progress) is gone too. Re-enter PlayMode and re-enable the pause point; do not assume the paused scenario is still intact. -- If `enable-pause-point` fails, read the failure `Message` and `RecommendedNextAction`: they name the exact next step, for example waiting for a reload to finish, re-resolving after a recompile, or what to do when the method cannot be patched. Enable-failure specifics (for example "No sequence point found") are covered in [references/troubleshooting.md](references/troubleshooting.md). +- If `enable-pause-point` fails, branch on the failure `ErrorCode` and follow `RecommendedNextAction`; `Message` explains the rejection in prose. Codes: `INVALID_ARGUMENT` (fix the rejected argument and re-run), `PAUSE_POINT_RELEASE_CODE_OPTIMIZATION` (switch to Debug and recompile), `PAUSE_POINT_RESOLVE_FAILED` (the file:line could not be mapped to a patch location), `PAUSE_POINT_PATCH_FAILED` (the resolved method cannot be patched). Enable-failure specifics (for example "No sequence point found") are covered in [references/troubleshooting.md](references/troubleshooting.md). - For scripts under `Packages/`, pass the package-id form of the path — `Packages//...`, exactly as the Unity Project window and console stack traces show it. The physical checkout path of an embedded package does not resolve. diff --git a/Packages/src/Editor/CliOnlyTools~/PausePoint/Skill/SKILL.md b/Packages/src/Editor/CliOnlyTools~/PausePoint/Skill/SKILL.md index d68d90dfd..178a352a1 100644 --- a/Packages/src/Editor/CliOnlyTools~/PausePoint/Skill/SKILL.md +++ b/Packages/src/Editor/CliOnlyTools~/PausePoint/Skill/SKILL.md @@ -197,8 +197,8 @@ For `ResumePlayResult` semantics, why `Time.timeScale = 0` is not a substitute f ## Requirements & Safety -- **Debug code optimization is required.** When the Editor's Code Optimization mode is Release, enable is rejected with instructions; switch to Debug via the bug icon in the main toolbar, recompile, then retry. +- **Debug code optimization is required.** When the Editor's Code Optimization mode is Release, enable is rejected with `ErrorCode: PAUSE_POINT_RELEASE_CODE_OPTIMIZATION`; follow `RecommendedNextAction` to switch to Debug and recompile. The Debug setting does not survive an Editor restart, including `uloop launch -r` — it reverts to the Preferences > General > Code Optimization On Startup value, so after any restart expect to switch and recompile again before arming pause points. - **Patches do not survive compiles or domain reloads.** Any script compile or domain reload removes every source pause point together with its marker, leaving the code exactly as compiled. Re-enable after the reload finishes. This is also why an interrupted CLI session never leaves stale patches behind. - **`uloop compile` while PlayMode is running triggers this same domain reload.** The reload also resets the running PlayMode session itself, so the game state you had arranged (scene, spawned objects, progress) is gone too. Re-enter PlayMode and re-enable the pause point; do not assume the paused scenario is still intact. -- If `enable-pause-point` fails, read the failure `Message` and `RecommendedNextAction`: they name the exact next step, for example waiting for a reload to finish, re-resolving after a recompile, or what to do when the method cannot be patched. Enable-failure specifics (for example "No sequence point found") are covered in [references/troubleshooting.md](references/troubleshooting.md). +- If `enable-pause-point` fails, branch on the failure `ErrorCode` and follow `RecommendedNextAction`; `Message` explains the rejection in prose. Codes: `INVALID_ARGUMENT` (fix the rejected argument and re-run), `PAUSE_POINT_RELEASE_CODE_OPTIMIZATION` (switch to Debug and recompile), `PAUSE_POINT_RESOLVE_FAILED` (the file:line could not be mapped to a patch location), `PAUSE_POINT_PATCH_FAILED` (the resolved method cannot be patched). Enable-failure specifics (for example "No sequence point found") are covered in [references/troubleshooting.md](references/troubleshooting.md). - For scripts under `Packages/`, pass the package-id form of the path — `Packages//...`, exactly as the Unity Project window and console stack traces show it. The physical checkout path of an embedded package does not resolve. From f41b81aaa75d6cb621290d47109433e049f70fdb Mon Sep 17 00:00:00 2001 From: hatayama Date: Wed, 29 Jul 2026 00:46:29 +0900 Subject: [PATCH 3/3] test: serialize ErrorCode wire-name pin through production JSON settings Bare JsonConvert.SerializeObject would not catch ContractResolver renames on the JsonRpcResponseFactory path; use UnityCliLoopJsonResponseSerializerSettings. Co-authored-by: Cursor --- .../Tests/Editor/PausePointEnableFailureErrorCodeTests.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Assets/Tests/Editor/PausePointEnableFailureErrorCodeTests.cs b/Assets/Tests/Editor/PausePointEnableFailureErrorCodeTests.cs index 940559ac7..5decff039 100644 --- a/Assets/Tests/Editor/PausePointEnableFailureErrorCodeTests.cs +++ b/Assets/Tests/Editor/PausePointEnableFailureErrorCodeTests.cs @@ -8,6 +8,7 @@ using io.github.hatayama.UnityCliLoop.FirstPartyTools; using io.github.hatayama.UnityCliLoop.Runtime; +using io.github.hatayama.UnityCliLoop.ToolContracts; namespace io.github.hatayama.UnityCliLoop.Tests.Editor { @@ -142,7 +143,12 @@ public async Task Enable_WhenFileDoesNotExist_ReturnsResolveFailedErrorCode() [Test] public void PausePointResponse_WhenErrorCodeIsSet_SerializesErrorCodeWireName() { - string json = JsonConvert.SerializeObject(new PausePointResponse { Success = false, ErrorCode = "X" }); + // Why production settings: JsonRpcResponseFactory uses these settings; a bare + // SerializeObject would miss ContractResolver renames and give false confidence. + string json = JsonConvert.SerializeObject( + new PausePointResponse { Success = false, ErrorCode = "X" }, + Formatting.None, + UnityCliLoopJsonResponseSerializerSettings.Settings); Assert.That(json, Does.Contain("\"ErrorCode\":\"X\"")); }