Skip to content

Fix empty accessibility hierarchy on iOS 26+ simulators#312

Closed
dpearson2699 wants to merge 1 commit intogetsentry:mainfrom
dpearson2699:fix/290
Closed

Fix empty accessibility hierarchy on iOS 26+ simulators#312
dpearson2699 wants to merge 1 commit intogetsentry:mainfrom
dpearson2699:fix/290

Conversation

@dpearson2699
Copy link
Copy Markdown
Contributor

Summary

On iOS 26+ fresh simulators, AccessibilityEnabled and ApplicationAccessibilityEnabled default to 0, which causes snapshot_ui to return an empty accessibility hierarchy with no children.

This PR proactively enables both accessibility flags at simulator boot time via xcrun simctl spawn <udid> defaults write, so snapshot_ui works correctly on first use.

Changes

  • src/utils/simulator-accessibility.ts — New ensureSimulatorAccessibility() utility that reads the current AccessibilityEnabled value and writes both flags if disabled. Failures are logged but never propagated (accessibility setup should not block boot).
  • src/mcp/tools/simulator/boot_sim.ts — Calls ensureSimulatorAccessibility() after successful boot.
  • src/mcp/tools/simulator/build_run_sim.ts — Calls ensureSimulatorAccessibility() after the boot-if-needed section, covering the implicit boot path.
  • src/utils/__tests__/simulator-accessibility.test.ts — 6 unit tests covering enable, skip-if-already-enabled, error handling, and command verification.
  • src/mcp/tools/simulator/__tests__/boot_sim.test.ts — Updated command verification test to account for accessibility calls after boot.

Design decisions

  • Proactive at boot rather than reactive at snapshot_ui time — avoids the overhead of detecting an empty hierarchy, retrying, and adding response notes.
  • Idempotent — reads first, writes only if needed. Persists until device erasure.
  • Fire-and-forget — errors are caught and logged at warn level, never thrown.

Fixes #290

Comment thread src/mcp/tools/simulator/boot_sim.ts
Comment thread src/utils/simulator-accessibility.ts Outdated
Comment thread src/utils/simulator-accessibility.ts
Comment thread src/utils/__tests__/simulator-accessibility.test.ts
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e963be0. Configure here.

Comment thread src/utils/simulator-accessibility.ts
Comment thread src/mcp/tools/simulator/boot_sim.ts
)

On iOS 26+ fresh simulators, AccessibilityEnabled and
ApplicationAccessibilityEnabled default to 0, which prevents
accessibility hierarchy queries from returning any elements.

Enable both flags via xcrun simctl spawn defaults write after
boot_sim and build_run_sim boot the simulator. The check is
idempotent (reads first, writes only if needed) and failures
are logged but never propagated to avoid blocking boot.
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 27, 2026

Open in StackBlitz

npm i https://pkg.pr.new/xcodebuildmcp@312

commit: 638147c

@cameroncooke
Copy link
Copy Markdown
Collaborator

Thanks for the work on this, @dpearson2699. Before merging we ran a controlled test of the underlying premise on three freshly-created iOS 26.4 / iPhone 17 simulators (scripts in scripts/investigations/, full report in the investigation report). The data does not support the causation this PR is built on, and unfortunately shows the change is an active regression rather than a fix.

What we observed

T+0 anchored at "simctl bootstatus -b returned AND SpringBoard is in launchctl list AND a defaults read round-trip succeeds". axe describe-ui polled every 1 s.

Scenario T_ready (s) Outcome
A — no flag write 8, 8 Flags read missing at T+0, 1/1 by T+5 s; AX hierarchy non-empty at T+8 s
B0 — write flags at T+1 (this PR's behaviour) never (>90), never (>90) Flags read 1/1 immediately and stay 1/1. AX hierarchy stays empty for the full 90 s.
C — passive: no AX queries, no writes flags missing for 45 s Subsystem does not self-write the defaults absent an AX query; one query at T+45 s succeeds
E — write flags 10 s after AX confirmed ready 5, 6 Late write is harmless; AX continues to respond

Two trials of B0 produced permanent failure of axe describe-ui for the rest of that boot. Reads of the flags afterward show 1/1, which is why a "did the write succeed" check sees success while the AX subsystem itself is wedged.

Why we think this happens

axe describe-ui goes through FBSimulator.accessibilityElements(withNestedFormat:)FBSimulatorAccessibilityCommands.m → CoreSimulator's [SimDevice sendAccessibilityRequestAsync:...] → in-simulator AX-translation XPC service. None of that path reads com.apple.Accessibility/AccessibilityEnabled (verified by reading the AXe and FBSimulatorControl sources). The flags are user-facing assistive-technology toggles; they share a plist domain with the AX introspection bus but they aren't the same switch.

The most plausible mechanism for the regression: accessibilityd reads its configuration from com.apple.Accessibility defaults during its ~5–8 s post-boot bind. A concurrent simctl spawn defaults write to that domain mid-bind leaves the daemon with an inconsistent view; it fails to register a valid translation object with CoreSimulatorBridge. After that, every AX query returns the empty AXApplication shape (accessibilityFrame == CGRectZero, no children) for the rest of the boot. This matches the symptom shape FBSimulatorControl already documents in its stale-SpringBoard remediation path (FBSimulatorAccessibilityCommands.m:1428-1456).

Test E confirms the breakage is specifically a race against accessibilityd's startup, not a property of the write itself: writes after the subsystem is already serving requests are harmless.

Likely interpretation of the original observation in #290

When you ran defaults write then axe describe-ui and saw it work, the wall-clock time consumed by typing/running those commands had pushed past the ~5–8 s warmup window. The second describe-ui succeeded because the subsystem had finished initialising naturally, not because of the write. The flags reading 1/1 afterward is consistent with both your manual write and the subsystem self-writing them as a side effect of a successful query.

Closing this PR

I'm closing this rather than merging because the change makes things worse for any agent that calls a UI-automation tool quickly after a fresh boot — exactly the case this PR was meant to help. Please don't take that as dismissal: this is a real bug and your reproduction in #290 is genuine. The methodology just turned out to point at a different cause than the fix targets.

If you can reproduce a case where:

  1. T_A in our table is much longer than 8 s (e.g. consistently 25–30 s as your original report suggested), and
  2. T_B0 is consistently shorter than T_A in the same configuration,

then the flag write would be doing something we couldn't measure, and we'd reopen the discussion. The scripts at ax-readiness-test-v2.sh and ax-late-write-test.sh will run end-to-end on a fresh sim in about 5 minutes; please feel free to run them on your machine and share the logs if you see different numbers. We'd also be interested in any reproduction where simctl shutdown && boot does not recover an empty hierarchy — that would suggest a different mechanism than the one we measured.

Either way, thanks again for the contribution and for the detailed reproduction in #290. Sorry to close after a rebase — the empirical finding only emerged when we tried to verify it.

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.

describe-ui returns empty hierarchy on iOS 26+ simulators — auto-enable accessibility defaults

2 participants