
Security News
GPT-6 Astra Attempts Supply Chain Attacks Against Open Source Maintainers in Testing
GPT-6 Astra hits 100% on ExploitBench and finds zero-days autonomously, while independent tests reveal scope violations and monitoring gaps.
memorydetective
Advanced tools
MCP server for iOS leak hunting and performance investigation. 37 tools across .memgraph and .trace files, 36-pattern retain-cycle classifier with Swift fixTemplate snippets, abandoned-memory diff for KVO/NotificationCenter/cache shapes that escape leaks(
Diagnose iOS retain cycles and performance regressions from your chat window. No Xcode required.

.memgraph files captured by Xcode (or by memorydetective itself on simulators), find ROOT CYCLEs, classify them against known SwiftUI/Combine patterns, and get a one-liner fix hint. All from a script or a chat.xctrace; sample-level Time Profile is parsed when xctrace symbolicates the trace and returns a structured workaround notice when it can't (the underlying xctrace SIGSEGV on heavy unsymbolicated traces is an Apple-side limitation we surface explicitly). Memory Graph capture works on Mac apps and iOS simulator; physical iOS devices still need Xcode.What's new in v1.14 (2026-05-15): trace-side reliability + breadth release. Fixes two parser bugs that were causing
inspectTrace+summarizeTraceto return empty results against real Apple-produced traces (xpath now uses--toc; symbol extraction now reads frame name not weight column). NewanalyzeNetworkActivityMCP tool (37th) for the network family. ExtendedanalyzeHangsto read thehang-risksschema alongsidepotential-hangs. Pattern-matching schema discovery refactor: all 5 trace analyzers now resolve schema names through a regex map so future xctrace renames degrade gracefully. UnifiedsupportStatus[]surface across all 6 trace analyzers (XcodeTraceMCP-pattern). Three opt-in UX paths around the macOS 26.x xctrace--time-limitregression: a 2-second pre-flight probe (MEMORYDETECTIVE_PREFLIGHT_XCTRACE), auto-open of the partial.tracein Instruments.app (MEMORYDETECTIVE_AUTO_OPEN_INSTRUMENTS), and a README + USAGE callout naming the bug. FLEX-inspiredcountAlivesize view (sortBy: "totalBytes",instanceSizeBytesper class). MLeaksFinder + DebugSwift-inspiredverifyFixexpectedAliveClasses[]whitelist with curated default list. 546 → 626 tests. 37 MCP tools.Also recent: v1.13 shipped
summarizeTrace+/summarize-traceMCP prompt. v1.12 completed reference-tree propagation across countAlive / findRetainers / verifyFix and added auto cross-schema correlation in analyzeHangs. v1.11 added inspectTrace, diffMemgraphs reference-tree, and CLI abandoned-memory mini-table. v1.10 closed the notelet retro feedback loop. v1.9 shipped analyzeAbandonedMemory, detectLeaksInXCTest, cleanupTraces, mainThreadViolations. Full notes in CHANGELOG.
Heads up for macOS 26.x users: Apple shipped a
task_for_pidkernel regression on macOS 26.x that blocksleaks --outputGraph,heap, ANDxctrace --template Allocationsagainst iOS simulator processes regardless ofMallocStackLogging. Even Xcode's "View Memory Graph Hierarchy" hits it unlessMalloc Stack Loggingis enabled in the scheme's Diagnostics tab. memorydetective surfaces this as a proactiveplatformAdvisoryon the first capture-class tool call, plus aworkaroundNoticewithissue: "macos-26-task-for-pid-broken"ifleaksis invoked. The most reliable workaround today is to target an iOS 18 simulator runtime (install via Xcode > Settings > Platforms > +iOS 18.x). Empirically validated in the notelet investigation 2026-05-12 where three independent CLI memory-introspection paths all failed before iOS 18 was identified as the working escape hatch. SetMEMORYDETECTIVE_SUPPRESS_PLATFORM_ADVISORY=1to silence the notice once you have settled on a workaround.
Also on macOS 26.x:
xctrace recordis broken for simulator targets. Independent from thetask_for_pidregression above,xcrun xctrace record --time-limit Nsagainst iOS simulator processes wedges past the time limit, eventually exits when killed, and the resulting.tracebundle is missing template metadata.xctrace export --tocthen fails withDocument Missing Template Error. Re-validated against Xcode 26.5 (build 17F42, xctrace 16.0) 2026-05-15: regression survives the update. This hits the entirexctrace-based ecosystem the same way (memorydetective.recordTimeProfile, XcodeTraceMCP, and nakedxcrun xctrace recordcalls all fail identically). Workarounds: (1) record from an older macOS host with Xcode 26.0 if you have one; (2) record via Instruments.app GUI which still works on macOS 26.x and produces a valid.tracethat memorydetective's trace-side analyzers (inspectTrace,summarizeTrace,analyzeHangs,analyzeTimeProfile,analyzeAnimationHitches,compareTracesByPattern) read cleanly; (3) record against a physical device (the regression appears to be simulator-specific). USAGE.md > Troubleshooting has a step-by-step.
npm install -g memorydetective
Add to your MCP client config (Claude Code shown):
// ~/.claude/settings.json
{
"mcpServers": {
"memorydetective": { "command": "memorydetective" }
}
}
Or use it directly from the command line:
memorydetective analyze ~/Desktop/myapp.memgraph
memorydetective classify ~/Desktop/myapp.memgraph
→ See Examples for chat-driven flows · API for the full tool reference · Configure for Claude Desktop / Cursor / Cline · USAGE.md for the full walkthrough including how fixes flow from diagnosis to your codebase.
Realistic chat snippets. The agent calls the MCP tools listed in API.
You: I just exported a memory graph at
~/Desktop/example.memgraph. Something's leaking after I close a detail screen. What is it?Claude: (calls
analyzeMemgraph→classifyCycle) Found 4 ROOT CYCLE blocks across 60,436 leaks. The dominant one matches swiftui.tag-index-projection with high confidence. A closure attached via.tag()on a photo carouselForEachis capturingselfstrongly through_viewModel.wrappedValue, which keepsDetailViewModeland the navigation coordinator alive after dismissal.Suggested fix: replace
[weak self]capture in the tap handler with a static helper, or weak-capture the view-model via[weak coord = self.coordinator]. Want me to find the exact file?
You: I shipped the fix. Compare
~/Desktop/before.memgraphto~/Desktop/after.memgraphand tell me whetherDetailViewModelcount dropped.Claude: (calls
diffMemgraphs→countAlive)DetailViewModel: 12 → 0 (delta-12). Theswiftui.tag-index-projectioncycle is gone. Total leak count fell from 60,436 to 55,576 (−4,860). Three other ROOT CYCLEs persist. They're SwiftUI internals, not your code.
When leaks --outputGraph fails on macOS 26.x (Failed to get DYLD info for task), the cause is usually that the app was not launched with MallocStackLogging=1. The new orchestration tools handle this end to end without you wiring up xcodebuild + simctl + axe yourself.
You: Reproduce the leak in
MyAppfor me. Build, launch with the right env vars, repeat the photo carousel flow 5 times to amplify it, capture before, then I'll ship the fix and you capture after.Claude: (calls
bootAndLaunchForLeakInvestigation) Built + booted iPhone 15 (UDIDABCD-...). LaunchedMyApp(PID49581) withMallocStackLogging=1. Bundle idcom.example.MyApp.(calls
replayScenariowith 5 taps on the carousel + back-button cycle, repeat 5) Executed 25 actions in 12.3s. No failures.(calls
captureScenarioState({label:"before"})) Wrotebefore.memgraph(63,201 leaks),before.png,before.ui.json.MallocStackLoggingactive, no workaroundNotice.(you ship the fix and rebuild)
(calls
bootAndLaunchForLeakInvestigation+ replay +captureScenarioState({label:"after"}))(calls
diffMemgraphs→verifyFix)DetailViewModel: 15 → 0.swiftui.tag-index-projectionPASS. Bytes freed: 28.4 MB.
You: Profile DemoApp on my iPhone for 90 seconds and tell me where the hangs are.
Claude: (calls
listTraceDevices→recordTimeProfile→analyzeHangs) Recorded~/Desktop/run.trace(90s, attached to DemoApp on iPhone 17 Pro Max). Found 23 user-visible hangs (>500 ms each), longest 1.16 s, average 769 ms. Severe load. All on the main thread. Sample-level hotspots aren't symbolicated yet; open the trace in Instruments for that step (analyzeTimeProfilereturns a structured workaround notice whenxctracecan't symbolicate. See CHANGELOG known limits).
The full loop, from artifact to code edit. Combines memory-graph analysis with Swift source-bridging.
You: Tem um leak. Memgraph em
~/Desktop/example-leaks.memgraph. The cycle points atDetailViewModel. Find where it lives in the project~/Development/myapp/and suggest a fix.Claude: (calls
analyzeMemgraph→classifyCycle→reachableFromCycle) Found 4 ROOT CYCLE blocks. The dominant one matchesswiftui.tag-index-projectionwith high confidence.reachableFromCycleconfirms 4DetailViewModelinstances pinning ~1100NSURLSessionConfigurations each. The VM is the culprit, the network stack is collateral.(calls
swiftSearchPatternto locate.tag(+onImageSliderTappatterns in~/Development/myapp/Sources/) 1 match inMyApp/Views/MediaCarousel.swift:142.(calls
swiftGetSymbolDefinition({ symbolName: "DetailViewModel", projectRoot: "...", candidatePaths: ["MyApp/ViewModels/"] })) Declared atMyApp/ViewModels/DetailViewModel.swift:18.class DetailViewModel: ObservableObject.(calls
swiftFindSymbolReferencesto scope the blast radius. 7 callsites)Suggested fix at
MediaCarousel.swift:142:// before .tag(index) .onImageSliderTap { index in self.viewModel.handlePhotoTap(at: index) } // after. Static helper + weak captures .tag(index) .onImageSliderTap { [weak vm = _viewModel.wrappedValue, weak coord = self.coordinator] index in Self.handlePhotoTap(index: index, viewModel: vm, coordinator: coord) }Want me to apply this and verify with a fresh
.memgraph?
The pitch in one sentence: memorydetective turns a 50–500 MB binary memgraph (or a 200 KB leaks(1) text dump) into a 2–5 KB structured summary your AI agent can actually reason about. That changes the economics of using an LLM for iOS perf investigation.
A real-world retain-cycle investigation, run twice. Once with memorydetective, once with the agent reading the raw leaks(1) output directly:
| Step | Without MCP (agent reads raw output) | With memorydetective |
|---|---|---|
Load leaks text dump (~280 KB) | ~70,000 input tokens | n/a |
analyzeMemgraph summary | n/a | ~750 input tokens |
classifyCycle + fix hint | agent re-reasons over the dump per follow-up (3–4 extra turns) | 1 turn, structured patternId + fixHint |
findRetainers / reachableFromCycle | agent re-scans the dump | ~500 tokens, scoped query |
| Net per investigation | ~85,000 tokens, ~6 turns | ~3,000 tokens, ~2 turns |
Translates to roughly $0.40–$1.20 per investigation depending on the model (Claude Opus / Sonnet / Haiku). Compounds linearly with file size and investigation depth.
The same investigation, measured by the developer:
| Step | Without MCP | With memorydetective |
|---|---|---|
Capture memgraph + run leaks | 5 min | 5 min (same) |
Read & interpret leaks text dump | 15–30 min (skim 200 KB of repetitive frames) | 30 sec (read 3 KB summary) |
| Identify the responsible pattern | 10–20 min (recognize the cycle shape from experience) | instant (classifier returns patternId + fix hint) |
| Locate the suspect type in source | 10–15 min (grep + manual navigation) | 30 sec (swiftGetSymbolDefinition returns file:line) |
| Find every callsite to gauge fix blast radius | 5–10 min (Xcode / grep) | 10 sec (swiftFindSymbolReferences) |
| Net wall-clock | 45–80 min | ~10 min |
Numbers are rounded from a single anonymized real investigation (a SwiftUI retain cycle over a tagged ForEach that pinned ~28 MB of network-stack state). Your mileage will vary with cycle complexity and codebase size.
Be honest about where this doesn't help much:
grep, you don't need this.The win compounds with (a) file size, (b) investigation depth (multi-turn), and (c) how many leaks you investigate per quarter. For a single dev fixing one leak per year, the value is mostly the dev-time saving. For a team running CI gates with verifyFix across every PR, the token + time savings stack across hundreds of runs.
The memorydetective binary speaks MCP over stdio. Point any MCP-compatible client at it.
// ~/.claude/settings.json (global) or .mcp.json (per-project)
{
"mcpServers": {
"memorydetective": { "command": "memorydetective" }
}
}
// ~/Library/Application Support/Claude/claude_desktop_config.json
{
"mcpServers": {
"memorydetective": { "command": "memorydetective" }
}
}
Restart Claude Desktop after editing.
// ~/.cursor/mcp.json
{
"mcpServers": {
"memorydetective": { "command": "memorydetective" }
}
}
// VS Code settings.json
{
"cline.mcpServers": {
"memorydetective": { "command": "memorydetective" }
}
}
Kiro supports MCP servers via its global config. The block mirrors Claude Desktop's:
{
"mcpServers": {
"memorydetective": { "command": "memorydetective" }
}
}
Consult Kiro's MCP setup docs for the exact config file path on your system.
GitHub Copilot supports MCP servers in Agent mode (VS Code 1.94+). Add to .vscode/mcp.json in your repo:
{
"servers": {
"memorydetective": {
"type": "stdio",
"command": "memorydetective"
}
}
}
Copilot's MCP integration moves fast. If this snippet is stale, see the VS Code MCP docs.
| Variable | Default | Effect |
|---|---|---|
MEMORYDETECTIVE_REDACTION | balanced | Output scrubbing applied to every tool response. balanced collapses home-directory paths to ~/... and masks token-shaped secrets (AWS keys, GitHub PATs, Stripe, Slack, Bearer auth). strict adds hostname, IPv4, and bundle-identifier masking. off disables redaction (useful for local-only debugging). Mode is logged once at server startup. |
MEMORYDETECTIVE_ALLOW_LAUNCH | unset | Set to 1 to allow bootAndLaunchForLeakInvestigation. The tool executes xcodebuild and xcrun simctl launch against caller-supplied paths and bundle ids, so opt-in is required. Without the var set, the tool returns ok: false with state: launchNotAllowed and a clear explanation. Set this only when you trust the inputs the agent is producing. |
MEMORYDETECTIVE_MAX_RECORDING_SECONDS | 300 | Cap on recordTimeProfile.durationSec. Requests above the cap are rejected with a clear error. Bounded internally to a 3600s (1h) hard ceiling so a misconfigured env var cannot disable the gate. |
MEMORYDETECTIVE_TRACE_ROOT | ~/Library/Application Support/memorydetective/traces | Directory used when recordTimeProfile.output is a relative path. Absolute paths bypass this default for v1.8 backwards-compat. Also the default scan path for cleanupTraces. The directory is auto-created on first write. |
MEMORYDETECTIVE_ALLOW_EXTERNAL_CLEANUP | unset | Set to 1 to allow cleanupTraces to scan and delete .trace bundles OUTSIDE MEMORYDETECTIVE_TRACE_ROOT. Without it, requests that resolve outside the configured root return ok: false with the failure reason and delete nothing. Default-deny on destructive disk operations outside the configured boundary. |
MEMORYDETECTIVE_SUPPRESS_PLATFORM_ADVISORY | unset | Set to 1 to silence the macOS 26.x platform advisory that captureMemgraph, captureScenarioState, and bootAndLaunchForLeakInvestigation emit on first use. Useful once you have an iOS 18 sim runtime installed and do not need the reminder. |
MEMORYDETECTIVE_AUTO_OPEN_INSTRUMENTS | unset | Set to 1 to make recordTimeProfile invoke open -a Instruments <tracePath> as a fire-and-forget escape hatch when xctrace times out (the macOS 26.x regression). The partial .trace opens in Instruments.app, which can still symbolicate + display traces the CLI export path rejects. Off by default so unattended agent runs and CI do not spam the user's GUI. The response's openedInInstrumentsApp field reports whether the open was invoked. |
MEMORYDETECTIVE_PREFLIGHT_XCTRACE | unset (auto) | Controls the pre-flight probe in recordTimeProfile that detects the macOS 26.x xctrace wedge in ~3-5 seconds instead of paying the user's full durationSec plus 30s grace. 1 forces on regardless of platform / target. 0 forces off. When unset, the probe auto-enables on macOS 26.x simulator attach (the known-broken combo) and stays off elsewhere. Pre-flight is skipped for --launch mode to avoid double-launching the app. Side-effect of auto-enable: 2-second probe runs before the full recording starts. Set to 0 if you have already settled on a workaround and want maximum recording-start latency. |
36 MCP tools + 34 Resources + 6 Prompts, grouped by purpose. Tool descriptions are tagged with a category prefix ([mg.memory], [mg.trace], [mg.build], [mg.scenario], [mg.code], [mg.log], [mg.render], [mg.ci], [mg.discover], [ops], [meta]) so related tools are visible at a glance.
Many tools include a suggestedNextCalls field in their response. A typed list of { tool, args, why } entries pre-populated from the current result, so the orchestrating LLM can chain calls without re-reasoning. Start with getInvestigationPlaybook(kind) for the canonical sequence. Or just type /investigate-leak (one of the Prompts) in any client that exposes MCP slash commands.
The cycle classifier ships 36 named antipatterns spanning SwiftUI (including the Swift 6 / @Observable / SwiftData / NavigationStack era, plus the v1.9 swiftui.observable-write-on-every-render shape), Combine, Swift Concurrency (incl. AsyncSequence-on-self and the new Observations API), UIKit (Timer/CADisplayLink/UIGestureRecognizer/KVO/URLSession/WebKit/DispatchSource, plus the v1.9 uikit.viewcontroller-retained-after-pop shape), Core Animation, Core Data, Coordinator pattern, and the popular third-party libs RxSwift + Realm. Each pattern carries:
fixHinthigh / medium / low)staticAnalysisHint pointing at the SwiftLint rule that complements the runtime evidence (or an explicit gap notice when no rule exists. Reinforces the differentiator: memorydetective sees what linters miss at parse time)fixTemplate with concrete Swift before/after snippets (new in v1.7) the agent can adapt directly to the user's code via the SourceKit-LSP source-bridging tools| Tool | What |
|---|---|
analyzeMemgraph | Run leaks against a .memgraph and return summary (totals, ROOT CYCLE blocks, plain-English diagnosis). |
findCycles | Extract just the ROOT CYCLE blocks as flattened chains, with optional className substring filter. |
findRetainers | "Who is keeping <class> alive?". Returns retain chain paths from a top-level node down to the match. |
countAlive | Count instances by class. Provide className for one number, or omit for top-N most-leaked classes. |
reachableFromCycle | Cycle-scoped reachability. "How many <X> instances are reachable from the cycle rooted at <Y>?". Distinguishes the actual culprit from its retained dependencies. |
diffMemgraphs | Compare two .memgraph snapshots: total deltas + class-count changes + cycles new/gone/persisted. |
analyzeAbandonedMemory | Diff two .memgraph snapshots on heap reference-tree class counts (not cycle list) and classify each grown class as kvo-observer-orphaned, notificationcenter-observer-leaked, cache-too-aggressive, singleton-retains-payload, or unknown-growth. Surfaces the family of bugs leaks(1) reports as leakCount: 0 because no strict cycle exists. v1.10 adds actionableGrowth[] + actionableShrinkage[] (framework-noise-filtered views) and supports outputFormat: "verify-fix-table" which emits a focused Class | Before | After | Delta markdown table directly. |
verifyFix | Cycle-semantic diff: per-pattern PASS/PARTIAL/FAIL verdict + bytes freed. CI-gateable. |
classifyCycle | Match each ROOT CYCLE against a built-in catalog of 36 named antipatterns (SwiftUI / Combine / Concurrency / UIKit / Core Animation / Core Data / Coordinator / RxSwift / Realm) with confidence + textual fixHint + staticAnalysisHint (which SwiftLint rule complements this, or explicit gap) + fixTemplate (Swift before/after snippet). |
analyzeHangs | Parse xctrace potential-hangs schema; return Hang vs Microhang counts + top N longest. Pass topFramesByHangStartNs (typically from a chained analyzeTimeProfile correlation) to enrich each top hang with mainThreadViolations[] classifying the blocker as sync-io, db-lock, network, or lock-contention. |
analyzeAnimationHitches | Parse xctrace animation-hitches schema; report by-type counts and how many hitches crossed Apple's user-perceptible 100ms threshold. |
analyzeTimeProfile | Parse xctrace time-profile schema; return top symbols by sample count. Reports SIGSEGV with workarounds when xctrace can't symbolicate. |
analyzeAllocations | Parse xctrace allocations schema; return per-category aggregates (cumulative bytes, allocation count, lifecycle = transient/persistent/mixed) and top allocators. |
analyzeAppLaunch | Parse xctrace app-launch schema; return cold/warm launch type + per-phase breakdown (process-creation, dyld-init, ObjC-init, AppDelegate, first-frame). |
logShow | One-shot query of macOS unified logging via log show --style compact with predicate / process / subsystem filters. Returns parsed entries (timestamp, type, process, subsystem, category, message). |
| Tool | What | Sim | Device |
|---|---|---|---|
recordTimeProfile | Wrap xcrun xctrace record --template "Time Profiler" --attach ... --time-limit Ns --output .... | ✅ | ✅ |
captureMemgraph | Wrap leaks --outputGraph <path> <pid>. Resolves appName → pid via pgrep -x. Returns a structured workaroundNotice on the macOS 26.x Failed to get DYLD info for task regression with stable issue ids (minimal-corpse, permission-denied, leaks-not-found, transient) and a fallback path to recordTimeProfile (Allocations) + analyzeAllocations. | ✅ | ❌. Use Xcode |
logStream | Wrap log stream --style compact for a bounded duration (≤ 60 s). Returns parsed entries collected during the window. | n/a | n/a |
These three tools combine into a single deterministic verify-fix loop: launch the app with MallocStackLogging=1 so leaks works, drive the UI to amplify the suspected leak, snapshot before, ship the fix, snapshot after, then diffMemgraphs.
| Tool | What |
|---|---|
bootAndLaunchForLeakInvestigation | Single-call build + boot + install + launch with MallocStackLogging=1 propagated via SIMCTL_CHILD_*. Resolves the simulator (udid, name+os, or whichever is booted), discovers BUILT_PRODUCTS_DIR / WRAPPER_NAME / EXECUTABLE_NAME / PRODUCT_BUNDLE_IDENTIFIER from xcodebuild -showBuildSettings -json, and returns the host PID + UDID + bundle id ready to chain into captureMemgraph. Required because leaks --outputGraph regressed on macOS 26.x and only works when the target was launched with malloc-stack-logging in its environment. |
replayScenario | Drive the iOS Simulator through tap / swipe / wait / type actions with a repeat count to amplify leaks that only manifest after N iterations. Tap targets accept label, elementId, or coords. Soft dependency on Cameron Cooke's axe CLI. |
captureScenarioState | Composite snapshot for verify-fix: writes .memgraph + .png screenshot + .ui.json accessibility tree into outputDir, all prefixed by label (typically before / after). Sub-captures are best-effort: if leaks fails on macOS 26.x the screenshot + UI tree still complete and the captureMemgraph workaroundNotice is surfaced via memgraphWorkaroundNotice. |
| Tool | What |
|---|---|
listTraceDevices | Parse xcrun xctrace list devices (devices + simulators + UDIDs). |
listTraceTemplates | Parse xcrun xctrace list templates (standard + custom). |
inspectTrace | Orientation tool for .trace bundles. Returns schemas present + row counts + device/OS/template metadata + suggestedNextCalls[] mapping each populated known schema to its analyzer. Use this as the FIRST call on any .trace. New in v1.11. |
| Tool | What |
|---|---|
summarizeTrace | Single call that chains inspectTrace + the 5 analyzers in parallel + cross-correlates findings (hangs overlapping with hitches, etc.) + pre-renders a compact (<10 KB) markdown summary card with a 1-sentence headline, per-area sub-sections, and suggestedNextCalls. The "trace-to-summary-card-in-one-call" play. Use this when you want one synthesis pass instead of chaining 5-6 analyzers manually. New in v1.13. |
| Tool | What |
|---|---|
renderCycleGraph | Read a .memgraph, pick a ROOT CYCLE, and emit a Mermaid graph (markdown-embeddable) or Graphviz DOT. App-level classes highlighted in red; CYCLE BACK terminators amber. |
| Tool | What |
|---|---|
cleanupTraces | Preview and delete .trace bundles under MEMORYDETECTIVE_TRACE_ROOT. dryRun: true by default (the agent has to opt into deletion). Stops at the .trace boundary (does NOT descend INTO bundles). External roots require MEMORYDETECTIVE_ALLOW_EXTERNAL_CLEANUP=1 (default-deny). Useful as a periodic call once a few recordTimeProfile sessions have accumulated tens to hundreds of MB of traces. |
| Tool | What |
|---|---|
detectLeaksInXCTest | Build the unit-test scheme, run with an optional -only-testing: filter, capture .memgraph baseline + after against the xctest runner (or a custom processName for app-hosted bundles), diff. Returns passed: false when new ROOT CYCLEs appear that aren't in the user's allowlist. Set outputHtmlPath to also write a self-contained HTML report. CI-runnable. |
detectLeaksInXCUITest | XCUITest sibling: build the workspace, run the named XCUITest, capture .memgraph baseline + after against the host app, diff. Returns passed: false when new ROOT CYCLEs appear that aren't in the user's allowlist. Set outputHtmlPath to also write a self-contained HTML report. CI-runnable. |
compareTracesByPattern | Trace-side counterpart to verifyFix. Compares two .trace bundles for a perf category (hangs, animation-hitches, or app-launch) and returns PASS/PARTIAL/FAIL with before/after stats and deltas. Apply thresholds: hangs PASS when longest is below hangsMaxLongestMs; hitches PASS when longest is below hitchesMaxLongestMs (default 100ms. Apple's user-perceptible threshold); app-launch PASS when total is below appLaunchMaxTotalMs (default 1000ms). |
detectLeaksInXCTest + outputHtmlPath are the building blocks for a per-PR leak gate. The job below runs the named unit-test scheme on every push and PR, uploads the HTML report as a workflow artifact, and fails when new ROOT CYCLEs appear outside the allowlist. Copy the file into .github/workflows/leaks.yml and adjust the workspace + scheme + test identifier:
name: leaks
on: [push, pull_request]
jobs:
detect-leaks:
runs-on: macos-14
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- run: sudo xcode-select -s /Applications/Xcode_15.4.app
- run: npm install -g memorydetective
- run: |
xcrun simctl boot "iPhone 15" || true
xcrun simctl bootstatus "iPhone 15" -b
- run: |
cat > leaks.json <<EOF
{
"workspace": "DemoApp.xcworkspace",
"scheme": "DemoAppTests",
"destination": "platform=iOS Simulator,name=iPhone 15,OS=18.0",
"testCaseFilter": "DemoTests/LeakSensitiveCase",
"outputHtmlPath": "${GITHUB_WORKSPACE}/leak-report.html",
"allowlistPatterns": ["SwiftUI", "_TtC"]
}
EOF
memorydetective tool detectLeaksInXCTest --input leaks.json
- if: always()
uses: actions/upload-artifact@v4
with:
name: leak-report
path: leak-report.html
retention-days: 14
The same file is in examples/ci/github-actions-leaks.yml if you want to copy it verbatim. Notes:
macos-14 runners. The macOS 26.x kernel regression (task_for_pid) breaks leaks against iOS 26 sims; iOS 18 is the canonical escape hatch (see the Highlights callout above)._TtC covers Swift mangled class prefixes that occasionally show up in SwiftUI internals.actions/cache@v4 keyed on Package.resolved + *.xcconfig to skip build-for-testing rebuilds across runs. Then pass --skipBuild to the second invocation when chaining multiple detectLeaksInXCTest calls on the same job.Pair the memory-graph diagnosis with source-code lookups via SourceKit-LSP. Closes the loop "found this leak in the cycle → find the file/line in your project".
| Tool | What |
|---|---|
swiftGetSymbolDefinition | Locate the file:line where a Swift symbol is declared. Pre-scans candidatePaths (or hint.filePath) with a fast regex, then asks SourceKit-LSP for jump-to-definition. |
swiftFindSymbolReferences | Find every reference to a Swift symbol via SourceKit-LSP textDocument/references. Requires an IndexStoreDB for cross-file results. The response carries a needsIndex hint when the index is missing. |
swiftGetSymbolsOverview | List top-level symbols (classes, structs, enums, protocols, free functions) in a Swift file via documentSymbol. Cheap orientation when the agent lands in a new file. |
swiftGetHoverInfo | Type info / docs at a (line, character) position. Disambiguates self captures: a class self in a closure can leak; a struct self can't. |
swiftSearchPattern | Pure regex search over a Swift file (no LSP, no index). Catches what LSP misses: closure capture lists, Task { ... self ... } blocks, custom patterns from a leak chain. |
These tools require macOS + Xcode (full Xcode, not just Command Line Tools. xcrun sourcekit-lsp must be available). They start a sourcekit-lsp subprocess per project root and reuse it across calls; the subprocess shuts down after a 5-minute idle window.
Why
captureMemgraphdoesn't work on physical iOS devices:leaks(1)only attaches to processes running on the local Mac (which includes iOS simulators). Memory Graph capture from a real device goes through Xcode's debugger over USB/lockdownd. Different mechanism, no public CLI equivalent.
The cycle-pattern catalog is also surfaced as MCP resources, browsable at memorydetective://patterns/{patternId}. Each resource is a markdown body with the pattern name, a longer description, and the fix hint. Use this to let an agent (or a human in a UI-aware MCP client) browse the catalog without burning a classifyCycle call.
memorydetective://patterns/swiftui.tag-index-projection
memorydetective://patterns/concurrency.async-sequence-on-self
memorydetective://patterns/webkit.wkscriptmessagehandler-bridge
memorydetective://patterns/swiftdata.modelcontext-actor-cycle
…
resources/list returns all 34 entries. resources/read resolves any memorydetective://patterns/{id} URI to its markdown body.
Investigation playbooks are exposed as MCP prompts (slash commands in clients that surface them, e.g. Claude Code).
| Slash command | What it does | Args |
|---|---|---|
/investigate-leak | Runs the canonical 6-step memgraph-leak investigation: analyzeMemgraph → classifyCycle → reachableFromCycle → swiftSearchPattern → swiftGetSymbolDefinition → swiftFindSymbolReferences. | memgraphPath |
/investigate-hangs | Diagnose user-visible main-thread hangs from a .trace. | tracePath |
/investigate-jank | Diagnose dropped frames / animation hitches from a .trace. | tracePath |
/investigate-launch | Diagnose cold/warm launch slowness from a .trace. | tracePath |
/verify-cycle-fix | Diff a before/after pair of .memgraph snapshots to confirm a fix landed. | before, after |
Each prompt fills the canonical playbook's argument templates with the user-provided values, then hands the agent a ready-to-execute brief. Calls the same tools listed in Read & analyze. Prompts are an orchestration shortcut, not a separate engine.
The same binary is also a thin CLI for scripting and CI:
memorydetective analyze <path-to-.memgraph> # totals, ROOT CYCLEs, diagnosis
memorydetective classify <path-to-.memgraph> # match patterns + render fix hint
memorydetective tool <toolName> --input <json> # generic dispatcher for any MCP tool
memorydetective --help
memorydetective --version
When called with no arguments, the binary starts as an MCP server over stdio.
The tool subcommand dispatches to any registered MCP tool by name, reading inputs from a JSON file. Exit code is 0 when the tool returns ok && passed !== false, 1 otherwise, so it slots cleanly into CI gates. Currently supported tool names: detectLeaksInXCTest, detectLeaksInXCUITest (the CI recipe above uses this).
xcode-select --install)git clone https://github.com/carloshpdoc/memorydetective
cd memorydetective
npm install
npm test # 546 unit tests
npm run build # build → dist/
npm run dev # tsx, stdio mode (dev mode)
./scripts/demo.sh # full demo against a real .memgraph (set MEMGRAPH=path)
Contributions are welcome. Bug reports, feature requests, new cycle patterns, all of it.
npm install → make changes → npm test (546 tests must stay green) → open a PR with a concise description of what changed and why.classifyCycleclassifyCycle ships with 36 built-in patterns covering SwiftUI (incl. Swift 6 / @Observable / SwiftData / NavigationStack / the v1.9 observable-write-on-every-render and viewcontroller-retained-after-pop shapes), Combine, Swift Concurrency (incl. AsyncSequence-on-self and Observations), UIKit (Timer / CADisplayLink / UIGestureRecognizer / KVO / URLSession / WebKit / DispatchSource), Core Animation, Core Data, the Coordinator pattern, RxSwift, and Realm. To add one:
src/tools/classifyCycle.ts. Add an entry to PATTERNS with id, name, fixHint, and a match function.src/tools/readTools.test.ts that asserts the new pattern fires against a representative memgraph fixture.staticAnalysisHint entry in src/runtime/staticAnalysisHints.ts (the test in that file enforces 1:1 coverage with PATTERNS).fixTemplate entry in src/runtime/fixTemplates.ts (same 1:1 coverage guard).If memorydetective saves you time, you can support continued development:
Every contribution helps keep this maintained and documented.
Apache 2.0. See LICENSE and NOTICE.
Permits commercial use, modification, distribution, patent use. Includes attribution clause via the NOTICE file.
Hunting retain cycles in SwiftUI feels like detective work: you have a body (the leaked instance), a crime scene (the .memgraph), and a chain of suspects (the retain chain). The tool helps you read the evidence and name the killer. The brand follows the work.
FAQs
MCP server for iOS leak hunting and performance investigation. 42 tools across .memgraph, .trace, and .mxdiagnostic files. v1.18 adds analyzeMetricKitPayload (42nd tool, post-mortem production-diagnostics from MetricKit JSON payloads — crash clusters, han
The npm package memorydetective receives a total of 28 weekly downloads. As such, memorydetective popularity was classified as not popular.
We found that memorydetective demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
GPT-6 Astra hits 100% on ExploitBench and finds zero-days autonomously, while independent tests reveal scope violations and monitoring gaps.

Product
Socket can now send alerts and supply chain attack notifications to Microsoft Teams, with filters that route the right updates to each channel.

Security News
pnpm 12 rewrites the package manager in Rust, cutting install times by up to 90% while preserving pnpm 11 workflows and lockfiles.