Skip to content

Fix:#96 fail to process xcodebuild error ooutput.#97

Open
romanr wants to merge 4 commits into
conorluddy:mainfrom
romanr:96
Open

Fix:#96 fail to process xcodebuild error ooutput.#97
romanr wants to merge 4 commits into
conorluddy:mainfrom
romanr:96

Conversation

@romanr

@romanr romanr commented Jan 1, 2026

Copy link
Copy Markdown

Failed to detect when xcodebuild encounters "simulator locked" issue and produces streaming output. Which caused xc-mcp to not return anything at all. Enhanced xcodebuild output with immediate error/warning visibility and streaming command execution.

…to latest 1.25.1 which depends on zod v4 which has breaking changes.
…ssue and produces streaming output. Which caused xc-mcp to not return anything at all. Enhanced xcodebuild output with immediate error/warning visibility and streaming command execution.
Copilot AI review requested due to automatic review settings January 1, 2026 19:56

Copilot AI 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.

Pull request overview

This PR addresses issue #96 where xcodebuild failed to properly detect and handle simulator-locked errors and other fatal conditions during streaming output. The solution introduces streaming command execution with early termination on fatal patterns, adds immediate error/warning visibility to build responses, and pins the MCP SDK version for stability.

Key Changes:

  • Added executeCommandStreaming function with fatal pattern detection and early process termination
  • Enhanced build responses to include first 10 errors/warnings at top level for immediate visibility
  • Reduced build timeout from 10 minutes to 55 seconds to stay under MCP transport limits

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/utils/command.ts Adds new streaming command execution with fatal pattern detection and timeout handling
src/utils/response-cache.ts Extends build summary to include arrays of first 10 errors and warnings
src/tools/xcodebuild/build.ts Integrates streaming execution, processes fatal matches and timeouts, surfaces errors/warnings at top level
tests/tests/utils/response-cache.test.ts Updates tests to validate new errors/warnings arrays and 10-item limit
package.json, package-lock.json Pins @modelcontextprotocol/sdk to exact version 1.17.1
README.md Updates documentation to reflect immediate error/warning visibility in build responses

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/utils/command.ts
Comment on lines +72 to +147
export async function executeCommandStreaming(
command: string,
options: StreamingOptions = {}
): Promise<StreamingCommandResult> {
const {
timeout = 60000,
maxBuffer = 10 * 1024 * 1024,
fatalPatterns = [],
onFatalMatch,
} = options;

return new Promise<StreamingCommandResult>((resolve, reject) => {
const child = spawn(command, { shell: true, timeout });

let stdout = '';
let stderr = '';
let timedOut = false;
let fatalMatch: string | undefined;

const checkPatterns = (chunk: string) => {
if (fatalMatch) return;
for (const pattern of fatalPatterns) {
const match = chunk.match(pattern);
if (match) {
fatalMatch = match[0];
onFatalMatch?.(match[0]);
child.kill();
break;
}
}
};

const timeoutId = setTimeout(() => {
timedOut = true;
child.kill();
}, timeout);

child.stdout?.on('data', data => {
const text = data.toString();
stdout += text;
checkPatterns(text);
if (stdout.length > maxBuffer) {
child.kill();
clearTimeout(timeoutId);
reject(
new McpError(
ErrorCode.InternalError,
`Command output exceeded max buffer size of ${maxBuffer} bytes`
)
);
}
});

child.stderr?.on('data', data => {
const text = data.toString();
stderr += text;
checkPatterns(text);
});

child.on('close', code => {
clearTimeout(timeoutId);
resolve({
stdout: stdout.trim(),
stderr: stderr.trim(),
code: code ?? 0,
timedOut,
fatalMatch,
});
});

child.on('error', error => {
clearTimeout(timeoutId);
reject(new McpError(ErrorCode.InternalError, `Failed to execute command: ${error.message}`));
});
});
}

Copilot AI Jan 1, 2026

Copy link

Choose a reason for hiding this comment

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

The new executeCommandStreaming function lacks test coverage. While other functions in this module have comprehensive tests in command.test.ts, there are no tests for the streaming functionality, fatal pattern detection, timeout behavior, or buffer overflow handling. Consider adding tests for this new function to match the existing test coverage pattern.

Copilot uses AI. Check for mistakes.
Comment thread README.md
Comment on lines +593 to +604
\`\`\`typescript
// Build returns errors/warnings at top level - no second call needed
xcodebuild-build({ projectPath: "MyApp.xcodeproj", scheme: "MyApp" })
// Returns:
{
buildId: "abc123",
success: true,
warnings: ["...warning: unused variable..."], // immediate visibility
summary: { errorCount: 0, warningCount: 1, duration: 5000 }
}
// Only use xcodebuild-get-details for full logs or >10 errors/warnings
\`\`\`

Copilot AI Jan 1, 2026

Copy link

Choose a reason for hiding this comment

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

The backticks in the code block are escaped with backslashes, which will render literally in the markdown instead of creating a proper code block. Remove the backslash escapes before the triple backticks.

Copilot uses AI. Check for mistakes.
Comment thread src/utils/command.ts
Comment on lines +81 to +85
} = options;

return new Promise<StreamingCommandResult>((resolve, reject) => {
const child = spawn(command, { shell: true, timeout });

Copilot AI Jan 1, 2026

Copy link

Choose a reason for hiding this comment

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

The cwd option from CommandOptions is not extracted from the options parameter and not passed to the spawn call. This means the working directory cannot be configured for streaming commands, creating an inconsistency with the existing executeCommand function which does support cwd. Consider extracting cwd from options and passing it to spawn.

Suggested change
} = options;
return new Promise<StreamingCommandResult>((resolve, reject) => {
const child = spawn(command, { shell: true, timeout });
cwd,
} = options;
return new Promise<StreamingCommandResult>((resolve, reject) => {
const child = spawn(command, { shell: true, timeout, cwd });

Copilot uses AI. Check for mistakes.
Comment thread src/utils/command.ts
child.stderr?.on('data', data => {
const text = data.toString();
stderr += text;
checkPatterns(text);

Copilot AI Jan 1, 2026

Copy link

Choose a reason for hiding this comment

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

The stderr buffer size is not checked against maxBuffer, only stdout is checked. This could lead to unbounded memory growth if stderr produces large amounts of output. Consider adding a similar check for stderr length as is done for stdout at line 113.

Suggested change
checkPatterns(text);
checkPatterns(text);
if (stderr.length > maxBuffer) {
child.kill();
clearTimeout(timeoutId);
reject(
new McpError(
ErrorCode.InternalError,
`Command error output exceeded max buffer size of ${maxBuffer} bytes`
)
);
}

Copilot uses AI. Check for mistakes.
@claude

claude Bot commented Jun 18, 2026

Copy link
Copy Markdown

Code Review — PR #97: Fix xcodebuild error output (streaming + early visibility)

Overview

This PR adds streaming command execution to xcodebuild-build and promotes errors/warnings to the top level of the build response. The motivation is solid — the old code was silent when xcodebuild produced streaming errors (like "simulator locked") because it buffered everything and returned nothing on certain failure modes.


Relationship to PR #98

This PR and PR #98 are from the same author and share most of their changed code:

  • The executeCommandStreaming utility in src/utils/command.ts is identical
  • The README changes are identical
  • The build.ts streaming integration overlaps heavily

PR #98 is a superset of this PR (it adds the resolveDestination refactor on top). If PR #98 is merged, this PR should be closed as superseded. If this PR is considered for merge independently, the reviewer feedback on PR #98 applies here too.


Issues (shared with PR #98)

🔴 55-second build timeout will break large real-world projects

File: src/tools/xcodebuild/build.ts

const timeoutMs = 55_000; // Stay under MCP transport limits

This drops the timeout from 10 minutes to 55 seconds. Medium-to-large iOS projects routinely exceed 55 seconds on a clean build, and this will cause builds to report timedOut: true as false failures.

The fatal pattern early-exit and the timeout are independent mechanisms — early-exit on detected fatal patterns can stay at 55s or even 0 (immediate), while the overall timeout should remain at 5–10 minutes for legitimate builds.

🟡 SDK pinned to exact version 1.17.1

The ^ range should be kept to allow patch-level security and bug-fix updates unless there's a documented regression.


Code Quality

executeCommandStreaming addresses the real failure mode

The streaming approach means fatal log lines are visible as soon as they appear, and the process can be killed immediately rather than waiting for the full timeout. The implementation is correct — stdout/stderr are buffered, pattern check happens on each chunk, and the close event returns collected output with timedOut/fatalMatch metadata.

✅ Errors/warnings surfaced at top level

Surfacing the first 10 errors/warnings in the immediate response is a good ergonomics improvement for AI agents using this tool.

✅ Fatal pattern list is conservative

The three patterns (notification_proxy, passcode protected, device matching) are specific enough to avoid false positives while catching the most common early-exit scenarios.


Summary

The core idea is right — streaming + early fatal detection is a better model for long-running xcodebuild invocations. The critical fix needed is restoring a reasonable build timeout (5–10 min) while keeping the fatal-pattern early exit. Also recommend coordinating with PR #98 to avoid duplicate review cycles on overlapping code.

@claude

claude Bot commented Jun 18, 2026

Copy link
Copy Markdown

Code Review — PR #97: Fix xcodebuild error output processing

Overall: 🔶 Needs changes (shares critical issues with PR #98, plus merge conflict)

What this does

  • Adds streaming command execution with fatal-pattern early exit (executeCommandStreaming).
  • Fixes xcodebuild-get-details errors-only filter to recognise ** TEST FAILED ** and per-test failure lines for both XCTest and Swift Testing formats.
  • Surfaces top-level errors[]/warnings[] on the build response.

⚠️ Overlap with PR #98

This PR and PR #98 (from the same author) touch the same files: command.ts, response-cache.ts, and build.ts. They are effectively two halves of the same change. Merging either will create a conflict in the other. Please coordinate with the maintainer to either:

  1. Consolidate both PRs into one, or
  2. Establish a merge order and rebase the second PR on the first.

🚨 Critical — same 55-second timeout issue as PR #98

const timeoutMs = 55_000; // Stay under MCP transport limits

See the detailed comment on PR #98. MCP stdio transport has no per-call timeout — this limit is unnecessary and will break real iOS builds (which routinely exceed 1–2 minutes). Please restore a longer default or make it configurable.


Bug — same double-timeout race in executeCommandStreaming as PR #98

spawn(command, { shell: true, timeout }) runs concurrently with the manual setTimeout. The timedOut flag may be false even when the process was killed by spawn's internal timer. Remove the timeout option from spawn.


Strengths

get-details.ts errors-only improvements are clean and correct:

line.includes('** TEST FAILED **') ||
/Test case '[^']+' failed/i.test(line) ||   // Swift Testing
/Test Case '-\[.+\]' failed/.test(line)      // XCTest

These three patterns together cover the real failure-line formats that were previously invisible. The regex for Swift Testing uses a case-insensitive flag (correct, since the format can vary) while the XCTest regex preserves the exact capitalisation from xcodebuild output.

Streaming fatal-pattern detection — the concept is sound: kill the process early when a known-fatal message is seen (passcode-protected device, mismatched destination specifier) rather than waiting for the full timeout. The patterns chosen are specific and unlikely to produce false positives.


Minor

The PR description has a typo in the title ("ooutput"). Not a code issue but worth fixing for changelog readability.

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.

2 participants