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
Added the configurable image subfolder strategy to the Settings panel. Updated documentation for the YAML config file. Also make changing the strategy act in realtime to future saves.
Admins can now set image_subfolder_strategy from the UI. The setting is persisted through the existing runtime config endpoint to invokeai.yaml. The API schema, frontend generated types, English locale strings, tests, and YAML config docs were updated.
Medium: invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsImageSubfolderStrategySelect.tsx:18-23 hardcodes the four strategy options (flat, date, type, hash) rather than deriving them from the backend IMAGE_SUBFOLDER_STRATEGY literal exposed via schema.ts. The satisfies ImageSubfolderStrategyOption[] annotation only enforces that each declared value is assignable to ImageSubfolderStrategy (defined at line 11 as NonNullable<S['InvokeAIAppConfig']['image_subfolder_strategy']>), not that the union is exhaustively covered. The new test invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsImageSubfolderStrategySelect.test.ts:11 hardcodes toEqual(['flat', 'date', 'type', 'hash']), so it exercises the constant against itself and cannot detect drift. If a backend developer adds a fifth literal in IMAGE_SUBFOLDER_STRATEGY at invokeai/app/services/config/config_default.py:33 and regenerates the OpenAPI schema, the dropdown silently omits the new value and admins cannot select it from the UI even though the PATCH endpoint accepts it.
To expose this issue, add a vitest that asserts every literal of the S['UpdateAppGenerationSettingsRequest']['image_subfolder_strategy'] union appears in imageSubfolderStrategyOptions (e.g., via an exhaustiveness pattern using Exclude<Strategy, OptionValue> resolving to never).
Low: invokeai/app/api/routers/app_info.py:112-116 declares image_subfolder_strategy: IMAGE_SUBFOLDER_STRATEGY = Field(default=None, ...). The annotation IMAGE_SUBFOLDER_STRATEGY = Literal["flat", "date", "type", "hash"] does not include None, so the declared default is not a valid value for the type. The json_schema_extra=lambda schema: schema.pop("default", None) hides this in the generated OpenAPI (and schema.ts shows the field as a clean four-value enum), and model_dump(exclude_unset=True) plus runtime literal validation make the PATCH endpoint behave correctly today (verified by test_update_runtime_config_rejects_null_image_subfolder_strategy). However the pattern is fragile: a future Pydantic release that validates default values, or a developer copy-pasting the same idiom for a non-Literal-only field, can either start raising at import time or accidentally widen the request type to allow null. A safer construction is image_subfolder_strategy: IMAGE_SUBFOLDER_STRATEGY | None = Field(default=None, ...) paired with an explicit model_validator that rejects None, or simply omitting the strategy field from model_fields_set via the existing exclude_unset flow without lying about the type.
To expose this issue, add a backend test that loads app.openapi() and asserts the schema for UpdateAppGenerationSettingsRequest.image_subfolder_strategy contains exactly the four enum values, no default, and no null or None permitted, so any regression in the JSON Schema lambda is caught.
Low: invokeai/app/api/routers/app_info.py:138-153 performs a read-modify-write of invokeai.yaml (load_and_migrate_config -> update_config -> write_file) without acquiring any lock. The external-provider write path at invokeai/app/api/routers/app_info.py:248-286 holds _EXTERNAL_PROVIDER_CONFIG_LOCK while it does the equivalent. A concurrent PATCH /runtime_config and POST /external_providers/config/{provider_id} (or two concurrent PATCHes from an admin clicking quickly) can interleave so that the later writer clobbers the earlier writer's persisted change. The race is pre-existing for max_queue_history, but this branch enlarges the writable surface and increases the chance of admin-initiated concurrent edits in the same Settings panel.
To expose this issue, add a backend test that injects a small delay between the load and write inside update_runtime_config (via monkeypatch of load_and_migrate_config), fires a concurrent POST /external_providers/config/openai from a thread, and asserts both updates are still on disk afterwards.
Low: tests/app/routers/test_app_info.py:181-194 only asserts the response body and the on-disk yaml. It does not assert that the runtime config the production image-creation path reads (self.__invoker.services.configuration.image_subfolder_strategy at invokeai/app/services/images/images_default.py:60) reflects the new value. In production the InvocationServices.configuration instance is the same get_config() singleton injected at invokeai/app/api/dependencies.py:123,198, so the wiring works, but the test fixture in tests/conftest.py:35 constructs a separate InvokeAIAppConfig(...) and the lru-cached singleton is a different object, so the test as written cannot prove the "newly-created images use the new strategy" guarantee that the docs added in docs/src/content/docs/configuration/invokeai-yaml.mdx promise.
To expose this issue, add a backend test that PATCHes image_subfolder_strategy and then asserts the strategy resolved by create_subfolder_strategy(get_config().image_subfolder_strategy) matches what was just written, or alternatively invokes ImageService.create and verifies the persisted image_subfolder reflects the new strategy.
Low: invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsImageSubfolderStrategySelect.tsx:36 silently coerces runtimeConfig?.config.image_subfolder_strategy ?? 'flat'. If the runtime ever surfaces a value the frontend does not know about (for example a backend literal added without regenerating schema.ts, or a future migration), the dropdown will display Flat while the actual stored value is something else, and the user has no indication that the displayed selection is a fallback. There is no toast, no warning, and the Comboboxvalue prop becomes undefined (because options.find(...) === undefined) so the control silently shows nothing while internal state believes the strategy is the unknown value.
To expose this issue, add a vitest that simulates runtimeConfig.config.image_subfolder_strategy === 'unknown' and asserts the component either renders an explicit unknown-value indicator or the option list is augmented with the unknown value rather than collapsing to the default.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Added the configurable image subfolder strategy to the Settings panel. Updated documentation for the YAML config file. Also make changing the strategy act in realtime to future saves.
Admins can now set
image_subfolder_strategyfrom the UI. The setting is persisted through the existing runtime config endpoint toinvokeai.yaml. The API schema, frontend generated types, English locale strings, tests, and YAML config docs were updated.Related Issues / Discussions
Related to PR #8969.
QA Instructions
Verify manually as an admin or when in single-user mode:
invokeai.yamlis updated.Confirm non-admin multiuser accounts cannot update the setting.
Merge Plan
Checklist
What's Newcopy (if doing a release after this PR)