Menu

#431 fix(tray): retire the redundant meridian-a11y-helper Accessibility entry

closed
nobody
None
2026-07-10
2026-07-10
Anonymous
No

Originally created by: Akarsh-Hegde

Summary

The DMG install path (backend_install.rs) unconditionally staged and registered a separate com.meridiona.a11y-helper launchd agent on every packaged install/update — even though capture has been the default (and only shipped) feature since the Gap-2 Bucket 2 cutover.

That agent's whole job was poking AXManualAccessibility on Electron/Chromium apps so the old external screenpipe process could see their AX tree. Since the cutover, the in-process capture engine's screenpipe-a11y tree walker (tray/src-tauri/src/capture/screenpipe.rs) already does that same poke itself, under the tray's own Accessibility grant (confirmed in that module's own doc comment: "Running the frame grab + OCR + AX walk in this process is what yields the single 'Meridian' Screen-Recording / Accessibility TCC entries"). health.rs even already special-cases #[cfg(feature = "capture")] to skip the a11y-helper trust check for exactly this reason.

So the helper agent was pure dead weight — its only visible effect was a second, confusingly-named "meridian-a11y-helper" entry showing up in System Settings → Privacy & Security → Accessibility, alongside the real "Meridian" entry.

This is also consistent with an existing guard test (tests/tray_assets.rs::install_dev_skips_screenpipe_agents), whose comment already states "a11y capture is in-process inside the Tauri tray since v1.64.0"install-dev.sh was already correctly skipping this; only the packaged DMG install path (backend_install.rs) had missed the memo.

Changes

  • tray/src-tauri/src/backend_install.rs: stop staging/registering the a11y-helper binary + launchd agent in install(). Added cleanup_legacy_a11y_helper() (mirroring the existing cleanup_legacy_screenpipe/cleanup_legacy_mlx_server pattern) that boots out any leftover agent from a prior install, removes its plist + staged binary, kills any live process, and best-effort tccutil resets its Accessibility grant so the stale entry doesn't linger grayed-out in System Settings after the binary is gone. Updated module docs and the bundled_templates_fully_substituted test accordingly.
  • tray/src-tauri/tauri.conf.json: stop bundling meridian-a11y-helper + its plist into Meridian.app/Contents/Resources/backend/.
  • tray/src-tauri/src/lib.rs: updated the ensure_backend_installed call-site comment.
  • tray/src-tauri/src/backend_install.rs (review follow-up): added cleanup_legacy_a11y_helper_removes_stale_files, a regression test proving the leftover plist + staged binary get removed.

Scope note

Intentionally left scripts/a11y-helper/, scripts/install-a11y-helper-daemon.sh, and the npm/curl bundle path (scripts/install-from-bundle.sh) untouched — that install path still runs the external screenpipe process (see its pinned SCREENPIPE_VERSION), where the helper may still be load-bearing. This PR only retires it from the DMG/tray path ("the app"), which is the one built on capture by default and the one this was reported against.

Test plan

  • [x] cargo fmt --check (workspace + tray crate)
  • [x] cargo clippy -- -D warnings (workspace + tray crate)
  • [x] cargo test --lib backend_install — 4/4 pass, including the new cleanup_legacy_a11y_helper_removes_stale_files and the updated bundled_templates_fully_substituted
  • [x] Full pre-push hook (fmt + clippy + UI build + UI tests + security audit + cargo test) — all green
  • [x] Structural: tauri.conf.json's bundle.resources ships only backend/meridian + backend/com.meridiona.daemon.plist — nothing exists for a fresh install to stage/register, so no "meridian-a11y-helper" entry can appear
  • [x] Automated: cleanup_legacy_a11y_helper_removes_stale_files proves the plist + staged binary are actually removed on an update
  • [ ] Manual (needs a real machine/GUI): on a machine with a pre-existing com.meridiona.a11y-helper launchd agent + Accessibility grant, confirm an update boots out the agent and the entry disappears from System Settings → Privacy & Security → Accessibility (the launchctl/tccutil syscalls themselves aren't unit-testable — see PR comment)

🤖 Generated with Claude Code

https://claude.ai/code/session_01NUNrrP7ie4eeJKGPV6EXNp

Related

Tickets: #440

Discussion

  • Anonymous

    Anonymous - 2026-07-10

    Originally posted by: coderabbitai[bot]

    [!IMPORTANT]

    Review skipped

    Auto reviews are disabled on base/target branches other than the default branch.

    Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.


    ⚙️ Run configuration

    Configuration used: Organization UI

    Review profile: ASSERTIVE

    Plan: Pro Plus

    Run ID: ba62c822-5f62-415a-84af-99d5219a714c

    You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

    Use the checkbox below for a quick retry:
    - [ ] 🔍 Trigger review

    ✨ Finishing Touches
    🧪 Generate unit tests (beta) - [ ] Create PR with unit tests - [ ] Commit unit tests in branch `fix/remove-a11y-helper-privacy-entry`

    Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

    ❤️ Share - [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai) - [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai) - [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai) - [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

    Comment @coderabbitai help to get the list of available commands.

     
  • Anonymous

    Anonymous - 2026-07-10

    Originally posted by: adityaharishch

    Review

    Overview: Retires the redundant com.meridiona.a11y-helper launchd agent from the DMG (.app) install path, since the in-process capture engine (screenpipe-a11y tree walker) already does the same AXManualAccessibility poke under the tray's own Accessibility grant. Stops staging/bundling the helper binary + plist, adds cleanup_legacy_a11y_helper() to boot out any leftover install from a prior version, and updates docs/tests accordingly.

    Correctness

    • cleanup_legacy_a11y_helper follows the exact same shape as the existing cleanup_legacy_screenpipe/cleanup_legacy_mlx_server siblings (bootout → remove plist → remove binary → pkill), so it's consistent with established conventions.
    • Verified the tccutil reset Accessibility com.meridiona.a11y-helper call is correct: scripts/a11y-helper/build.sh ad-hoc signs the binary with --identifier com.meridiona.a11y-helper, which is exactly the identifier TCC keys grants to for un-bundled Mach-O binaries. So the reset call will actually clear the stale grant, not silently no-op.
    • Confirmed health.rs's a11y trust-check codepath is already #[cfg(not(feature = "capture"))]-gated, so no dangling reference to the retired agent survives under the default (capture-enabled) build.
    • Confirmed no other tray source file or asset (entitlements, other tauri.conf.json blocks) still references the helper outside the intentionally-untouched npm/bundle scripts under scripts/.
    • Scope note in the description (leaving scripts/a11y-helper/, install-a11y-helper-daemon.sh, install-from-bundle.sh alone since that path still runs external screenpipe) is accurate and well-justified.

    Style / conventions

    • Doc comments updated in lock-step with the code (module header, install(), AGENTS, test doc) — good adherence to the repo's "document as you go" convention.
    • New function carries the required .rs file header (inherited, unchanged) and proper tracing::info! on the one non-best-effort-swallowed success path.

    Minor observations (non-blocking)

    • No new unit test covers cleanup_legacy_a11y_helper directly, but this matches the existing precedent — none of the sibling cleanup fns (cleanup_legacy_screenpipe, cleanup_legacy_mlx_server) have direct unit tests either (they'd need a mocked launchctl/filesystem), so this isn't a regression in coverage.
    • The two manual test-plan items (fresh install shows no stale entry; update path boots out a pre-existing grant) are marked unchecked — worth confirming before merge since this is exactly the observable behavior the fix is for.

    Risk: Low. All changes are additive cleanup + removal of dead staging code; failure modes are best-effort/non-fatal by design (matches existing patterns), so a launchctl/tccutil hiccup can't break tray startup.

    Looks good to merge once the manual verification steps are checked off.

     
  • Anonymous

    Anonymous - 2026-07-10

    Originally posted by: Akarsh-Hegde

    Thanks for the review — addressed the one open item (automated test coverage was called out as a non-blocking gap).

    Fix pushed (fcf8c7c4): added cleanup_legacy_a11y_helper_removes_stale_files, a #[tokio::test] that stages a fake leftover plist + binary under a temp home and asserts cleanup_legacy_a11y_helper removes both. cargo fmt --check, cargo clippy -- -D warnings, and cargo test --lib backend_install (now 4/4) all pass, as does the full pre-push suite.

    On the two manual checklist items:

    • "Fresh install shows no stale entry" — structurally guaranteed, not just expected: tauri.conf.json's bundle.resources only ships backend/meridian + backend/com.meridiona.daemon.plist now (re-verified on this branch — no meridian-a11y-helper binary/plist is bundled), so there is nothing for a fresh .app install to stage or register in the first place.
    • "Update path boots out a pre-existing grant" — the file-removal half (plist + staged binary) is now covered by the new automated test above. The remaining piece — the real launchctl bootout / tccutil reset syscalls actually clearing a live agent + TCC grant on a machine that has the old helper installed — is inherently a real-machine/GUI check (same category as PR [#425]'s manual items), since it needs an actual System Settings inspection to confirm visually. I don't have a way to drive that from here; recommend whoever tests this before merging do a quick smoke check (start from a machine/VM with the old com.meridiona.a11y-helper agent + grant present, run through an "update," confirm the entry disappears from System Settings → Privacy & Security → Accessibility).

    No other code changes were needed — the review didn't flag any correctness issues, and grepping the branch again confirms no other stray staging/bundling reference survives outside the intentionally-untouched npm/bundle scripts.

     

    Related

    Tickets: #425

  • Anonymous

    Anonymous - 2026-07-10

    Ticket changed by: Akarsh-Hegde

    • status: open --> closed
     

Log in to post a comment.