Bulk delete merged staged batches by physically moving each loser's
files into the winner's batch dir with os.rename. Cross-volume bulks
(winner and loser on different filesystems) always hit EXDEV, forcing a
rollback and degrading to the batch_ids array with per-batch undo.
Merge is now manifest-only: loser entries are appended to the winner's
manifest with their staged paths unchanged, so staged files keep living
in each model's own .lm-pending-delete/<batch_id> dir (no data IO, no
EXDEV). Loser dirs are recorded in the winner manifest's merged_sources
and each loser manifest is stamped merged_into so its own purge timer, a
post-restart sweep or a direct undo call no-op. A cross-volume bulk is
one undoable batch again, and undo/purge clean up the loser dirs once
the merged batch settles.
Phase 2 of docs/plans/issue-1085-rate-limit-design.md:
- Batch import: items that fail due to vendor rate limiting are now
SKIPPED with a "re-run the import later" hint instead of FAILED, so a
transient 429 no longer pollutes failure accounting; the progress
broadcast carries a rate_limited flag.
- Batch import UI: show a one-time "rate limited — slowing down" toast
and swap the running status text while rate_limited; i18n keys synced
to all locales.
- Downloader: download_file / download_to_memory / get_response_headers
register 429 cooldowns with the RateLimitCoordinator, so subsequent
API calls queue behind a download-triggered rate-limit window.
Implement Phase 1 of docs/plans/issue-1085-rate-limit-design.md:
- New RateLimitCoordinator: per-host shared Retry-After gate with
exponential backoff (30s base, 1800s cap), minimum inter-request pacing
(default 0.75s), herd-free waiter serialization via per-destination
locks, and a bounded wait (default 300s) that raises instead of parking.
- Downloader.make_request: connectivity-guard fail-fast first, then gate
pacing; on 429 register the cooldown and wait-and-resend (bounded);
errors that passed through the gate are marked gate_handled.
- FallbackMetadataProvider / MetadataSyncService: a network provider 429
no longer fails over to other network providers (stops the CivArchive
flood); sqlite stays as local last resort. Rate-limited lookups now
report "Rate limited" instead of "Model not found", so transient 429s
no longer mark models civitai_deleted.
- _RateLimitRetryHelper skips its own sleep for gate_handled errors,
removing the double wait.
- New settings: rate_limit_gate_enabled, rate_limit_max_wait_seconds,
rate_limit_min_interval_seconds.
Address the rate-limit flood and secondary errors seen during large
recipe ingestion (example-images directory import):
- batch import: share one adaptive-concurrency semaphore across the whole
batch (previously each item got a fresh semaphore, so the min/max
concurrency bounds never applied and every item ran concurrently);
synchronize the shared semaphore capacity after each completed item.
- comfy parser: guard ckpt_name against list/None values so re.search no
longer raises TypeError and fails the whole image import.
- civarchive client: normalize empty-string failure payloads to
"Request failed" and treat a missing payload as an error, fixing the
"'NoneType' object has no attribute 'get'" crash.
- civarchive client: log connectivity-guard offline-cooldown
short-circuits at DEBUG instead of one ERROR per request.
Add LORA_MANAGER_SETTINGS_DIR env var and standalone --settings-path to pin
the settings location (settings.json, cache/, wildcards/, backups/, logs/,
stats/) to an arbitrary directory. The override takes precedence over
portable mode and the platform user config dir, and skips legacy migration,
so sandboxed dev/E2E runs no longer need to write settings.json in the repo
root or collide with the real instance.
standalone.py pre-scans argv for --settings-path at import time because the
settings location is resolved before main() parses arguments. SettingsManager
portable-switch migration is a no-op while the directory is pinned.
Update the lora-manager-e2e skill (prefer --settings-path sandboxing;
start_server.py passes it through) and the lora-manager-runtime-context
skill (document precedence; inspect script honors the override).
- Log expected "GID not found" tellStatus probes at DEBUG, and treat a
forgotten GID as permanent so the poll loop recovers immediately
instead of burning 4 retries x 3s of ERROR lines per cycle
- cancel_download tolerates a forgotten GID and always pops the
in-memory transfer so concurrent polls cannot re-register a
cancelled download
- Restore sweep deletes aria2 state records with no resolvable target
path instead of skipping them forever
- Clearing the download queue now also cancels in-memory tasks, removes
live aria2 transfers and drops persisted state for the cleared ids
(partial files on disk are preserved)
With trust_env=True, aiohttp auto-loads credentials from ~/.netrc (e.g. a
'machine civitai.red' or 'default' entry) and refuses to combine them with
the explicit Authorization: Bearer header, aborting every authenticated
CivitAI request with 'Cannot combine AUTHORIZATION header with AUTH
argument or credentials encoded in URL'.
Distinct files of the same model version queued before a backend restart
were silently collapsed by deduplicate(), which grouped rows by
(model_id, model_version_id) only. Extract the file id from file_params
via json_extract and add it to the dedup key; rows without file identity
keep the old per-version behavior (NULL matches NULL).
Consolidate the duplicate name-matching logic into ModelScanner:
find_matching_models is now the single core, using each scanner's own
file_extensions for suffix stripping. get_model_info_by_name gains
require_unique/base_model kwargs while legacy route behavior is kept
byte-identical. reconnect_lora passes the recipe base model as a guard
and distinguishes ambiguous, base-model-mismatched, and missing LoRAs
in its error messages.
CivitAI's PaidAccess cutover deprecated the availability=EarlyAccess and
earlyAccessEndsAt signals; gated versions now report availability=Public
with a paidAccess DTO that LoRA Manager previously ignored, so "Hide
Early Access Updates" missed paid/early-access models and downloads
failed with 401.
Parse and persist paidAccess from model-level, bulk, and by-hash
responses; treat timed paid gates as early access and permanent paid
versions as a distinct is_paid state; add a hide_paid_updates setting
with a "Paid" badge in the versions tab; warn before downloading gated
versions. Includes SQLite migration, i18n for all locales, and
backend/frontend tests.
Track recipe modal opens in a separate stats file (never touching recipe
JSON/EXIF), expose a fire-and-forget POST endpoint, and add an 'opened'
sort that hides never-opened recipes as a true recently-opened view.
Includes i18n for all locales and backend/frontend tests.
Downloads failed with "No suitable file found in metadata" for models whose
only file uses newer CivitAI file types (e.g. 'Enhancement LoRA' for
Anima/AIR image-editing LoRAs) because the primary-file allowlist only
covered legacy types.
- unify the weights-type allowlist as MODEL_WEIGHT_FILE_TYPES
(py/utils/constants.py) and apply it across download, recipe and
metadata-refresh lookups
- mirror CivitAI's getPrimaryFile() semantics: prefer weights-type primary,
fall back to weights files, then trust CivitAI's primary flag (excluding
non-downloadable artifacts like Config/Archive/Workflow)
- mirror the allowlist in the frontend via shared isModelWeightFile() helper
- add regression tests for the Enhancement LoRA primary-file download,
primary-flag fallback and weights-over-non-weights-primary preference
Version-tab updates reused the current version's folder, so updating a LoRA
to a version with a different base model (e.g. Illustrious -> Anima) ignored
the download path template and landed in the old version's directory.
When the target version's base model differs from the current local version
and a path template is configured, re-resolve the template under the same
model root. The backend keeps an explicitly provided root when
use_save_dir_as_root is set, so regular downloads still use the default root.
- Remove delete_undo_enabled setting (backend default, frontend state,
settings modal UI, 10 locales); staged deletes with 30s undo are now
the only delete path and stale settings keys are silently ignored
- Remove the 1500ms delete-button arm delay (armDeleteButton) from all
delete modals; misclicks are recoverable via the undo toast
- Delete modal always shows the recoverable warning
- Log the first staged file path in staging log lines for easier support
Promote aria2 stderr lines that indicate disk write failures (e.g. the
'cause: No space left on device' line following 'Write disk cache flush
failure') from DEBUG to INFO so the root cause is visible in default logs,
including Windows-specific phrases (file locked by another process, sharing
violation). The same line is rate-limited to one INFO report per 60s window
and the report map is pruned on insert so repeated failures cannot spam the
log or grow memory. All other stderr output stays at DEBUG.
Fix ~790 basedpyright errors across the test suite:
- Type stub subclasses of real production classes with super().__init__()
- Add missing generic type arguments and Dict[str, Any] annotations
- Add None guards before subscript/member access
- Adapt tests to production API changes (removed dead handlers,
PersistentModelCache.get_default, _i18n_filter_added location)
- Read AutoV3 directly from the downloaded file's own file_info hashes
(no SHA256 cross-matching against version_info.files, so the value is
captured even when the API omits SHA256)
- Extract normalize_autov3() validation helper shared with the
sha256-matching autov3_from_civitai_files path
- Fall back to the embedded safetensors header hash at download
completion; mark '' (checked-unavailable) so the startup backfill
query (autov3 IS NULL) never revisits the row
- Clear archive-level AutoV3 for zip-extracted models so per-file
header resolution applies to every extracted model