You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Now actionable (2026-07-25). Both things that blocked this are resolved: the face-detection rewrite landed (#268 / PR #273), and the owner decided this pass runs first, ahead of photo descriptions. The description job was split out into #275.
Added 2026-07-25 after review. This pass cannot see HEIC photos, and it fails silently: detectFaces throws on every HEIC and face-worker.js:178-181 logs the error and continues. The job would finish, report a face count, and never tell you it skipped 62,614 photos — 47% of the library.
Nothing on this machine can decode a HEIC today (measured 2026-07-25): sharp's bundled libheif fails on all sampled files, and there is no ffmpeg, ImageMagick, or Windows HEIF codec. Ingestion worked only because exifr reads EXIF metadata without decoding pixels — this pass is the first code that needs the pixels.
The gap is concentrated in exactly the years with the most people in frame:
year
HEIC
other
HEIC share
2019
0
8,738
0%
2021
10,338
10,481
50%
2022
12,765
3,602
78%
2023
16,314
6,001
73%
2025
6,654
4,909
58%
2026
3,202
1,374
70%
Running before #280 lands means person search works well for 2015-2019 and goes progressively blind through the years you have the most photos of people — with no error to tell you so.
One piece of good news: the continue fires before the photo is written to the state file, so skipped photos are never marked done. Land #280 and re-run — everything is picked up, with no state to clear. But it is cheaper to land #280 first than to run this pass twice.
#280 also fixes the same blocker for #275, which needs it too.
Problem
The photo library — about 129,000 photos — can currently be searched only by when and where a photo was taken, plus the folder it came from. You can't ask for photos of a particular person.
There's an already-built job (connectors/photo-exif/face-worker.js) that fixes this. It finds faces in each photo, groups the ones that look like the same person into anonymous piles, and then — once you tell it "that pile is Amy" — tags every photo in that pile with her name. Those tags are what make "show me photos of Amy" work.
It has never run on this machine — verified 2026-07-25: zero photos have any face information.
Why it's runnable now
Two things were in the way, both now cleared:
The face-detection software wouldn't install. The old setup needed a component with no prebuilt version for this machine's Node version, and building it from scratch needed a Visual Studio C++ toolchain that isn't installed. feat(photo-exif): swap face stack from tfjs-node to onnxruntime-node #268 replaced that whole stack with one that ships ready-to-run binaries. No build step anymore.
Ordering: this runs first, descriptions run second
Both jobs replace a photo's stored text and small metadata record rather than adding to them — so whichever runs second overwrites some of what the first one wrote. Understanding that tradeoff is the whole reason ordering matters.
Nothing to redo — #276 made the two passes preserve each other
No longer overwritten (#276, merged 2026-07-25). The description job used to wipe this pass's bookkeeping — the detected-face count and the "Pictured: …" sentence. Both jobs now build their photo record through one shared component, so each carries the other's work through. Nothing is lost in either direction and no re-run is needed.
One rule remains: run the two passes one at a time, never overlapping. Each reads the other's state once when it starts, so naming a face while a description run is in flight would still be missed by that run.
(Faces-first was chosen on 2026-07-25 to avoid delaying person search by months. Since #276 the order no longer costs anything either way.)
Current state (verified 2026-07-25)
Thing
State
Photos in scope
129,143
— of those, with face info
0
— of those, from Google Photos (so they have Google's own people tags)
129,127 (99.99%)
Photos not in scope (came from Messages, outside the photo folder)
5,395
Face models downloaded?
no — FACE_MODELS_PATH is unset
Job's progress files
absent
Photo import finished?
yes — nothing new since 2026-07-17
How naming the piles works — two paths, cheap one first
Because 99.99% of the library came from Google Photos, most photos carry Google's own people tags. That makes the cheap path the primary one:
From Google's tags (feat(photo-exif): suggest cluster labels from single-name Google Photos sidecars #272 — free, no AI, no network). Where a photo has exactly one face and Google tagged exactly one person, that's a vote for "this pile is that person." Piles with a clear enough majority get a suggested name printed. This should name most piles for nothing.
Both paths only ever print suggestions. Neither ever applies a name by itself — you confirm each one. A wrong automatic name would pollute the contact graph worse than leaving the pile anonymous.
Download the two face models into a folder and point FACE_MODELS_PATH at it (currently unset in connectors/photo-exif/.env): det_10g.onnx (finds faces) and w600k_r50.onnx (recognizes them), the buffalo_l pair from the InsightFace model zoo. One-time download; fully offline afterward.
Run the pass:node face-worker.js from connectors/photo-exif/. No pause between photos by default, so it runs at full speed. Saves progress after every photo and resumes, so killing it mid-run is safe. Watch the log for face detection failed for ... lines — a run producing tens of thousands of them means fix(photo-exif): shared HEIC-capable image decode for both AI passes #280 didn't take, and the job will still exit reporting success.
Get name suggestions, cheap path first:node face-worker.js suggest-from-sidecars, then node face-worker.js export-thumbnails ./faces and suggest-labels for whatever's left unnamed.
Confirm the names you recognize:node face-worker.js label <id> "<name>" for each. This is the step that actually creates the person tags.
The face pass completes over the library; the count of photos with face information climbs from 0 and is reported.
HEIC photos were actually examined — roughly 47% of the photos with face information are .heic, and the run did not emit a large volume of face detection failed lines. Without this, a run that skipped half the library still passes the criterion above.
suggest-from-sidecars runs and its suggestion count is reported (how many piles Google's tags named for free).
At least one pile is confirmed with a real name, and that person's photos then surface when searching for them.
No photo gets duplicated — face info attaches to the existing photo record.
The date, location, place name, and original-file pointer on each photo are unchanged after the job touches it.
Store→recall smoke test still green (server boots, 0 errors).
Problem
The photo library — about 129,000 photos — can currently be searched only by when and where a photo was taken, plus the folder it came from. You can't ask for photos of a particular person.
There's an already-built job (
connectors/photo-exif/face-worker.js) that fixes this. It finds faces in each photo, groups the ones that look like the same person into anonymous piles, and then — once you tell it "that pile is Amy" — tags every photo in that pile with her name. Those tags are what make "show me photos of Amy" work.It has never run on this machine — verified 2026-07-25: zero photos have any face information.
Why it's runnable now
Two things were in the way, both now cleared:
Ordering: this runs first, descriptions run second
Both jobs replace a photo's stored text and small metadata record rather than adding to them — so whichever runs second overwrites some of what the first one wrote. Understanding that tradeoff is the whole reason ordering matters.
Chosen order — faces first:
No longer overwritten (#276, merged 2026-07-25). The description job used to wipe this pass's bookkeeping — the detected-face count and the "Pictured: …" sentence. Both jobs now build their photo record through one shared component, so each carries the other's work through. Nothing is lost in either direction and no re-run is needed.
One rule remains: run the two passes one at a time, never overlapping. Each reads the other's state once when it starts, so naming a face while a description run is in flight would still be missed by that run.
(Faces-first was chosen on 2026-07-25 to avoid delaying person search by months. Since #276 the order no longer costs anything either way.)
Current state (verified 2026-07-25)
FACE_MODELS_PATHis unset2026-07-17How naming the piles works — two paths, cheap one first
Because 99.99% of the library came from Google Photos, most photos carry Google's own people tags. That makes the cheap path the primary one:
Both paths only ever print suggestions. Neither ever applies a name by itself — you confirm each one. A wrong automatic name would pollute the contact graph worse than leaving the pile anonymous.
Implementation Plan
Land fix(photo-exif): shared HEIC-capable image decode for both AI passes #280 first (HEIC decode). Without it this pass silently skips 47% of the library — see the blocked-by note at the top. Everything below assumes fix(photo-exif): shared HEIC-capable image decode for both AI passes #280 has merged.
Download the two face models into a folder and point
FACE_MODELS_PATHat it (currently unset inconnectors/photo-exif/.env):det_10g.onnx(finds faces) andw600k_r50.onnx(recognizes them), the buffalo_l pair from the InsightFace model zoo. One-time download; fully offline afterward.Install the connector's dependencies:
npm installinconnectors/photo-exif/. No compiler needed — both packages ship prebuilt binaries (this is what feat(photo-exif): swap face stack from tfjs-node to onnxruntime-node #268 fixed).Run the pass:
node face-worker.jsfromconnectors/photo-exif/. No pause between photos by default, so it runs at full speed. Saves progress after every photo and resumes, so killing it mid-run is safe. Watch the log forface detection failed for ...lines — a run producing tens of thousands of them means fix(photo-exif): shared HEIC-capable image decode for both AI passes #280 didn't take, and the job will still exit reporting success.Get name suggestions, cheap path first:
node face-worker.js suggest-from-sidecars, thennode face-worker.js export-thumbnails ./facesandsuggest-labelsfor whatever's left unnamed.Confirm the names you recognize:
node face-worker.js label <id> "<name>"for each. This is the step that actually creates the person tags.No follow-up re-run needed — fix(photo-exif): caption pass must preserve face enrichment #276 made the description job preserve this pass's output. Just don't run the two passes at the same time.
Acceptance Criteria
.heic, and the run did not emit a large volume offace detection failedlines. Without this, a run that skipped half the library still passes the criterion above.suggest-from-sidecarsruns and its suggestion count is reported (how many piles Google's tags named for free).Out of Scope