launchservices-health

v2026.09.24

Diagnose macOS LaunchServices DB bloat and launchservicesd CPU spikes. Use when launchservicesd is pegged at high CPU or WindowServer XPC stalls are suspected.

GitHub
Install command
npx skhub add laurigates/launchservices-health
Markdown
SKILL.md

LaunchServices Health (macOS)

When to Use This Skill

Use this skill when...Use something else when...
launchservicesd is at sustained high CPUGeneral process forensics — use ps/top directly
GUI feels sluggish after weeks of uptimeThe machine actually hung — see macos-incident-postmortem
Quantifying LS DB bloat (size, bundle count, claim count)Checking app launch failures — use system_profiler SPApplicationsDataType
Deciding whether lsregister rebuild is warrantedFirst-time install setup — use configure-plugin

Platform Guard

This skill is macOS-only. The lsregister binary, launchservicesd daemon, and the LaunchServices database are Darwin-specific. Refuse to act if uname -s is not Darwin.

test "$(uname -s)" = "Darwin" || { echo "macos-plugin: not Darwin, refusing"; exit 1; }

Core Expertise

LaunchServices is the macOS subsystem that maps documents and URL schemes to applications. State is held by launchservicesd and persisted to a binary database. Two failure modes matter:

  1. DB bloat — over months of uptime and app churn (Homebrew updates, Xcode reinstalls, Wine/CrossOver bottle creation, Electron app upgrades), the DB accumulates stale bundle records and duplicate claims. The bigger the DB, the slower lsregister -dump runs and the more memory launchservicesd holds.
  2. Daemon wedge — launchservicesd can spin at high CPU when a misbehaving process repeatedly registers/unregisters bundles. WindowServer makes synchronous XPC calls into launchservicesd for icon and document-handler lookups; a wedged daemon manifests as a frozen GUI.

The diagnostic answer is always: measure first, rebuild only if warranted. Rebuild (lsregister -kill -r ...) takes minutes, makes every app's "open with" disappear briefly, and rebuilds the DB from scratch — only do it when the metrics justify it.

Where the bits live

PathPurpose
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregisterThe lsregister CLI — not on $PATH, must be invoked by absolute path
~/Library/Application Support/com.apple.PersistentURLTranslator/Per-user persisted state
/var/folders/*/0/com.apple.LaunchServices*LS cache directories (varies by macOS version)

Quick Snapshot

The reference script ls-stats produces a one-page health snapshot. Install at ~/.local/bin/ls-stats (mode 0755):

#!/usr/bin/env bash
# ls-stats: macOS LaunchServices DB + launchservicesd health snapshot
set -euo pipefail

[ "$(uname -s)" = "Darwin" ] || { echo "not Darwin" >&2; exit 1; }

LSREG=/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister
PID=$(pgrep -x launchservicesd) || { echo "launchservicesd not running" >&2; exit 1; }

DUMP=$(mktemp); trap 'rm -f "$DUMP"' EXIT
"$LSREG" -dump >"$DUMP" 2>/dev/null

echo "=== launchservicesd ==="
ps -o pid,rss,etime,command -p "$PID" | sed 's/^/  /'

echo
echo "=== LaunchServices DB ==="
wc -c <"$DUMP" | awk '{printf "  size:    %d bytes (%.1f MB)\n", $1, $1/1048576}'
printf "  bundles: %d\n" "$(grep -c '^bundle id:' "$DUMP")"
printf "  claims:  %d\n" "$(grep -c '^claim '    "$DUMP")"
printf "  records: %d\n" "$(grep -c '^----'      "$DUMP")"

echo
printf "=== Diagnostic reports ===\n  launchservicesd CPU events: %d\n" \
  "$(ls /Library/Logs/DiagnosticReports/ 2>/dev/null | grep -c launchservicesd)"

echo
echo "=== Uptime ==="
uptime | awk '{print "  " $0}'

The script prints four blocks: daemon process state (RSS, etime), DB size and counts, recent CPU diagnostic-report count, and current uptime. All numbers are absolute — interpret them against the baseline thresholds below.

Interpreting the Snapshot

launchservicesd RSS

RSS (KB)Meaning
< 50 000 (~50 MB)Healthy
50 000 – 150 000Normal for active use
150 000 – 400 000Bloated; consider rebuild
> 400 000 (~400 MB)Pathological; rebuild warranted

Daemon etime (elapsed time since launch)

launchservicesd is launched once per boot and lives until reboot. etime close to uptime is normal. etime significantly less than uptime means the daemon was relaunched mid-session — usually after a crash; check Diagnostic Reports.

DB size

SizeMeaning
< 5 MBNormal
5 – 20 MBHealthy heavy-app workstation
20 – 50 MBBloated; rebuild improves things
> 50 MBPathological

Bundle / claim / record counts

There is no absolute threshold — these matter relative to the expected number of installed apps. Useful sanity check:

ls /Applications /Applications/Utilities ~/Applications 2>/dev/null | wc -l

If bundle id: count is 5x or more above the visible-.app count, the DB has stale records.

Diagnostic-report CPU events

/Library/Logs/DiagnosticReports/launchservicesd-*.cpu_resource.diag files indicate the daemon hit a CPU threshold (typically 80% sustained for 90s). Each file is one event. Recent volume:

find /Library/Logs/DiagnosticReports -name 'launchservicesd*.cpu_resource.diag' -mtime -7 | wc -l
Count (last 7 days)Meaning
0Normal
1 – 3Notable; monitor
4+Pattern; likely a misbehaving registrar — investigate

Rebuild Procedure

Rebuild only when the snapshot shows multiple red flags. The rebuild takes 1–10 minutes depending on app count.

# Rebuild the LaunchServices database
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister \
  -kill -r -domain local -domain system -domain user

What the flags do:

FlagEffect
-killReset the existing database first
-rRecursively scan for bundles to register
-domain local/Applications
-domain system/System/Applications, /System/Library
-domain user~/Applications, ~/Library/Application Support

Side effects:

  • "Open With" submenus regenerate (a few seconds of "no apps available")
  • Default-app preferences are preserved (stored separately)
  • The Dock badge and icon caches are NOT affected
  • A new launchservicesd process is spawned; PID changes

After rebuild

Re-run ls-stats to confirm RSS, DB size, and counts dropped.

Common Patterns

Snapshot before/after a rebuild

ls-stats > /tmp/ls-before.txt
"$LSREG" -kill -r -domain local -domain system -domain user
ls-stats > /tmp/ls-after.txt
diff -u /tmp/ls-before.txt /tmp/ls-after.txt

Track DB growth across boots

Append a snapshot to a logbook on each session:

{ date -Iseconds; ls-stats; echo "---"; } >> ~/.local/state/launchservices.log

Plot or grep over time to see whether DB size is monotonically growing.

Identify the registrar driving CPU

When launchservicesd is hot and you suspect a specific app:

log stream --predicate 'process == "launchservicesd"' --style syslog | head -200

Look for repeated bundle id or path strings — that's the offender.

Check for bottle-creation amplification

Wine/CrossOver bottle creation registers hundreds of .exe-as-bundle entries. Count them:

"$LSREG" -dump 2>/dev/null | grep -c -i 'wineprefix\|bottle'

A 4-digit answer is the smoking gun.

Justfile Recipe

[doc('Quantify LaunchServices DB and launchservicesd state (macOS)')]
[macos]
ls-stats:
    @~/.local/bin/ls-stats

The [macos] attribute (just 1.34+) skips the recipe on non-Darwin. On older just versions, drop the attribute and let the script's uname -s check do the gating.

Agentic Optimizations

ContextCommand
One-line size$LSREG -dump 2>/dev/null | wc -c | awk '{printf "%.1f MB\n", $1/1048576}'
RSS onlyps -o rss= -p "$(pgrep -x launchservicesd)"
Bundle count$LSREG -dump 2>/dev/null | grep -c '^bundle id:'
Recent CPU eventsfind /Library/Logs/DiagnosticReports -name 'launchservicesd*.cpu_resource.diag' -mtime -7 | wc -l
Daemon uptime vs system uptimeps -o etime= -p "$(pgrep -x launchservicesd)"; uptime

Quick Reference

Key paths

PathPurpose
/System/.../Support/lsregisterThe CLI
/Library/Logs/DiagnosticReports/CPU and crash diag files
~/Library/Application Support/com.apple.PersistentURLTranslator/Per-user state

Useful flags

FlagDescription
-dumpPrint the entire DB to stdout
-kill -r -domain ...Rebuild
-hList all flags (undocumented elsewhere)
-f <bundle>Force-register a single bundle
-u <bundle>Unregister a bundle

Indicator thresholds (rough)

MetricHealthyBloatedRebuild
DB size< 5 MB20–50 MB> 50 MB
Daemon RSS< 50 MB150–400 MB> 400 MB
Bundle count vs visible apps1–2x3–5x> 5x
CPU diag events / 7 days01–34+

Error Handling

SymptomCauseFix
lsregister: command not foundNot on $PATH by designUse the full /System/.../Support/lsregister path
lsregister -dump exits non-zero with stderrPermissions or DB corruptionCapture stderr separately; rebuild may resolve it
pgrep -x launchservicesd returns nothingDaemon crashed and not yet relaunchedCheck Diagnostic Reports; force a relaunch with lsregister -kill -r ...
Rebuild appears to hangIndexing many app bundles is genuinely slowWait — typical 1–10 min depending on app count
Sudden RSS jump after Homebrew upgradeMany bundles re-registered at onceExpected; will settle within minutes

Related Skills

  • macos-incident-postmortem — when LaunchServices CPU caused a GUI hang, parse Diagnostic Reports there
  • kitty-session-persistence — recover terminal sessions lost when WindowServer wedged on an LS XPC call
Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

MIT

Source path

macos-plugin/skills/launchservices-health

Default branch

main

Latest commit

1668324

Tree SHA

b2d4cc3