Newly created workflow entries used to be hardcoded to "Unknown". The
base model is now resolved at creation time only (Create LM-managed
workflow and first-time scans); write-back never re-resolves it.
Resolution order:
1. generation-time hint: on create with a prompt context, the primary
checkpoint from the metadata collector (primary-sampler tracing plus
lm_marker_role marks) outranks static analysis when it resolves in
the local checkpoint library
2. static votes: majority base model of the referenced checkpoints in
the local library, falling back to the referenced LoRAs
3. CivitAI URN fallback for one-off user actions (never scans)
Workflow analysis now extracts structured loader model references from
both API-format and UI-format graphs, including loaders whose widget
values are serialized as arrays.
`get_workflow_path` (like the recipes path) stores an absolute path, so a
POSIX literal such as "/workflows" comes back as "D:\workflows" on Windows
and the six workflow-path tests failed in the Windows job, which is meant
to become required once the suite is Windows-clean.
Compare against the same normalization instead of the raw literal.
`tensor_to_png_bytes` imported torch unconditionally inside its try block,
so every run without torch (standalone, CI) hit ModuleNotFoundError, logged
it as an image-processing error and returned None. That silently disabled
workflow previews on standalone installs and failed two backend tests:
`test_tensor_to_png_bytes_*` and
`test_resolve_workflow_preview_requires_matching_prompt`.
Detect torch tensors behind an optional import instead; numpy arrays keep
converting as before when torch is absent.
Shared chrome borrowed the model-library nouns on the Recipes and Workflows
pages:
- The header filter panel is titled per page (`recipes.filter.title`,
`workflows.filter.title`) instead of "Filter Models".
- Bulk guards and move failures use page-specific copy
(`recipes.bulk.noSelection`, `recipes.move.failed`,
`workflows.bulk.noSelection`) instead of "No models selected" / "Failed to
move model(s)", covering the bulk menu, the sidebar drag-and-drop path and
the recipe move client.
- Workflows got a bulk-action config, so model-only bulk entries (send to
workflow, auto-organize, re-import/rematch, copy syntax) stay hidden
instead of appearing unfiltered.
- The workflows page's global context menu no longer offers the model-only
example-image entries.
- MoveManager no longer assumes every API client exposes `modelType`: the
recipe client has none, which made the "use default path" preview throw
and stored the toggle under `use_default_path_undefined`.
Recipes card copy is translatable now too: the card tooltips (favorites,
share, send, delete, blur, NSFW levels) and the delete dialog go through
i18n instead of hard-coded English.
10 new keys (the move-only noSelection key folded into
`workflows.bulk.noSelection`), translated in all 9 locales; no
[TODO: Translate] placeholder remains.
The 19 keys added with the workflow card wording (itemLabel, copy-filename /
move / delete menu labels, delete + move + bulk-delete dialog copy, relink
toasts) are now translated in all 9 locales, following the §2 "Workflows
feature" terminology (工作流 / 工作流 / ワークフロー / 워크플로 / Workflow /
workflow) and each locale's existing colon and {message} conventions.
docs/i18n-translation-guidelines.md: new status note, and the Workflows
section now lists the card-action / dialog renderings and records that
workflows (like recipes) have no exclude copy.
The workflow card menu reused the model-library copy ("Exclude Model",
"Delete Model", "Copy Model Filename"), and the shared delete dialog
hard-coded "Model:" in its info box.
- Workflow-specific labels for copy-filename / move / delete, and for the
delete confirm dialog (title, message, info-box label, failure alert)
- Move dialog title, root label and hint now go through i18n instead of
hard-coded English; bulk delete dialog switches to workflow copy
- Failed move / nothing-selected toasts use workflow wording
- "Copy Workflow Filename" now copies the file name; it used to click the
card's copy icon, which copies the workflow document (duplicate of
"Copy Workflow")
- Fix "Link to CivitAI" posting to /api/lm/loras/relink-civitai: the
mixin's prefix map was keyed by singular type and missed the plural
page type used by WorkflowContextMenu
Also drops the "Exclude Workflow" action: workflows, like recipes, have
no exclusion concept. The file has no consumer outside LM, and the
excluded view is only reachable from the global context menu, so
excluding a workflow was a one-way hide. The entry is removed from the
card menu, the global menu and the excluded-view banner; the backend
route stays available.
Make check-model-exists and check-models-exist consult the workflow scanner while Workflow management is enabled, including the downloaded-history fallback, so Civitai cards can show workflow versions and download state.
Return workflows_disabled from model-versions-status instead of the generic unsupported reason, and add a structured workflows_no_paths reason to the download refusal when no workflow root is configured. Expose workflow_folder_paths as a one-item compatibility list alongside the newer single workflow_path setting so the browser extension can keep pre-flighting downloads.
ComfyUI exposes commands and menuCommands for the menu bar, but the
workflow tab context menu is built inside WorkflowTab.vue and has no
extension hook. Reuse the registered create/update actions and inject
the entries into the reka-ui portal, the same fallback the Graph menu
already uses.
The entries sit above the tab-close group. Clicking one activates the
right-clicked tab through app.extensionManager.workflow.openWorkflow
(the switch the native Save/Export entries use), so the action targets
that tab's graph instead of whichever tab was active. Update is enabled
from that tab's own lm_source marker and link-status check, and a menu
instance is only enhanced once.
Translate the six workflow keys left as [TODO: Translate] in all nine
locales by the single-folder change:
- settings.folderSettings.workflowFolder / .workflowFolderHelp /
.enableWorkflowsHelp
- workflows.noPaths.title / .description / .hint
The copy follows each locale's existing workflow terminology (工作流 /
ワークフロー / 워크플로 / Workflow / workflow), keeps Workflows, ComfyUI,
CivitAI Workflow resources and LoRA Manager untranslated, names the folder
in the singular, points at Settings → Library with the locale's existing
nav-path style, and never asks the user to edit settings.json.
Update the i18n status note accordingly; no [TODO: Translate] placeholder
remains in any locale.
- Replace the multi-root folder_paths.workflows workflow library with a
scalar workflow_path setting (Settings > Library); remove the
extra_folder_paths.workflows path and the standalone Model Paths editor.
- Migrate pre-release settings.json files that still keep
folder_paths.workflows / extra_folder_paths.workflows on startup, so an
upgrade keeps the configured folder instead of silently losing it.
- Add "Create LM-managed workflow" to the ComfyUI Graph menu and the File
menu/command palette: it writes the serialized graph into the configured
folder, stamps extra.lm_source and registers the entry in the scanner
cache without forcing a rescan.
- Attach the decode image of the client's last executed prompt as the
workflow preview. The prompt id is sent with the create request and the
backend only honors it while it is still the registry's current prompt,
so a tab never borrows another tab's output; otherwise the card keeps the
placeholder preview.
- Detect library entries deleted/renamed while a graph still carries the
marker: a link-status endpoint clears the stale extra.lm_source (on graph
load, menu open and before create/update), and the write-back route
returns 404 instead of recreating the document.
- Collapse the workflows.noPaths standalone/ComfyUI split into a single
description/hint; the folder is edited in Settings > Library in both
modes, so the manual settings.json guidance and the standalone
open-settings-folder fallback are gone.
- Add the preview tensor conversion as a shared utility (reused by the
recipe widget save path) and cover create, preview gating, link status,
migration and menu injection with tests.
LM widgets were painted with a fixed dark palette: translucent slate fills
plus light text. On the dark palettes those values happened to blend into the
node, but on the Light palette the translucency composited into grey slabs and
light text landed on light surfaces. The prompt textarea was the clearest
case: it used the fixed --color-charcoal-400 scale token, which never flips
with the theme, so it stayed charcoal inside a white node.
Add web/comfyui/lm_theme.css as the single colour layer. Every value aliases the
active ComfyUI palette (--comfy-input-bg, --component-node-*, --comfy-menu-bg,
...) and derives raised/hover/selected surfaces with color-mix, so built-in and
user-imported palettes are covered without per-theme rules. Contexts select the
token family the neighbouring built-in widget uses: lm-widget-root for canvas
DOM widgets, lm-vue-node for the Vue node renderer, lm-popover for menus
appended to <body>. Only status colours, shadows and scrims keep a light/dark
variant. Whole-node containers no longer paint a background, so the node
surface shows through and only inputs, cards and buttons are filled.
lm_styles.css, the canvas widget JS (autocomplete dropdown, tag chips, group
editor) and all Vue SFC styles now consume these tokens; tag category badges
derive their hue from oklch tokens so the tint and text contrast flip with the
theme. Empty states get an explicit 12px size: the canvas DOM overlay inherits
the 16px body size while Vue nodes render at text-xs, which made the
placeholders oversized and heavy in canvas mode.
A regression test rejects new colour literals outside the preview-image
overlays, and the DOM widget guide documents the token map and the
blend-don't-overlay rule. Rebuilt the committed vue-widgets bundle.
t('header.navigation.' ~ current_page) made the static i18n scanner
collect the bare prefix 'header.navigation.' as a key of its own, so
test_static_sources_only_use_existing_translations failed on main and
took both backend jobs down with it.
Resolve the key through a variable (t(page_title_key)), the same shape
the search placeholder already uses, keeping the literal prefix out of
the t() call. Rendered output is unchanged for every page.
Also add templates/** and locales/** to the backend workflow paths:
those are the inputs the i18n tests read, so a template- or locale-only
change could previously skip the very job that validates it.
The phone search field was forced to border-radius:999px. That looked
out of place next to the 6px used everywhere else, and at that radius the
1px border vanished along both arcs - the element's overflow:hidden
rounded clip cuts the outline, so the field read as a broken frame.
Use var(--border-radius-sm, 6px), i.e. exactly the wide-screen value.
Verified at 320/390/520/600 and focused, light and dark.
The <=600px header collapsed the search to a floating 32px circle, which
left ~250px of empty bar at 600px and hid the primary action behind a tap
even though the width supported a real field.
- The phone breakpoint moves to 520px: at 521px the icon nav and the
200px search floor still coexist, and 480-600px keep their tabs.
- Below that the search is an always-visible pill that fills the row and
the current page name sits next to the logo, so the bar still says
where you are now that the tabs live in the menu.
- Drops the click-to-expand overlay and its Escape handler: no fixed
positioning, no transparent placeholder, no breakpoint duplicated in JS.
- Active menu entry uses an accent tint; the hover token it used before
has no dark-mode value.
Verified 320-1920px: no clipping or overlap, page title appears at
381-520px and the menu carries all seven tabs.
Below 600px seven tabs plus a usable search field no longer fit. The bar
now reads logo | search | menu:
- The nav entries are repeated in the hamburger dropdown (shown there
only at <=600px) and the main nav is hidden.
- The search collapses to a round magnifier and expands into a fixed
overlay covering the header row on focus; Escape or a click elsewhere
returns it to the button.
- SettingsManager.setNavEntryVisible keeps both copies of the opt-in
entries (Other, Workflows) in sync when the switches are flipped.
- The dropdown scrolls when its 12 entries do not fit the viewport.
Verified in a headless browser from 320px to 1920px: no clipping and no
overlap, and the menu keeps its five actions above 600px.
The opt-in Other Models and Workflows pages took the nav to seven tabs
(~783px with icons), which left the header search field 73px wide at
1280px - just its two inline toggles. Rebuild the responsive ladder
around the measured widths:
- <=1500px: drop nav icons, keep the labels; icons return above 1500px
- <=1366px: compact gaps/padding and drop the Ctrl+F cue
- <=1150px: hide the app title
- <=1100px: collapse the five round controls into the hamburger
- <=780px: labels collapse to icons (was 700px; the row overflowed and
clipped the hamburger by 39px at 720px)
- <=520px: drop the search field's 200px floor (the row was clipped by
93px at 390px)
Also give the nav links the a11y metadata the icon-only states need:
aria-label (the label span is display:none below 780px), title, and
aria-current on the active tab; decorative icons are aria-hidden.
Fill the 41 [TODO: Translate] placeholders left by the workflow
management series across all 9 locales: the Workflows page
(workflows.* - title, context menu, the send/update/copy/restore
toasts, the restore confirmation modal, the disabled state and the
"no workflow folders" empty state), the settings toggle and folder
list (settings.folderSettings.enableWorkflows*, .workflowFolders*),
the Workflow Paths folder-key label, the two nav paths and
initialization.workflows.*. No [TODO: Translate] remains.
Workflow follows the term already fixed per locale: 工作流 in
zh-CN/zh-TW, ワークフロー in ja, 워크플로 in ko, and the Latin
loanword in fr/de/es/ru/he - so header.navigation.workflows and
workflows.title stay "Workflows" in those five. The copy reuses
existing renderings rather than inventing terms: workflows.disabled.*
mirrors other.disabled.* ("management is off", "Enable it to ...",
"Failed to enable ..."), workflows.noPaths.* mirrors other.noPaths.*
and its three open* buttons are byte-identical to their
other.noPaths.open* twins, the nav-path hints follow
other.disabled.hint ("Settings > Library") and
other.noPaths.descriptionStandalone ("Settings -> Model Paths"), and
the restoring line follows recipesPathMigrating.
"CivitAI Workflow resources" keeps Workflow in Latin even in the CJK
locales because it names CivitAI's resource type, and
folder_paths.workflows stays byte-identical everywhere. Failure toasts
keep {message} verbatim; CJK uses the full-width colon, the other
locales ASCII (fr keeps its space before it). The guidelines doc gains
a "Workflows feature" section in the term maps, plus a 2026-10 status
entry.
Verified with pytest tests/i18n (21 passed) and
scripts/sync_translation_keys.py --dry-run (all locales up to date).
- Replace the browser confirm() with a generic #confirmationModal
(template + ModalManager registration + showConfirmationModal() helper
in modalUtils.js, promise-based, danger-styled confirm button).
- Show the loadingManager overlay while the restore request is in flight;
.json restores re-download from CivitAI and the wait previously looked
like a dead click. The overlay hides in a finally block.
New card context menu entry 'Restore Downloaded Version' discards local
edits made via the write-back flow:
- Container artifacts (.zip/images) were never modified — restore deletes
the derived .workflow.json sidecar and re-extracts from the pristine
archive, then re-syncs analysis metadata. Purely local, works for
self-hosted workflows too.
- Bare .json artifacts are rewritten in place by write-backs, so restore
re-downloads the recorded CivitAI version file (API-key auth, temp file
+ workflow validation + SHA256 check before the atomic replace).
The menu entry is disabled for local-only .json workflows that have no
restore source. On success the card is refreshed in place with the
shared 'Updated' cue.
The write-back result carries the new document hash; the LM page now
refreshes the card in place via virtualScroller.updateSingleItem, which
re-renders it with the shared 'Updated' indicator.
test_init_git_repo_creates_valid_repo fetched ~140 MiB of history from
GitHub with no timeout: a stalled connection hung the whole suite until
an external kill. It could not even skip - _init_git_repo catches its own
exceptions and returns (False, ""), so the pytest.skip branch was dead and
a slow network surfaced as a hard FAILED instead.
Build a local upstream repository in tmp_path (the remote URL is now a
module constant) and assert what actually needs a real git binary: user
data surviving git clean -fd. Add tests for the timeout wiring and for
the (False, "") failure contract.
Bound git network commands with kill_after_timeout: git has no default
timeout for a handshake that never completes, and http.lowSpeed* does not
cover it either.
Importing an image whose metadata references a model by CivitAI id
(ComfyUI URN names, A1111 "Civitai resources", recipe checkpoint blocks)
always queried the metadata provider, even when the model was already in
the library. That made large imports network-bound and prone to vendor
rate limiting for no benefit.
Parsers now resolve the id against the scanner's version index first and
fill the entry from the local cache (model name, base model, hash,
preview, size). The provider is only consulted on a miss, matching the
existing hash-based local-first path.
- RecipeScanner: get_local_lora_by_version_id / get_local_checkpoint_by_version_id
- RecipeMetadataParser: populate_checkpoint_from_local + resolver helpers
- comfy / automatic / recipe_format parsers use the local lookup first
Add an Update from ComfyUI entry to the workflow card context menu,
enabled only when the workflow is the active graph in a connected
ComfyUI tab and carries unsaved edits.
ComfyUI side (workflow_writeback.js): the first graphChanged after a
load/write-back flips a dirty flag inside the extra.lm_source marker
(ChangeTracker only dispatches on real state changes); the active
graph's link state (file_path, sha256, dirty) is reported to
POST /api/lm/workflows/comfyui-state on load, tab switch, dirty flip
and write-back. A new lm_collect_workflow WS handler answers LM-page
write-back requests: it reuses the regular PUT content flow and posts
the outcome to /api/lm/workflows/collect-result.
Backend: an in-memory WorkflowLinkRegistry holds per-client link states
(pruned against live websocket sids) and pending collect futures;
POST /api/lm/workflows/update-from-comfy targets the tab holding the
requested file (dirty holders win) and awaits the collect result,
mapping no_changes/cancelled to 200, timeout to 504.
Open in ComfyUI now stamps an extra.lm_source provenance marker
(file_path + document sha256) onto the loaded graph, which rides the
workflow draft serialization across tabs and reloads. A new
LoraManager.UpdateManagedWorkflow command (palette, menubar File group,
and an item injected into the workflow actions dropdown; disabled unless
the active graph carries the marker) serializes the current graph and
PUTs it to /api/lm/workflows/content.
The write targets the workflow document: bare .json artifacts are
rewritten in place, while zip/image containers keep their original bytes
(the SHA256 is their CivitAI identity) and receive the edited graph in
the derived .workflow.json sidecar. Optimistic concurrency via
base_sha256 rejects writes with 409 when the library copy changed since
open (force retries after user confirmation). Post-write, analysis
fields and (for .json artifacts) hash/size/modified are refreshed in the
metadata sidecar and caches.
Workflows load a whole graph document rather than inserting into the
current graph, so all workflow entry points (card icon, context menu,
modal button) now consistently read Open in ComfyUI; model types that
insert into the current graph keep Send to ComfyUI.
The card send/copy icons and the model modal's Send to ComfyUI button
only had lora/checkpoint/embedding branches; workflows fell through to
a silent no-op (modal) or lora-ish behaviour with wrong tooltips (card).
- shared helpers sendWorkflowToComfy / copyWorkflowToClipboard in
utils/workflowActions.js, reused by the context menu, card icons and
the model modal
- card send icon opens the workflow in the running ComfyUI graph; the
copy icon copies the workflow document JSON for paste-to-load
- correct tooltips (Open in ComfyUI / Copy workflow to clipboard)
- send entry points hidden in standalone mode (no graph to push into):
card icon and modal button, matching the context menu
Anima 2.9B/3.8B insert blocks between the original 28, so old LoRA keys land on the wrong layers and ComfyUI emits no missing-key warning. Add a model-family adapter that rewrites block indices in memory before the generic LoRA loader runs.
- 28->40, 40->52 and composed 28->52 mappings, with anchored kohya/generic key patterns
- Anima detection from image_model/llm_adapter and block count from blocks/num_blocks
- skip text encoder, llm_adapter and semantic connector keys; collision and newer-LoRA errors
- ComfyUI setting (default on) synced to the LM settings API; no base-model-specific node toggle
- docs, unit and loader integration tests
Reuses the manifest data from PR #1104; supersedes its node-level toggle design.
- GET /api/lm/workflows/content returns the extracted workflow document
(json direct, zip/png/webp via the derived sidecar); no ComfyUI needed,
so it also serves standalone mode where send-to-comfy is unavailable
- workflow cards gain a Copy Workflow context-menu action that puts the
document JSON on the clipboard for paste-into-ComfyUI loading
- the Open in ComfyUI item is hidden in standalone mode, where pushing
to a graph is impossible by definition
The fallback produced WORK (first four letters), which reads like a verb
and is wider than the other badges; WF matches community shorthand and
the two-letter style of CN/CV/TE. Also give the workflow sub-type its
full display name so tooltips show Workflow instead of the raw key.
The in-place reconcile from #1078 no-oped whenever the downloaded model
had no card in the current view, which silently swallowed brand-new
downloads (e.g. pasting a CivitAI URL for a model never downloaded
before): the backend added the entry but the listing only caught up on
manual refresh.
Reconcile now takes wasLocallyOwned (derived from the existsLocally
flags of the version list the modal fetched before the download):
- owned model outside the current view: keep the #1078 no-op
- brand-new or unknown ownership with no matching card: fall back to
the legacy reload so the new card appears
Library entries are the original artifact bytes (SHA256 = CivitAI identity);
container artifacts (.zip, .png, .webp) get their extracted workflow document
materialized as a *.workflow.json sidecar that follows rename/move/delete.
- workflow_extraction: json direct-read, zip candidate pick, png/webp metadata
- scanner whitelist extended; non-workflow files rejected; adjust_metadata
fills analysis fields for downloaded entries; image artifacts self-preview
- download_manager: workflow branch end-to-end, no zip unpacking, file
selection prefers primary then json/zip/image
- lifecycle: delete/rename/pending-delete carry the derived document
- send-to-comfy reads the derived document for container artifacts
- fix: force_refresh with no cache falls back to full initialization
(first scan before deferred background init no longer crashes)
Add a Workflows page that manages ComfyUI workflow JSON files as a
first-class library alongside loras/checkpoints/embeddings:
- opt-in via the new 'Workflow Management' settings toggle (default off)
- dedicated workflow folder roots with sidecar/preview support, separate
from ComfyUI's own workflow storage; 'Send to ComfyUI' loads a workflow
straight into the graph
- workflow scanner parses the graph document (UI/API format sniffing,
node classes, model/civitai refs) and persists the analysis in the
SQLite model cache
- settings UI: workflow folder paths editor in the Library tab and the
standalone Model Paths page
Fixes from sandbox E2E verification:
- include the workflow scanner in startup initialization so the page no
longer hangs on 'initializing' when enabled without folders
- add the missing workflows entry to the frontend page-state map (page
init crashed and the sidebar fell back to the loras tree without it)
- project workflow analysis fields through the scanner cache entry and
persist them in the model cache (new columns with auto-migration)
- keep symlinked paths intact in the directory picker (abspath instead
of resolve), so stored library paths match what the user sees on disk
Also rebuilds the vue-widgets bundle to sync it with web/comfyui/utils.js.
Civitai's post.addImage resolves the image's Tools field from a lowercase
`engine` detail key (getToolByAlias/getToolByName), and a Tool named
"ComfyUI" exists, so emitting `engine: ComfyUI` in the A1111 parameters
line makes every saved image auto-tag as ComfyUI on upload.
The key is written before `additional_data` so a user-supplied `engine:`
still wins (duplicate detail keys are last-one-wins). Techniques are
deliberately not written: `process`/`type`/`workflow` cannot be derived
reliably from the graph, and a wrong value is user-visible on Civitai.
Fill the 36 [TODO: Translate] placeholders left by the first-run series
across all 9 locales: firstRun.* (30, the welcome page that gates the
initial scan), initialization.error.*, banners.bulkFetchPrompt.*,
modals.metadataFetchSummary.rateLimitedNotice,
toast.api.metadataFetchRateLimited, the two
modals.download.errors.linkedFile* messages and
toast.duplicates.deletePartialFailed/.cacheUpdateFailed. The same pass
re-translates the three keys whose English text changed without being
re-queued: settings.folderSettings.activeLibrary/.activeLibraryHelp
(multi-library deprecation) and toast.contextMenu.linkHfSuccess (model
sources beyond Hugging Face). No [TODO: Translate] remains.
Most of the copy reuses existing renderings rather than inventing terms:
firstRun.title mirrors onboarding.languageSelection.title, libraries.*
the header.navigation.* model-type names, libraryStats the rootModels
count shape, "~{minutes} min" the eta.minutes abbreviation, and
firstRun.sidecar.* the sidecarStorage.modeOptions.* choice plus
"(recommended)". The rate-limit copy quotes each locale's
loras.controls.fetch.title verbatim (zh-TW therefore keeps its own
"... metadata" label instead of a second rendering), cacheUpdateFailed
quotes refresh.full, and the sidecar descriptions use the §5 metadata
noun with the §2 sidecar rendering.
Guidelines doc: new §2 "First-run welcome page and fetch-status copy"
terminology subsection, a 2026-10 status entry, and the leaf-key count
updated to 2308.
Downloads, imports, reconciles, metadata syncs and bulk updates all add
freshly allocated objects to caches that were frozen at startup, so
ComfyUI's post-prompt gc.collect() grows again within a running session
(issue #1161, follow-up to #1147).
- add_model_to_cache now stores the slim cache representation: license
flags are derived from the full source payload first, then the civitai
payload is slimmed to the scan-path shape. Fresh downloads previously
also ended up with default license flags.
- New schedule_refreeze(): debounced re-freeze with an idle guard that
reschedules while the ComfyUI prompt queue is busy, so the freeze never
lands mid-prompt. Wired into downloads/imports, reconcile, single-model
updates, deferred bulk updates and metadata sync.
- freeze_long_lived_objects() logs collect duration and the tracked-object
count at DEBUG level to tell 'more objects' apart from 'slower objects'.
Stop registering zero-byte models, repair the empty-string placeholder on scan
and fetch, refuse it in CivArchive/the archive DB/ModelScope linking, and
rebuild the required local fields when relinking without a sidecar.
calculate_sha256 now reports "no hash" for an empty read. The checkpoint and
other-model lazy paths used to store that (previously the empty-string digest)
as a completed hash and index it, so an empty or truncated file would keep a
poisonous identity.
Both paths now leave hash_status="pending" for a later retry and skip the hash
index entirely. The repair flow also tolerates a failing cache update instead
of aborting a metadata fetch whose SHA256 was already repaired.
Relinking is the documented remedy after deleting a broken .metadata.json, so
the route starts from `{}`. update_model_metadata then persisted only the
remote fields (civitai/model_name/base_model); file_name, file_path, size,
modified and sha256 were missing, so the sidecar could not be parsed on the
next startup and the scanner skipped the model for good (issue #1162).
relink_metadata now rebuilds the payload from
MetadataManager.load_metadata_payload (local facts plus any existing sidecar),
layers the caller's values on top, and fills what a relink cannot get from the
provider: sha256 re-read from the file, a display name, and the preview path.
The zero-byte digest identifies no file, but CivArchive's by-hash index lists
211 unrelated foreign files for it (198 on huggingface.co, plus a civision pair
pointing at ppxjmqq/pam-krea2). Resolving it linked seven unrelated LoRAs to
that ModelScope repository (issue #1162); the archive DB could do the same.
- CivArchive: get_model_by_hash returns not-found without a request, the
file-entry fallback returns nothing, and no foreign-source ref is recorded
- SQLite archive provider: guarded before a connection is opened
- MetadataSyncService._try_link_foreign_source: refuses the placeholder
- ModelScope: a published 0-byte upload is dropped from the digest map and the
strict hash gate rejects the placeholder itself
A stored hash of zero bytes identifies no file. Looking it up either hits a
polluted index entry (CivArchive resolved seven unrelated LoRAs to one
ModelScope repository, issue #1162) or reports "not found", which additionally
marks the model as deleted on CivitAI.
- the scan pass re-hashes a model whose stored hash is the placeholder as soon
as the file has content, and writes the sidecar back, so an existing library
heals on its next full scan
- fetch_and_update_model re-hashes before any provider runs, and refuses the
lookup outright for a zero-byte or missing file instead of querying remote
indexes and recording a bogus deletion
A file whose directory entry reports zero bytes hashes to the SHA256 of an
empty byte string. That value is shared by every unread or still-being-written
file: the library collapses them into one bogus duplicate group and every
member resolves to the same polluted remote entry (#1162).
- _calculate_sha256_sync returns "" when the read yields no bytes, so no
caller can persist the placeholder as a real hash
- create_default_metadata refuses to create a sidecar for an empty file
- ModelScanner._process_model_file skips zero-byte files before loading or
creating metadata, leaving them unmanaged until they have content
- a file whose metadata cannot be created no longer aborts the scan via assert
A Metadata Overwrite (LoraManager) node can now be wired directly into a
Save Image (LoraManager) node's new metadata input. Wired overwrites apply
only to that Save node and are excluded from the global merge, so each
stage of a multi-stage/multi-model workflow can record its own prompt,
model, seed, and sampler settings.
Unwired overwrite nodes keep the legacy behavior of applying to every
image saved in the run.
The merge logic (truthy check, clip_skip sentinel handling, and the
model->checkpoint bridge) is factored into a shared
MetadataProcessor.apply_overwrite_params helper used by both paths.