diff --git a/CHANGELOG.md b/CHANGELOG.md index b9c456ff..fe90deb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ ### Fixed +- Clarified `debug_attach_sim` PID attach arguments so the schema documents that `pid` must be used without `bundleId` or `waitFor`, and invalid `pid` + `waitFor: true` calls now fail validation before LLDB is invoked ([#417](https://github.com/getsentry/XcodeBuildMCP/issues/417)). - Fixed Claude UI benchmark preflight so transient malformed or still-loading UI snapshots no longer crash the harness or finish before app UI is observable. - Fixed Claude UI benchmark preflight so configured first-run dismissals require a concrete simulator ID, suite-provided simulator IDs are recorded in command logs, and preflight-launched apps are terminated after post-launch failures. - Fixed Claude UI benchmark config handling so invalid `failurePatterns` regexes and runtime-incompatible `sessionDefaults` fail before a suite starts and partial `allowedVariance` overrides preserve defaults for omitted metrics. diff --git a/src/mcp/tools/debugging/__tests__/debugging-tools.test.ts b/src/mcp/tools/debugging/__tests__/debugging-tools.test.ts index 331456b7..2f6f6968 100644 --- a/src/mcp/tools/debugging/__tests__/debugging-tools.test.ts +++ b/src/mcp/tools/debugging/__tests__/debugging-tools.test.ts @@ -126,6 +126,12 @@ describe('debug_attach_sim', () => { it('should expose schema with expected shape', () => { expect(attachSchema).toBeDefined(); }); + + it('documents PID attach argument constraints in the public schema', () => { + expect(attachSchema.pid.description).toContain('without bundleId'); + expect(attachSchema.pid.description).toContain('without waitFor'); + expect(attachSchema.waitFor.description).toContain('Only valid when attaching by bundleId'); + }); }); describe('Handler Requirements', () => { @@ -171,6 +177,34 @@ describe('debug_attach_sim', () => { expect(text).toContain('bundleId'); expect(text).toContain('pid'); }); + + it('rejects waitFor true when attaching by pid', async () => { + __setTestDebuggerToolContextOverride(createTestContext()); + sessionStore.setDefaults({ simulatorId: 'test-sim-uuid' }); + + const result = await callHandler(attachHandler, { + pid: 1234, + waitFor: true, + }); + + expect(result.isError).toBe(true); + const text = result.content[0].text; + expect(text).toContain('waitFor is only valid when attaching by bundleId'); + expect(text).toContain('For PID attach, omit waitFor or set it to false'); + }); + + it('allows waitFor false when attaching by pid', async () => { + __setTestDebuggerToolContextOverride(createTestContext()); + sessionStore.setDefaults({ simulatorId: 'test-sim-uuid' }); + + const result = await callHandler(attachHandler, { + pid: 1234, + waitFor: false, + }); + + expect(result.isError).toBeFalsy(); + expect(result.content[0].text).toContain('Attached'); + }); }); describe('Logic Behavior', () => { diff --git a/src/mcp/tools/debugging/debug_attach_sim.ts b/src/mcp/tools/debugging/debug_attach_sim.ts index b2360cea..e4fb801b 100644 --- a/src/mcp/tools/debugging/debug_attach_sim.ts +++ b/src/mcp/tools/debugging/debug_attach_sim.ts @@ -19,6 +19,9 @@ import { type DebuggerToolContext, } from '../../../utils/debugger/index.ts'; +const DEBUG_ATTACH_MODE_HELP = + 'Valid attach modes: provide bundleId without pid, or provide pid without bundleId and omit waitFor or set waitFor to false.'; + const baseSchemaObject = z.object({ simulatorId: z .string() @@ -32,9 +35,26 @@ const baseSchemaObject = z.object({ .describe( "Name of the simulator (e.g., 'iPhone 17'). Provide EITHER this OR simulatorId, not both", ), - bundleId: z.string().optional(), - pid: z.number().int().positive().optional(), - waitFor: z.boolean().optional().describe('Wait for the process to appear when attaching'), + bundleId: z + .string() + .optional() + .describe( + 'Attach by bundle identifier. Provide bundleId without pid; waitFor may be used with this mode.', + ), + pid: z + .number() + .int() + .positive() + .optional() + .describe( + 'Attach to an already-running process by PID. Provide pid without bundleId and without waitFor.', + ), + waitFor: z + .boolean() + .optional() + .describe( + 'Only valid when attaching by bundleId. For PID attach, omit waitFor or set it to false.', + ), continueOnAttach: z.boolean().optional().default(true).describe('default: true'), makeCurrent: z .boolean() @@ -47,10 +67,14 @@ const debugAttachSchema = z.preprocess( nullifyEmptyStrings, withSimulatorIdOrName(baseSchemaObject) .refine((val) => val.bundleId !== undefined || val.pid !== undefined, { - message: 'Provide either bundleId or pid to attach.', + message: `Provide either bundleId or pid to attach. ${DEBUG_ATTACH_MODE_HELP}`, }) .refine((val) => !(val.bundleId && val.pid), { - message: 'bundleId and pid are mutually exclusive. Provide only one.', + message: 'Provide either bundleId or pid, not both.', + }) + .refine((val) => !(val.pid !== undefined && val.waitFor === true), { + message: + 'waitFor is only valid when attaching by bundleId. For PID attach, omit waitFor or set it to false.', }), );