Skip to content

Apple Mail search says when it is broken instead of returning nothing (#446 option 1) - #469

Open
davekilleen wants to merge 1 commit into
mainfrom
fix/apple-mail-search-honest-status
Open

Apple Mail search says when it is broken instead of returning nothing (#446 option 1)#469
davekilleen wants to merge 1 commit into
mainfrom
fix/apple-mail-search-honest-status

Conversation

@davekilleen

Copy link
Copy Markdown
Owner

Summary

Closes the silent-failure path reported in #446. Takes option 1 from that issue (guided setup + honest health). Option 3 (the upstream ask) is being routed to the server's maintainer; option 2 (assessing alternative mail backends) is deferred.

Community Apple Mail servers have two data paths with different permission models, and only one of them announces failure:

Path Needs On failure
List / read Automation permission Prompts — you notice
Search A local index built only by a manual apple-mail-mcp index run, which itself needs Full Disk Access Returns empty, silently, forever

Because list and read keep working, the integration looks healthy. Search returns empty with a "try fewer keywords" hint, so the model retries different keywords instead of diagnosing, email-aware flows degrade to listing-and-reading, and nothing ever surfaces that the index does not exist. The reporter ran that way for months. The server's startup sync also no-ops silently without Full Disk Access, so even a once-built index quietly stales — producing the same false-empty answers as a missing one.

This is squarely the feature_status contract in CLAUDE.md: a broken feature must say broken and hand back the fix, not degrade quietly.

The check

New deep check mail.apple-search in core/utils/doctor.py, modelled directly on calendar.access (the closest existing probe — macOS-only, permission-dependent, external tool):

  • OFF — no Apple Mail server registered. The majority case; never nags.
  • UNKNOWN — registered but not macOS, or the index could not be read (including the existing sandbox-failure shape).
  • BROKEN — command missing, index never built, index empty, or index older than 7 days. Each carries the exact fix and names the Full Disk Access prerequisite, via a tier-3 Heal and a user_message.
  • OK — index exists and is fresh.

Registration is detected at user scope (~/.claude.json) as well as project scope, because user scope is what Dex's own ensure-mcp-user-scope.cjs hook steers people towards — and it is how the reporter had it registered. A project-scope-only check would have reported OFF on the exact vault that was broken.

Staleness is treated as broken rather than merely noted, because a stale index and a missing one are indistinguishable from where the user sits: both return answers that look like "no matching email."

The guided setup

New /apple-mail-setup skill, following the calendar-setup pattern. The ordering is the substance: Full Disk Access is granted and Terminal relaunched before the index build, not after — that ordering is what people get wrong, and getting it wrong is what produces the silent failure. The flow ends in apple-mail-mcp status plus /dex-doctor verification rather than assuming success, and names the "quit Terminal completely" trap that makes a granted permission look ungranted.

Verification

  • 9 new tests in core/tests/test_doctor.py covering all six verdict paths and registration detection at either scope.
  • core/tests/test_doctor.py: 310 passed, 2 failed — both (test_entity_dead_letter_heal_round_trip_returns_probe_to_ok, test_launchctl_domain_failure_is_an_unknown_instrument) fail identically on clean main on Linux, being launchctl-dependent. Confirmed by running them on a detached checkout of origin/main.
  • core/tests/test_skill_integrity.py: 119 passed (new skill frontmatter valid).
  • core/tests/test_release_catalog*.py, test_release_tag_uniqueness.py: 50 passed.
  • docs/architecture/INVENTORY.md regenerated; scripts/check-architecture-inventory.sh reports current.

Not verified here: the macOS-only paths cannot execute on this Linux host — _is_macos() and _apple_mail_cli_present() are monkeypatched in tests, as calendar.access already does for EventKit. The probe's real-machine behaviour wants one confirmation run on a Mac.

Changelog entry added as [1.98.0] in the house plain-English style.

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown

What this pull request touches

  • skills — feeds the guided workflows and commands people use with Dex.
  • the trust engine — feeds safe diagnostics and health checks for installed and customized Dex setups.
  • tests — feeds catching regressions before contributors and users encounter them.

Gates that will judge this change

  • Personal-data gate: added lines must not expose real identities or personal vault content.
  • Change-aware gates: source changes are checked for tests, path-contract use, documentation drift, and touched-file coverage.
  • Tests and coverage: the Python, MCP, migration, hook, and script suites must remain healthy.
  • Safety and quality: security, lint, distribution, path consistency, and large-vault checks still apply.

Based on 6 changed files.

@davekilleen
davekilleen force-pushed the fix/apple-mail-search-honest-status branch from 948b818 to 42cacb1 Compare August 11, 2026 18:43
Community Apple Mail servers have two data paths with different permission
models. List and read drive Mail.app through Automation permission and work.
Search queries a local index that only exists after a manual `apple-mail-mcp
index` run, which itself requires Full Disk Access. When that index is absent
the search tool returns empty-with-a-keyword-hint rather than an error, so the
model retries different keywords instead of diagnosing, and email-aware flows
degrade to listing-and-reading with no signal that anything is wrong. The
server's startup sync also no-ops silently without Full Disk Access, so even a
once-built index quietly stales. Reported in #446 after months of undetected
empty results.

Adds a deep Doctor check, mail.apple-search, modelled on calendar.access:

- OFF when no Apple Mail server is registered, so it never nags the majority
  who have not opted in. Registration is detected at user scope
  (~/.claude.json, the scope Dex's own ensure-mcp-user-scope hook steers people
  to) as well as project scope.
- UNKNOWN when a server is registered off macOS, or the index cannot be read,
  including the existing sandbox-failure shape.
- BROKEN, with the exact fix and the Full Disk Access prerequisite named, when
  the command is missing, the index was never built, the index is empty, or the
  index is older than seven days. Staleness is reported because a silently
  stale index produces the same false-empty answers as a missing one.
- OK only when an index exists and is fresh.

Adds /apple-mail-setup, which orders the steps so the one that gets skipped
cannot be: Full Disk Access is granted, and Terminal relaunched, before the
index build rather than after, and the flow ends in verification rather than
assumption.

Tests cover all six verdict paths plus registration detection at either scope.
The two failures in test_doctor.py on Linux (launchctl-dependent) are
pre-existing and unrelated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@davekilleen
davekilleen force-pushed the fix/apple-mail-search-honest-status branch from 42cacb1 to b8a6b20 Compare August 12, 2026 00:29
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.

1 participant