Immich-photo-manager library-health-report
git clone https://github.com/drolosoft/immich-photo-manager
T=$(mktemp -d) && git clone --depth=1 https://github.com/drolosoft/immich-photo-manager "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/library-health-report" ~/.claude/skills/drolosoft-immich-photo-manager-library-health-report && rm -rf "$T"
skills/library-health-report/SKILL.mdLibrary Health Report
⚠️ Connection Required — ALWAYS CHECK FIRST
Before doing ANYTHING else in this skill, call
on the Immich MCP server.ping
- If
succeeds → proceed with the skill normally.ping - If
fails or the MCP tools are not available → STOP. Do not continue. Tell the user:ping
❌ Immich is not connected. This plugin needs a running Immich MCP server to work.
Run /setup-immich-photo-manager to configure your Immich connection. You'll need:
- Your Immich server URL (e.g.,
)http://192.168.1.100:2283- An Immich API key (how to create one)
- The MCP server configured (see /setup-immich-photo-manager)
Nothing in this plugin will work until the connection is configured.
Do NOT skip this check. Do NOT try to run any other tool first. Always ping, always block if it fails.
Generate a comprehensive health assessment of an Immich photo library. Covers asset inventory, storage breakdown, metadata quality, and actionable recommendations.
When to Use
- First time exploring a new Immich library
- Periodic checkups (monthly/quarterly)
- After bulk imports to verify everything landed correctly
- Before cleanup operations to understand baseline
Report Sections
Section 1: Asset Inventory
Query Immich for the full library snapshot:
-- Total assets by type SELECT type, count(*) as total, pg_size_pretty(sum(("exifInfo"->>'fileSizeInByte')::bigint)) as total_size FROM asset WHERE "deletedAt" IS NULL GROUP BY type; -- Trash contents SELECT type, count(*) as trashed FROM asset WHERE "deletedAt" IS NOT NULL GROUP BY type;
Also use the MCP tool
get_statistics for the official Immich counts.
Present as:
ASSET INVENTORY Photos: 39,596 Videos: 4,983 Total live: 44,579 In trash: 8,433 Storage used: ~180 GB
Section 2: Import Sources
Identify where photos came from:
SELECT CASE WHEN "originalPath" LIKE '%Apple Fotos%' OR "originalPath" LIKE '%Apple Photos%' THEN 'Apple Photos' WHEN "originalPath" LIKE '%Google Fotos%' OR "originalPath" LIKE '%Google Photos%' THEN 'Google Photos' WHEN "originalPath" LIKE '%upload%' THEN 'Direct Upload' ELSE split_part("originalPath", '/', 5) END as source, count(*) as total, min("localDateTime") as earliest, max("localDateTime") as latest FROM asset WHERE "deletedAt" IS NULL GROUP BY source ORDER BY total DESC;
Section 3: Metadata Completeness
Check for gaps in critical metadata fields:
-- GPS coverage SELECT count(*) as total, count(*) FILTER (WHERE "exifInfo"->>'latitude' IS NOT NULL) as has_gps, round(100.0 * count(*) FILTER (WHERE "exifInfo"->>'latitude' IS NOT NULL) / count(*), 1) as gps_pct FROM asset WHERE "deletedAt" IS NULL AND type = 'IMAGE'; -- Date quality SELECT count(*) FILTER (WHERE "exifInfo"->>'dateTimeOriginal' IS NOT NULL) as has_exif_date, count(*) FILTER (WHERE extract(hour from "localDateTime") = 12 AND extract(minute from "localDateTime") = 0) as suspicious_noon, count(*) FILTER (WHERE extract(hour from "localDateTime") = 0 AND extract(minute from "localDateTime") = 0) as suspicious_midnight FROM asset WHERE "deletedAt" IS NULL AND type = 'IMAGE'; -- Camera info SELECT count(*) FILTER (WHERE "exifInfo"->>'make' IS NOT NULL) as has_camera_make, count(*) FILTER (WHERE "exifInfo"->>'lensModel' IS NOT NULL) as has_lens FROM asset WHERE "deletedAt" IS NULL AND type = 'IMAGE';
Present as:
METADATA QUALITY GPS coverage: 72.3% (28,616 of 39,596 photos) EXIF dates: 89.1% (35,280 photos) Suspicious dates: 1,204 (noon/midnight — likely recovered from paths) Camera info: 68.4% (27,072 photos) Lens info: 52.1% (20,623 photos)
Section 4: Time Distribution
SELECT extract(year from "localDateTime") as year, count(*) as photos, count(*) FILTER (WHERE type = 'VIDEO') as videos FROM asset WHERE "deletedAt" IS NULL GROUP BY year ORDER BY year;
Highlight years with unusually low counts (potential missing imports).
Section 5: File Format Breakdown
SELECT upper(split_part("originalPath", '.', -1)) as format, count(*) as total, pg_size_pretty(sum(("exifInfo"->>'fileSizeInByte')::bigint)) as size FROM asset WHERE "deletedAt" IS NULL GROUP BY format ORDER BY total DESC LIMIT 15;
Section 6: Recommendations
Based on findings, generate actionable recommendations:
- Low GPS coverage? → Suggest metadata-fixer skill
- Suspicious dates? → Suggest metadata-fixer skill
- Multiple import sources? → Suggest duplicate-report skill
- Large trash? → Suggest reviewing and permanently deleting
- Year gaps? → Suggest timeline-gaps skill
- Heavy storage? → Suggest storage-optimizer skill
Output Format
The report can be presented as:
- Inline — formatted text in the conversation
- Markdown file — saved to the user's vault/workspace
- HTML dashboard — interactive file with charts (uses Chart.js or Recharts)
Always ask the user which format they prefer.
Important Notes
- This skill is read-only — it never modifies assets or metadata
- Uses both Immich MCP tools (for official counts) and direct database queries (for deep analysis)
- Database access requires PostgreSQL connection details
- For libraries >50K assets, some queries may take 10-30 seconds
- Always show the query execution time so users know what to expect