Build for https://v7.invoke.ai and force-push the site to invoke-ai/docs-v7's gh-pages branch with a deploy key; invoke.ai now deploys from v6-bugfixes.
Pushes to main always rebuild so a non-docs push cannot cancel an earlier docs deploy.
Move the route middleware's sidebar logic into src/lib/sidebar.mjs and cover prefix ordering, alsoListedIn cross-listing, draft/hidden exclusion and prev/next with node --test.
Run the tests in the docs CI job.
Run UI quality checks on webv2 and generate contracts with an independent locked API package.
Retain legacy type imports and bundle packaging; update generation commands, tests and ownership docs.
Select webv2 for launches, frontend tooling, wheels, containers and dependency installation while preserving both source paths.
Retain explicit legacy fallback, shared API artifact ownership and deployment-scoped browser worker cleanup.
Raise requires-python to >=3.12, <3.13 and regenerate uv.lock, which drops
the 3.11 resolution forks, the cp311 wheels, tomli and backports-tarfile.
No package version or source changes.
CI runs 3.12 only: the test matrix loses its 3.11 dimension, and deploy-docs,
openapi-checks and typegen-checks move off the 3.11 interpreter they pinned.
flake.nix moves python310 -> python312, which was already below the old floor.
The rocm leg of `build container image` has timed out four times
(2026-07-28, 2026-09-17 x2, 2026-09-22). It is not a regression: run
35724289637 (42m37s, timed out) and run 35724304187 (16m25s, green) were
pushed ten seconds apart on near-identical trees. Every phase of the slow
run was ~2.4x slower than the fast one - apt 59s->125s, `uv sync`
296s->529s, sbom 44s->178s, layer export 386s->927s. It is runner
quality, and the job had no headroom to absorb it.
It has no headroom because the rocm image is big: its venv layer is
7.2GB compressed (cuda 4.5GB, cpu 0.8GB), BuildKit gzips a single layer
on one thread, and there is no build cache, so ~6.5 min of the budget is
one gzip stream even on a healthy runner. A ~17 min median against a 40
min cap dies at a 2.3x slowdown.
So: raise the cap to 90 min, and drop the export from BuildKit's default
gzip -6 (~19MB/s here) to gzip -1, which is 2-3x faster for ~15% larger
layers. The cap is what actually fixes this - gzip -1 attacks the single
largest slice, but the build phases ahead of the export are untouched by
it.
Setting a compression level means using `outputs` instead of `push`, so
the condition `push` carried moves into the expression. It does not move
verbatim: `github.event.inputs.*` is always a string and every non-empty
string is truthy, so the bare reference the old expression ended in
evaluated as TRUE for an unchecked box. `push:` absorbed that because the
action ran it through core.getBooleanInput, which maps "false" to false.
`outputs:` is read with getInputList and passes any non-empty string
straight to --output, so the reference needs an explicit `== 'true'` or a
manual run from a non-main branch with the box unchecked would publish
that branch to ghcr. All seven trigger combinations were checked against
the old push decision and match.
Also fix the paths filter, which listed `workflows/build-container.yml`
without the `.github/` prefix - a path that does not exist, so edits to
this workflow never triggered it. Without this the change would not run
until some unrelated push happened to touch `invokeai/**` or the
Dockerfile.
`force-compression` stays off, so base-image layers keep their existing
compression and only newly built layers are affected.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Keep main's LTX-2 video settings, policies, and locale keys with the branch's trimmed comments and CFG floor of 1.
Re-record the build baseline: main's LTX-2 growth plus the branch's install queue growth exceed the editor byte allowance together.
Windows ran one worker per vCPU, leaving no headroom for the helper
processes some tests spawn, and the deadline-sensitive tests missed their
budgets non-deterministically. The worker count and the job timeout are now
per-platform; linux and macos are unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Adds the single-stage text-to-video and image-to-video path: an inference
package, six invocations, and LTX-2 support in the webv2 Video panel. One
transformer produces both modalities at every step, so the soundtrack is
generated with the picture rather than dubbed onto it.
Update five frontend dependencies and run the full release gate in CI.
Accept recorded byte growth while preserving request and structural limits.
Stabilize accessibility journeys and retain failure screenshots.
- Anima VAE nodes accept both Wan 2.1 VAE layouts; loaders derive VAE bases and validation from VaeFacet
- Z-Image Base scheduler set declared per variant; Ideogram 4 bounds pinned to the node
- Control layers and the Generate retry handle a missing capability table without false warnings or lost focus
- Ceilings are derived at check time: the lower of max(1%, 4 KB) over the
base-branch reference and max(10%, 32 KB) over the committed baseline;
request counts take the lower count. `limits` blocks leave both baselines.
- Every push to main caches versioned build/browser reference files by SHA;
PRs into main restore their base's entry (or the latest main) and fall
back to the committed baseline, logged and annotated, when none applies.
- The re-record remedy is appended only for byte/request failures; source
owners stay pinned exactly. ARCHITECTURE.md documents the derivation.
The declaration this series exists to protect was itself checked for four of
sixteen bases: swapping FLUX_16 for SD3_16 in the FLUX declaration passed every
committed test and corrupted every FLUX preview at generation time. A 16-row
identity table now pins each base against the pre-refactor dispatch, and each of
the nine spaces has a hand-derived preview pixel. The smoothing-kernel test was
seeding after it drew its sample, so it had never been deterministic.
The guidance control's bounds now come from the architecture too, the way the
dimension grid already does. FLUX.2 accepts at most 20 and four architectures
reject below 1, while the input allowed 0 to 100 for every model -- so a value
the UI offered was rejected at enqueue. Pinned against the ge/le of the field
each denoise node actually validates.
Also: Ideal Size is no longer titled for SD, since it answers for every
architecture; the contributor docs describe the layout this branch created,
including the defs module whose absence refuses boot; and the docs-export test
no longer depends on pytest's working directory.
A variant row reused its architecture's primary latent space, so Wan TI2V-5B
was served grid 32 next to compression 8 while it denoises in the 48-channel
Wan2.2 space at 16x. `LatentSpaceFacet` now names the space a variant uses --
checked to be one the facet already declares, so the two resolvers cannot
disagree -- and a variant that differs only there gets a row of its own.
The Python change filter skipped the two cross-stack generation contracts: a
frontend-only change could regenerate them, pass vitest, and never run the
backend half that validates them against the registries.
- Each PR workflow now has a concurrency group keyed by PR number, so a new push cancels the stale run; main pushes and merge groups fall back to the run id.
- Groups use a literal workflow name: `github.workflow` is the caller's name inside a reusable workflow, and release.yml calls several at once.
- The bf16 forward faulted with an illegal instruction (0xC000001D) in the Windows CPU torch build's GEMM on some runner CPUs, crashing the xdist worker in 8 of the last 60 runs.
- CPU inference is float32 in the product; the CUDA branch keeps bf16 and the assertion still catches a dropped scale.
- The test only asserts the config field description now; the docs workflow's check-docs-data gate covers the generated settings, and UI/guide copy belong to their packages.
- python-tests no longer re-includes the legacy locale or docs files, so translation and docs edits skip pytest.
- Exclude invokeai/frontend/webv2 from the pytest, ruff, openapi, and typegen change filters; only legacy web was excluded before.
- Re-include the non-Python files pytest reads (webv2 workflow fixtures, legacy en.json, two docs files) and .py files under both frontend trees.
- python-checks now filters on **/*.py, matching what ruff lints from the root.
Torch sizes its thread pools to the whole machine, so four workers on a four-vCPU
runner ask for sixteen compute threads. The Windows CPU build crashed a worker in
the fp8 tests under that contention, twice in two runs.
Nothing in this repository runs `-m slow`, so marking a test removes it from CI
rather than moving it to a slower tier. Three guards go back to running:
- Loop-scheduler linearity now measures CPU time, which a worker losing its core
does not inflate, so the ratio no longer has to be a benchmark. Its absolute
sibling budget stays slow.
- bcrypt's work factor is read from the hash instead of timed; every other
assertion in that file holds at cost factor 4.
- test_flux2_scaled_fp8_keys.py drops to 0.90GB from 1.96GB by expanding one
element to the exact captured extents -- capping breaks it, because the fused
qkv is split into thirds.
Each xdist worker now reports its peak RSS and a dead worker fails the run at
once, so the next memory blow-up is a number in the log rather than a runner
that vanishes mid-run. The extent helper moves to the state_dicts utils the four
copies of it should have used.
CI spent 21-26 min per matrix job entirely in pytest. Running it under xdist
(`-n logical`, one worker per vCPU) takes the full suite from 1083s to 271s at
four workers locally. `--dist loadfile` lives in addopts so it also holds when
`-n` is passed by hand.
Fixes test_client_state_multiuser.py, which inherited its JWT secret from a file
that happened to run earlier and errored on any worker that scheduled it first.
Wall-clock and throughput benchmarks move behind the existing `slow` marker;
their behavioral assertions stay in the default run.
Brings in upstream through ef832d1aa5 (v6.14.1 prep, diffusers 0.40, transactional image deletion, model cache shutdown eviction, streaming video upload, custom fonts).
- Video upload: upstream's streaming multipart parser now carries the fork's video/audio classification and ffmpeg ingest.
- Model cache: upstream's first-use claims replace the fork's grace holder; paced VRAM loading kept and releases the claim after the stream settles.
- uv.lock regenerated from the merged pyproject; openapi.json and schema.ts regenerate unchanged.
- The accessibility journeys stay a local check:release step until they are re-authored against the current UI
- Journey locators updated for the Gallery center view and the Create widget rail
Preserve settings changes from both branches, regenerate queue receipt API contracts and measured performance baselines, and format the enqueue regression. Allow the complete browser architecture gate to finish without reducing its coverage or sampling.
* chore(deps): remove dependency on Jupyter Notebook
* chore: update uv version to 0.11.28
(the version currently used by launcher)
* chore(deps): use the global form of exclude-dependencies, and finish the uv bump
Two fixes to how the Jupyter exclusion is declared and checked.
1. `exclude-dependencies = ["notebook"]` instead of the per-package table form.
The table form is only understood by uv >= 0.11.25; on uv 0.10.0 - 0.11.24 it
is a hard `Failed to parse: pyproject.toml / invalid type: map, expected a
string`, which kills every uv command (sync, run, lock) for anyone whose uv is
a few weeks old -- including contributors following the `uv sync --frozen` in
docs/development/Setup/dev-environment.mdx. The global form has been accepted
since uv 0.9.8, older uv merely warns and ignores it, and it resolves to
exactly the same set: the regenerated lockfile differs only in the `[manifest]
excludes` line, and the resolved package set is identical (63 packages
removed, no version changes). No `required-version` is added on purpose -- it
would hard-fail the uv 0.6.12 bundled in launcher <= 1.8.1, which today
installs fine and, because the launcher syncs `--frozen`, already gets the
trimmed environment straight from the lockfile.
2. typegen-checks.yml was the one workflow left on uv 0.6.10. That uv does not
know `exclude-dependencies` at all, and on an unknown key it discards the
*entire* `[tool.uv]` settings table -- `index-strategy = "unsafe-best-match"`
and all four `[[tool.uv.index]]` definitions included -- with only a warning.
Bumped to 0.11.28 like the other five.
Verified with real uv binaries: `uv lock --locked` passes on 0.9.8, 0.10.3,
0.11.24, 0.11.25 and 0.11.28 (0.10.3 and 0.11.24 could not even parse the file
before); `uv export --frozen` under 0.6.10 yields 131 packages with zero
Jupyter-chain entries; scripts/check_aarch64_lock.py, check_pins.py and
check_classifiers.py all pass.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01JdJB9VXy5o6o7tfY1JSBCM
---------
Co-authored-by: Lincoln Stein <[email protected]>
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
* ci: attach provenance and SBOM attestations to the published container
* ci: restore trailing newline at end of file
---------
Co-authored-by: Lincoln Stein <[email protected]>
* ci: add pins.json consistency check, bump ROCm index to 7.1
pins.json is consumed only by the Invoke Launcher (fetched at the release
tag) to pick the torch wheel index for legacy installs. Nothing in-repo
references it, so it silently drifted from pyproject.toml: its Linux ROCm
index stayed at rocm6.3 after the torch-rocm index moved to rocm7.1,
causing the launcher to install ROCm 6.3 wheels (#9328, launcher#131).
- Add scripts/check_pins.py, which fails if any torchIndexUrl entry in
pins.json differs from the matching [[tool.uv.index]] URL in
pyproject.toml.
- Run it from the uv-lock-checks workflow, triggered by changes to any of
pyproject.toml, uv.lock, pins.json, or the check script.
- Bump pins.json rocm index to rocm7.1 (matches #9337) so the check
passes.
- Bump the workflow's uv from 0.6.10 to 0.11.28: uv.lock is already lock
format revision 3, which 0.6.x cannot parse.
Co-Authored-By: Claude Fable 5 <[email protected]>
* fix: cap torch below 2.12 on linux/windows
torch 2.12.x+rocm7.1 breaks generation (#9328; 2.11 works, and 2.10 is
what the rocm extra pins). The cap only matters for legacy (pre-6.14)
launcher installs, which resolve the base range live against the pytorch
wheel indexes instead of using uv.lock — with the range open to <3.0,
AMD users could land on the broken 2.12.x. Lockfile-based installs are
unaffected: uv.lock stays at 2.7.1 / 2.10.0+rocm7.1 (no locked versions
changed, only the recorded specifier).
Remove the cap once the 2.12 ROCm incompatibility is diagnosed.
Co-Authored-By: Claude Fable 5 <[email protected]>
* ci(check_pins): validate an explicit required platform/backend matrix
Addresses JPPhoto's review. The checker only iterated the entries that
already existed in pins.json, so deleting torchIndexUrl.linux.rocm still
exited 0 — recreating the exact silent-fallback failure the guardrail was
meant to prevent (the launcher only passes --index when the entry exists,
and its schema makes every backend key optional).
- Validate pins.json against REQUIRED_BACKENDS, an explicit matrix of the
platform/backend combinations the launcher can select and Invoke supports.
Missing entries, missing platform sections, unknown platforms and
unsupported backends are now all errors, alongside the existing URL check.
- Split the logic into check_pins(), with main() taking a repo root, so the
exit code is directly testable.
- Add tests/test_check_pins.py: removes each required entry in turn and
asserts a nonzero exit, plus stale-URL, unknown-platform and
unsupported-backend cases.
- pins.json: add the win32.cpu entry the matrix exposes as missing. Without
it, a Windows "no dedicated GPU" legacy install gets no --index and pulls
the CUDA-bundled PyPI torch wheel instead of the +cpu build. Linux already
had this entry.
- python-tests: trigger on scripts/** and pins.json so these tests actually
run when the checker or the pins change.
- Revert the uv 0.6.10 -> 0.11.28 bump in uv-lock-checks: 0.6.10 parses the
revision-3 lock fine (verified), so the stated justification was wrong and
the bump left this workflow out of step with python-tests.
* fix(deps): drop the blanket torch <2.12 cap; scope the ROCm caveat to docs
Addresses JPPhoto's second review. The cap was applied to the base
`sys_platform != 'darwin'` requirement, so it landed in published package
metadata for every Linux and Windows install — CUDA, CPU-only and Linux
ARM64 included — despite #9328 only ever reporting a ROCm 7.1 failure.
My original justification ("only affects legacy pre-6.14 launcher installs")
was wrong in both directions:
- It never protected legacy launcher installs. Those install an
already-released version, resolving against that release's frozen PyPI
metadata, not this pyproject.
- It never protected bootstrap (6.14+) launcher installs either. Those pass
`invokeai[cpu|cuda|rocm]`, and every one of those extras pins torch
exactly, so the base range is never consulted.
- What it did affect is manual installs, which per docs/start-here/manual
use `--torch-backend=<x>` with no extra and therefore resolve the base
range directly — on every platform, not just ROCm.
There is no environment marker for "resolving against the rocm7.1 index", so
the cap cannot be scoped in metadata. Constrain it where it can actually be
targeted instead: the `rocm` extra already pins torch==2.10.0+rocm7.1, and
the manual install docs now carry a ROCm-specific caution with the
`"torch<2.12"` workaround for users following the `--torch-backend=rocm7.1`
path.
pyproject.toml returns to `torch>=2.7.0,<3.0`, so uv.lock is untouched by
this PR again.
* docs: point the ROCm torch-2.12 caveat at an open tracking issue
#9328 was closed as completed once pins.json was corrected to the rocm7.1
index; the separate torch 2.12.x generation failure reported in that thread
had no open tracking issue, so the caveat had no removal condition. Filed
#9410 for it and link there instead.
Also narrow the cross-platform claim: we have no torch 2.12 smoke tests on
CUDA/CPU/Windows/ARM64/MPS, so say only that the workaround applies to the
reported ROCm case rather than asserting other backends are unaffected.
* ci(pins): validate pins.json's python pin against requires-python
The launcher's zod schema declares `python: z.string()` (required) and passes
pins.python to `uv venv --python` on both the bootstrap and legacy install
paths, before anything is installed. Bumping project.requires-python without
bumping pins.json therefore builds a venv the package metadata then rejects -
and the check passed, because it only looked at torchIndexUrl.
check_pins.py now also checks that pins.json's python satisfies
project.requires-python. It stays stdlib-only (CI runs it with a bare python3),
so the specifier handling is hand-rolled and deliberately narrow: ==, !=, >=,
<=, > and < against dotted numeric versions, zero-padded so 3.12 == 3.12.0.
Anything it cannot evaluate is reported rather than guessed at. Differentially
tested against packaging over 1014 version/operator/bound combinations with no
divergences.
Hardening found while attacking the above:
- check_python never raises. requires-python is optional in PEP 621 and can be
mistyped as a bare TOML float; either used to throw out of check_pins(),
which runs before the torchIndexUrl loop and so suppressed the ROCm drift
check this script exists for.
- A present-but-null platform section ("linux": null) passed silently, because
.get() cannot tell it from an absent one.
- Version components are bounded, so a pin like 3.12.99999999 - which satisfies
every specifier but which uv cannot resolve to an interpreter - is rejected.
- A trailing comma in requires-python is no longer read as an empty clause.
Also drop the unverified 'isn't needed for the other torch backends' clause
from the ROCm caveat, per review: nothing on CUDA/CPU/Windows/ARM64/MPS was
smoke-tested with torch 2.12.
* ci(pins): reject unreal python pins by shape, not by digit count
The python pin was validated with a bounded regex, which made a bogus
version merely unlikely rather than impossible: "3.12.9999" passed, and
`uv python find 3.12.9999` finds no interpreter, so CI stayed green
while the launcher's install broke at the last step.
Replace the heuristic with two checks that make the bad values
unrepresentable:
- The pin must be exactly major.minor. `uv venv --python 3.12` already
resolves to the newest 3.12.x, `--python 3.12.7` demands one exact
build that can leave uv's index, and the launcher's reinstall check
compares only major()/minor() of this field anyway. That removes the
whole 3.12.<anything> family by shape.
- Its major.minor must appear in project.classifiers. requires-python
says which versions the metadata allows; the classifiers say which
ones we ship for. Without it, an unreal version inside an open-ended
requires-python (">=3.11" and a "3.99" pin) satisfies every clause and
no digit bound catches it.
Also fix the module docstring: only the path handed to python is
location-independent, not the working directory it is typed in.
Hardening found by attacking the above:
- Bound the classifier pattern's digits like its two siblings. int()
refuses a string of more than 4300 digits, so an unbounded pattern let
a classifier raise straight out of check_python - which runs first and
would have taken the torchIndexUrl checks with it.
- Guard the pins.json top level. A non-object raised AttributeError from
pins.get(), with the same suppressing effect (pre-existing).
- Reject leading zeros, so the pin has exactly one spelling.
- Report dynamic classifiers as dynamic rather than as absent.
- De-duplicate classifier versions so a repeat is not listed twice.
Tests assert the reason a patch pin is rejected, not just the exit code:
the classifier check rejects those too, so without that a loosened
version pattern would leave the suite green while advising the user to
add a classifier for "3.12.7".
---------
Co-authored-by: Claude Fable 5 <[email protected]>
Co-authored-by: Jonathan <[email protected]>
* ci: assert uv.lock keeps torch installable on linux/aarch64
Nothing in CI covered aarch64, so a dependency bump could silently re-break the
PyPI fallback that linux/aarch64 relies on for torch and torchvision. That
already happened once, when a ROCm bump narrowed tool.uv.environments.
Check the lockfile rather than re-resolving: tool.uv.environments must still
admit aarch64, every torch extra must resolve torch and torchvision there, and
the pinned versions must ship linux/aarch64 wheels for each supported Python.
Narrowing tool.uv.environments or dropping the extras' aarch64 fallback pins
both leave uv lock --locked green, so neither was caught before.
* fix(ci): survive percent-encoded wheel URLs in aarch64 lock check
The PyTorch WHL indexes percent-encode the `+` of local versions in
wheel URLs, so a marker regression that pins aarch64 torch to a WHL
index crashed `parse_wheel_filename` with a raw traceback instead of a
verdict -- in exactly the still-locks-cleanly state the check exists
for. Unquote the filename, and turn any remaining unparseable name into
a one-line failure instead of a traceback.
Also from review: look up version-less dependency entries (uv omits
version/source when a package resolves to a single version across the
lockfile), check every dependency entry matching the aarch64
environment rather than the first, and cap `--with packaging` below 26
so a parsing-strictness bump can't change the verdict on its own.
---------
Co-authored-by: Lincoln Stein <[email protected]>
Brings in 18 upstream commits: video generation (#9163), Krea-2 (#9304),
Ideogram 4 (#9303), Wan 2.2, pressure-dependent brush opacity (#9232), GGUF
T5 / Qwen3 tokenizer support, and a queue-status performance refactor (#9355).
Nine files conflicted. Seven were the fork's wildcard_records service landing
on the same lines as upstream's videos/gallery services — both sides kept.
session_queue_sqlite.get_queue_status took upstream's lightweight 4-column
current-item query (which avoids deserializing the session graph on every
status poll) and carries the fork's origin_prefix filter into it, so an
origin-scoped caller still cannot see another scope's current item. Added a
negative-path test for that: the existing origin-scope test only covered a
matching item, so it would have passed with the filter dropped.
test_session_queue_status_event_isolation lost the get_current monkeypatch
race test, per upstream — the single-snapshot read makes the race structurally
impossible, and the monkeypatch would have become a silent no-op.
diffusers moves 0.37 -> 0.39 and imageio[ffmpeg] / psutil are new, so this
needs `uv sync`.
The pytest matrix jobs already run 9-11 minutes per platform on main, and
heavier PRs (e.g. #9163) push windows-cpu past the 15-minute cap, cancelling
the run mid-pytest. Raise the cap to 30 minutes to give feature branches
headroom.
Co-authored-by: Claude Fable 5 <[email protected]>
Establish the four-group module architecture: app owns boot, providers, and
composition roots; platform owns domain-neutral transport, state, query, i18n,
and UI foundations; workbench owns the project aggregate, shell, and widget
runtime; eight vertical features (queue, gallery, generation, workflow, upscale,
identity, models, nodes) sit behind registered public entry modules. Dependency
direction app -> workbench/features -> platform is enforced by an executable
dependency policy with zero migration exceptions. Includes deriving the
workbench aggregate command interface from the reducer intent union.
Claude-Session: https://claude.ai/code/session_019CLrxZcNUq3vT6CLpJB4ju