Skip to content

Read running apps' bundle IDs off the main thread - #1875

Merged
r3dbars merged 2 commits into
mainfrom
claude/app-health-check-w19eqh
Sep 26, 2026
Merged

r3dbars merged 2 commits into
mainfrom
claude/app-health-check-w19eqh

Conversation

@r3dbars

@r3dbars r3dbars commented Sep 26, 2026

Copy link
Copy Markdown
Owner

🤖 Generated with Claude Code

https://claude.ai/code/session_0126tPTsXGHmWx6EmsR32sjX

Why

The freeze reporting that shipped in 1.1.66 (#1864) caught its first real freeze. Sentry APPLE-MACOS-2Q, "App hanging for at least 5000 ms": 5 events in 30 minutes from one user on published 1.1.66, with the app idle. Stack: the main thread was inside a Swift async job, in -[NSRunningApplication bundleIdentifier], blocked 5+ s on a synchronous LaunchServices XPC call (_LSCopyApplicationInformation).

Two main-actor async paths scan every running app's bundle ID:

  • MeetingPromptDetector.evaluate(), which runs every 120 s plus on mic, camera and app events. Given the idle context, this is the likely source (inferred; Sentry 9.10 gives no app frame names).
  • BrowserWindowTitleReader.titles, which is @MainActor and filters runningApplications before its AX work.

Product Impact

  • Affects: meetings (call detection, all users)
  • Lane: meeting reliability / app health
  • Why this matters: a slow LaunchServices reply now just delays one detection pass instead of freezing the app.

What changed

  • New RunningApplicationsReader: reads NSWorkspace.shared.runningApplications (pid, bundle ID, regular-app flag) on one serial .utility queue behind a checked continuation. It uses a serial queue, not a detached task, so a stuck reply can't tie up the Swift thread pool (same lesson as Settings reads the login-item status once, off the main thread #1867).
  • MeetingPromptDetector.evaluate() takes bundle IDs from a new injectable runningBundleIDsProvider (default: the reader). now is taken after that await.
  • BrowserWindowTitleReader.titles builds its pid list from the reader.
  • Added the new file to run-tests.sh APP_SOURCES and to Sources/Meeting/CLAUDE.md.

Left alone:

  • frontmostApplication?.bundleIdentifier: the active app's info is already loaded, and tests inject it synchronously.
  • The workspace-notification app's bundle ID.
  • CallAppMicrophoneSharingMonitor.refresh: synchronous and only on app launch/quit; it isn't the async job in the stack.

How I checked it

  • bash scripts/dev/linux-checks.sh: 48 passed
  • python3 scripts/dev/check-source-pins.py --changed-only: PASS
  • bash build-deps.sh --force / bash build.sh --no-open / bash run-tests.sh / bash run-integration-smoke.sh: CI

Checks I could not run, and why:

  • Build, fast tests and integration smoke: cloud session, no Swift toolchain. CI runs them.
  • The freeze itself can't be reproduced on demand (it needs a slow LaunchServices).

Mac or hardware test still needed? If yes:

  • None to merge. After the next release ships, APPLE-MACOS-2Q should stop getting new events from the new version.

Risk Review

  • Privacy / local-first behavior reviewed (bundle IDs stay in memory, as before)
  • Checked the text-pin tests for every file I edited
  • Agent PRs got an independent deep review of the full diff (requested)
  • No private transcripts, audio, tokens, personal paths, or customer data are included

Notes

Found by the 1.1.66 release watch (/mnt/project-files/release-watch/1.1.66-watch-log.md).

Agent handoff

COORD_DONE: BRIEF | this PR | running-apps scan off main (Sentry 2Q) | none | none | linux-checks, source pins | CI + deep review


Generated by Claude Code

Freeze reporting caught its first real freeze on 1.1.66 (Sentry
APPLE-MACOS-2Q): the main thread blocked 5+ s in
-[NSRunningApplication bundleIdentifier], a synchronous LaunchServices
call, inside an async job. Meeting detection and the browser title reader
both scanned NSWorkspace.runningApplications on the main actor. They now
read through RunningApplicationsReader, on one serial background queue.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126tPTsXGHmWx6EmsR32sjX
Review of the freeze fix: the Sentry stack (one unsymbolicated app frame
before -[NSRunningApplication bundleIdentifier]) also fits meeting
detection's app-launch handler, which read a just-launched app's bundle
ID on the main actor. That read, and the call-app monitor's launch/quit
refresh, now go through RunningApplicationsReader (moved to Support, now
userInitiated). The monitor's synchronous pre-capture refresh stays, and
a generation counter drops background reads that land after a newer one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126tPTsXGHmWx6EmsR32sjX
@r3dbars
r3dbars marked this pull request as ready for review September 26, 2026 18:42
@r3dbars
r3dbars merged commit cb900ba into main Sep 26, 2026
8 checks passed
@r3dbars
r3dbars deleted the claude/app-health-check-w19eqh branch September 26, 2026 18:42
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.

2 participants