Added
[download.retry] max_download_attemptsTOML key. Mirrors the existing--max-download-attemptsCLI flag (default10, lifetime cap across syncs).0disables the cap. Resolution order matches every other config key: CLI > TOML > default. (#344)kei list --libraryis now repeatable and accepts the v0.13 selector grammar.kei sync --libraryalready took multi-valueprimary/shared/all/ raw zone names /!nameexclusions;kei list --librarywas typedOption<String>, so a second flag silently won and users couldn't list across multiple libraries. Bothkei list albumsandkei list librariesaccept the same grammar. (#344)
Changed
kei status --failedandkei status --pendingnow stream pages instead of loading every matching row.kei status --downloadedalready paginated; the inconsistency approached OOM on libraries with 100k pending or failed assets. On-screen output is unchanged. (#344)- State DB schema migrates v9 -> v10 on first sync. Adds
enumeration_errors INTEGER NOT NULL DEFAULT 0tosync_runs; existing rows backfill to0(they predate the counter). One-way, idempotent on re-entry. (#337)
Deprecated
[metrics]TOML section andKEI_METRICS_PORTenv var. Both are folded into[server](TOML) andKEI_HTTP_PORT(env). The deprecation warning for[metrics]has shipped since v0.13.0; pre-fix it only fired when[metrics] portwas actually being used to resolvehttp_port, so a config with both[server]and[metrics]set kept[metrics]silently around. TheKEI_METRICS_PORTenv var had the same gap. Both warnings now fire whenever the deprecated input is set, even when shadowed by a higher-precedence value (CLI /[server]TOML /KEI_HTTP_PORT). Removal target: v0.20.0; rename[metrics]to[server]andKEI_METRICS_PORTtoKEI_HTTP_PORTto clear the warnings. (#344)
Fixed
fetch_foldersnow follows CloudKit'scontinuationMarkerso accounts with >200 user albums no longer silently lose the tail. CloudKitrecords/querydefaults toresultsLimit=200and returns acontinuationMarkerwhen more records exist; pre-fixfetch_foldersissued one POST and trusted the response was complete. Tail-album members routed into the unfiled pass instead of{album}paths,--album all '!TailAlbum'exclusions bailed "Excluded album not found" even when the album existed in iCloud, and the truncation replayed every cycle. The loop is capped at 64 pages (~12,800 albums) with a warn on cap-fire so a server pathology stays loud. (#336)--smart-folderagainst a shared-only library set now bails at startup instead of running silently. CloudKit shared zones don't expose smart folders, so--library shared --smart-folder Favoritespreviously warn-and-skipped every smart-folder pass and exited 0. Users looked at primary-library Favorites and assumed shared-Favorites came along. A pre-flight insync_loopnow bails when the resolved library set has zero zones that support smart folders AND--smart-folderis set. Mixed sets (primary + shared) still pass through: primary handles smart folders, shared zones' lack of smart-folder passes is documented and expected. (#336)- Atomic install now fsyncs the parent directory after rename. ext4 default
data=ordereddoesn't guarantee directory entry durability afterrenamereturns Ok; a power loss between rename and the kernel committing the dir block could leave the file absent on reboot. The credentials and session-file path already did this; the data path was strictly weaker.rename_part_to_finalnow mirrors the pattern (warn-and-continue on fsync failure, since worst case is one redundant re-download next sync). (#337) log_sync_summarynow surfacesenumeration_errors. The error-counters line previously printed EXIF write failure(s) and state write failure(s) but skipped enumeration errors, so an operator chasing exit code 2 from aPartialFailuredriven purely by enumeration errors saw no count. Line two of the summary now appends "Z enumeration error(s)" when nonzero. (#337)kei <sync-only-flag> <non-sync-subcommand>now errors instead of silently running the subcommand with the flag swallowed.keiaccepts a bare invocation as shorthand forkei sync, so sync flags like--skip-videosare wired at the top level. clap then acceptedkei --skip-videos=true status: the flag was consumed, the status command ran, and the user thought their flag had been honored. Same trap applied to every top-level sync flag (download paths, threads, dry-run, the deprecated--auth-only/--list-albumsaliases). The new validator names every offender. Barekei,kei sync, andkei retry-failedcontinue to accept the flags. (#343)kei reset sync-tokennow requires confirmation. A typo or muscle memory afterreset statecould clear the sync token on a 100k-asset library and force a full re-enumeration on the next sync.reset stateshipped--yesplus a TTY prompt;reset sync-tokenshipped neither. Now prompts on a TTY (warning that the next sync re-enumerates every asset) and errors under non-interactive use without--yes. The hidden legacy aliaskei reset-sync-tokenkeeps its auto-confirm behavior so shell scripts and Docker callers don't break before the v0.20 removal. (#343)
Full changelog: CHANGELOG.md