Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ When reading issues:
- When working on skill sources in `skills/`, use the `skill-creator` skill workflow.
- After modifying any skill source, run `npx skill-check <skill-directory>` and address all errors/warnings before handoff.
-
## Multi-process filesystem state
- XcodeBuildMCP explicitly supports multiple concurrent MCP server, daemon, CLI, test, and helper processes for the same or different workspaces.
- Shared filesystem state under `~/Library/Developer/XcodeBuildMCP` must be multi-process safe.
- Use workspace-key scoped directories for workspace-owned state.
- Do not store runtime state under `~/.xcodebuildmcp`; `.xcodebuildmcp/config.yaml` is only project configuration.
- Use shared lock and atomic-write helpers for mutable shared files.
- Prefer one-record-per-file registries over shared aggregate files.
- Cleanup must verify ownership before deleting shared artifacts.

## Style
- Keep answers short and concise
- No emojis in commits, issues, PR comments, or code
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
- Fixed simulator OSLog helper cleanup so server and daemon startup reconcile same-workspace orphaned log streams without stopping helpers owned by live sessions in other workspaces ([#382](https://github.com/getsentry/XcodeBuildMCP/issues/382)).
- Fixed Weather example test discovery and made CLI test progress visible while tests are running instead of leaving the last build phase displayed.

### Changed

- Centralized workspace log retention and startup/shutdown filesystem cleanup so XcodeBuildMCP-owned logs are pruned consistently while preserving active daemon and simulator OSLog outputs.

## [2.5.0-beta.1]

### Breaking
Expand Down
9 changes: 9 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ When reading issues:
- When working on skill sources in `skills/`, use the `skill-creator` skill workflow.
- After modifying any skill source, run `npx skill-check <skill-directory>` and address all errors/warnings before handoff.
-
## Multi-process filesystem state
- XcodeBuildMCP explicitly supports multiple concurrent MCP server, daemon, CLI, test, and helper processes for the same or different workspaces.
- Shared filesystem state under `~/Library/Developer/XcodeBuildMCP` must be multi-process safe.
- Use workspace-key scoped directories for workspace-owned state.
- Do not store runtime state under `~/.xcodebuildmcp`; `.xcodebuildmcp/config.yaml` is only project configuration.
- Use shared lock and atomic-write helpers for mutable shared files.
- Prefer one-record-per-file registries over shared aggregate files.
- Cleanup must verify ownership before deleting shared artifacts.

## Style
- Keep answers short and concise
- No emojis in commits, issues, PR comments, or code
Expand Down
15 changes: 2 additions & 13 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
import { bootstrapRuntime } from './runtime/bootstrap-runtime.ts';
import { buildCliToolCatalog } from './cli/cli-tool-catalog.ts';
import { buildYargsApp } from './cli/yargs-app.ts';
import { getSocketPath, getWorkspaceKey, resolveWorkspaceRoot } from './daemon/socket-path.ts';
import { getSocketPath } from './daemon/socket-path.ts';
import { startMcpServer } from './server/start-mcp-server.ts';
import { listCliWorkflowIdsFromManifest } from './runtime/tool-catalog.ts';
import { flushAndCloseSentry, initSentry, recordBootstrapDurationMetric } from './utils/sentry.ts';
import { coerceLogLevel, setLogLevel, type LogLevel } from './utils/logger.ts';
import { hydrateSentryDisabledEnvFromProjectConfig } from './utils/sentry-config.ts';
import { configureRuntimeWorkspaceKey } from './utils/runtime-instance.ts';

function findTopLevelCommand(argv: string[]): string | undefined {
const flagsWithValue = new Set(['--socket', '--log-level', '--style']);
Expand Down Expand Up @@ -119,23 +118,13 @@ async function main(): Promise<void> {
},
});

// Compute workspace context for daemon routing
const workspaceRoot = resolveWorkspaceRoot({
cwd: result.runtime.cwd,
projectConfigPath: result.configPath,
});
const { workspaceRoot, workspaceKey } = result;

const defaultSocketPath = getSocketPath({
cwd: result.runtime.cwd,
projectConfigPath: result.configPath,
});

const workspaceKey = getWorkspaceKey({
cwd: result.runtime.cwd,
projectConfigPath: result.configPath,
});
configureRuntimeWorkspaceKey(workspaceKey);

const cliExposedWorkflowIds = await listCliWorkflowIdsFromManifest({
excludeWorkflows: ['session-management', 'workflow-discovery'],
});
Expand Down
29 changes: 22 additions & 7 deletions src/cli/daemon-control.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { spawn } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { dirname, resolve, basename } from 'node:path';
import { existsSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { existsSync, unlinkSync } from 'node:fs';
import { DaemonClient, DaemonVersionMismatchError } from './daemon-client.ts';
import { readDaemonRegistryEntry } from '../daemon/daemon-registry.ts';
import { removeStaleSocket } from '../daemon/socket-path.ts';
import {
cleanupWorkspaceDaemonFiles,
findDaemonRegistryEntryBySocketPath,
} from '../daemon/daemon-registry.ts';

/**
* Default timeout for daemon startup in milliseconds.
Expand Down Expand Up @@ -38,8 +40,7 @@ export function getDaemonExecutablePath(): string {
* sends SIGTERM, and removes the stale socket.
*/
export async function forceStopDaemon(socketPath: string): Promise<void> {
const workspaceKey = basename(dirname(socketPath));
const entry = readDaemonRegistryEntry(workspaceKey);
const entry = findDaemonRegistryEntryBySocketPath(socketPath);
if (entry?.pid) {
try {
process.kill(entry.pid, 'SIGTERM');
Expand All @@ -49,7 +50,21 @@ export async function forceStopDaemon(socketPath: string): Promise<void> {
// Brief wait for the process to exit.
await new Promise((resolve) => setTimeout(resolve, 500));
}
removeStaleSocket(socketPath);
if (entry) {
cleanupWorkspaceDaemonFiles(entry.workspaceKey, {
pid: entry.pid,
socketPath,
Comment thread
sentry[bot] marked this conversation as resolved.
Outdated
allowLiveOwner: true,
});
Comment thread
cameroncooke marked this conversation as resolved.
Outdated
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
} else {
// Registry entry missing; cannot derive workspace key from socket path alone.
// Clean up the socket file directly.
try {
unlinkSync(socketPath);
} catch {
// Socket may already be gone.
}
}
}
Comment thread
sentry[bot] marked this conversation as resolved.

export interface StartDaemonBackgroundOptions {
Expand Down
Loading
Loading