From 4d815c8343a403bb700a760a54df90c5338a9998 Mon Sep 17 00:00:00 2001 From: Colm Du Ve Date: Sun, 1 Mar 2026 08:25:17 -0700 Subject: [PATCH] fix: include stdout diagnostics when stderr is empty on swift package failure SPM writes compiler diagnostics to stdout, not stderr. The ?? operator does not treat empty string as falsy, so when stderr is "" the diagnostics in stdout were never included in the error response. Switching to || fixes the fallback for all three affected tools. Fixes #243 --- CHANGELOG.md | 6 +++++ .../__tests__/swift_package_build.test.ts | 26 +++++++++++++++++++ .../__tests__/swift_package_test.test.ts | 26 +++++++++++++++++++ .../swift-package/swift_package_build.ts | 2 +- .../swift-package/swift_package_clean.ts | 2 +- .../tools/swift-package/swift_package_test.ts | 2 +- 6 files changed, 61 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dff04f3..de4f011e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [Unreleased] + +### Fixed + +- Fixed `swift_package_build`, `swift_package_test`, and `swift_package_clean` swallowing compiler diagnostics on failure by treating empty stderr as falsy, so stdout diagnostics are included in the error response ([#243](https://github.com/getsentry/XcodeBuildMCP/issues/243)). + ## [2.1.0] ### Added diff --git a/src/mcp/tools/swift-package/__tests__/swift_package_build.test.ts b/src/mcp/tools/swift-package/__tests__/swift_package_build.test.ts index d44c25a8..7387e8fc 100644 --- a/src/mcp/tools/swift-package/__tests__/swift_package_build.test.ts +++ b/src/mcp/tools/swift-package/__tests__/swift_package_build.test.ts @@ -228,6 +228,32 @@ describe('swift_package_build plugin', () => { }); }); + it('should include stdout diagnostics when stderr is empty on build failure', async () => { + const executor = createMockExecutor({ + success: false, + error: '', + output: + "main.swift:10:25: error: cannot find type 'DOESNOTEXIST' in scope\nlet broken: DOESNOTEXIST = 42", + }); + + const result = await swift_package_buildLogic( + { + packagePath: '/test/package', + }, + executor, + ); + + expect(result).toEqual({ + content: [ + { + type: 'text', + text: "Error: Swift package build failed\nDetails: main.swift:10:25: error: cannot find type 'DOESNOTEXIST' in scope\nlet broken: DOESNOTEXIST = 42", + }, + ], + isError: true, + }); + }); + it('should handle spawn error', async () => { const executor = async () => { throw new Error('spawn ENOENT'); diff --git a/src/mcp/tools/swift-package/__tests__/swift_package_test.test.ts b/src/mcp/tools/swift-package/__tests__/swift_package_test.test.ts index 2e12f9cd..d8a6a13a 100644 --- a/src/mcp/tools/swift-package/__tests__/swift_package_test.test.ts +++ b/src/mcp/tools/swift-package/__tests__/swift_package_test.test.ts @@ -218,6 +218,32 @@ describe('swift_package_test plugin', () => { }); }); + it('should include stdout diagnostics when stderr is empty on test failure', async () => { + const mockExecutor = createMockExecutor({ + success: false, + error: '', + output: + "main.swift:10:25: error: cannot find type 'DOESNOTEXIST' in scope\nlet broken: DOESNOTEXIST = 42", + }); + + const result = await swift_package_testLogic( + { + packagePath: '/test/package', + }, + mockExecutor, + ); + + expect(result).toEqual({ + content: [ + { + type: 'text', + text: "Error: Swift package tests failed\nDetails: main.swift:10:25: error: cannot find type 'DOESNOTEXIST' in scope\nlet broken: DOESNOTEXIST = 42", + }, + ], + isError: true, + }); + }); + it('should handle spawn error', async () => { const mockExecutor = async () => { throw new Error('spawn ENOENT'); diff --git a/src/mcp/tools/swift-package/swift_package_build.ts b/src/mcp/tools/swift-package/swift_package_build.ts index 96383809..2dd87d93 100644 --- a/src/mcp/tools/swift-package/swift_package_build.ts +++ b/src/mcp/tools/swift-package/swift_package_build.ts @@ -57,7 +57,7 @@ export async function swift_package_buildLogic( try { const result = await executor(['swift', ...swiftArgs], 'Swift Package Build', false, undefined); if (!result.success) { - const errorMessage = result.error ?? result.output ?? 'Unknown error'; + const errorMessage = result.error || result.output || 'Unknown error'; return createErrorResponse('Swift package build failed', errorMessage); } diff --git a/src/mcp/tools/swift-package/swift_package_clean.ts b/src/mcp/tools/swift-package/swift_package_clean.ts index 4d8b47aa..1c2e8428 100644 --- a/src/mcp/tools/swift-package/swift_package_clean.ts +++ b/src/mcp/tools/swift-package/swift_package_clean.ts @@ -26,7 +26,7 @@ export async function swift_package_cleanLogic( try { const result = await executor(['swift', ...swiftArgs], 'Swift Package Clean', false, undefined); if (!result.success) { - const errorMessage = result.error ?? result.output ?? 'Unknown error'; + const errorMessage = result.error || result.output || 'Unknown error'; return createErrorResponse('Swift package clean failed', errorMessage); } diff --git a/src/mcp/tools/swift-package/swift_package_test.ts b/src/mcp/tools/swift-package/swift_package_test.ts index 25579b1e..8d022d02 100644 --- a/src/mcp/tools/swift-package/swift_package_test.ts +++ b/src/mcp/tools/swift-package/swift_package_test.ts @@ -67,7 +67,7 @@ export async function swift_package_testLogic( try { const result = await executor(['swift', ...swiftArgs], 'Swift Package Test', false, undefined); if (!result.success) { - const errorMessage = result.error ?? result.output ?? 'Unknown error'; + const errorMessage = result.error || result.output || 'Unknown error'; return createErrorResponse('Swift package tests failed', errorMessage); }