Repository navigation
hal0 v1.7.0
Highlights
- Image models have a catalog of their own.
hal0 comfyui models list|install|remove, the dashboard and/api/comfyui/models,/jobsand
/filesinstall catalog models with resumable, sha256-checked downloads,
and the installer offers a starter model sized to the box's RAM
(HAL0_IMAGE_MODELSanswers it unattended). Image models leave the LLM
registry, and/v1/images/generationsrenders every installed catalog
model the API can render plus your own SDXL and SD 1.5 checkpoints.
(#3032, #3037, #3054, #3061) - Open WebUI generates images through hal0's
/v1. Its image engine
moves to OpenAI, pointed at/v1/images/generationswith the box's client
key, so its images get the same model, defaults and GPU switch as any other
client, and the four defects that broke Open WebUI images are gone. (#3062) - Image mode lasts only while an image renders. Every GPU LLM slot
unloads for a render and reloads about 10 seconds after the last one, and
[image].idle_restore_minutesis gone. A chat that arrives mid-render
waits, a slot still loading is waited for and unloaded before ComfyUI runs,
and a failed unload rolls the switch back instead of leaving an LLM beside
ComfyUI. (#3062, #3080, #3082) - A fit check before you install or render. Every catalog model carries a
memory fit against the GPU pool and RAM, judged at the size you pick, and a
render that cannot fit is refused with 422image.wont_fitbefore any slot
unloads. Memory figures come from one owner (ADR-0039), which reads a
capped LXC guest's RAM limit, shows other guests on a shared GPU and
credits what image mode frees on bare-metal unified memory. (#3064, #3070,
#3079) - A new Image Gen tab: Generate, Models and Engine. A GPU status line says
what the GPU is doing and what a render costs chat, the Generate tab follows
the switch phase by phase and the render step by step, fit meters sit on
every model card, and Hold image mode pins the GPU behind a confirm.
(#3078) - ComfyUI's GPU gate authenticates with a token of its own, never the
admin key, so image generation keeps working withhal0 auth require on.
Runhal0 slot restart imgonce after updating. (#3067, ADR-0040)
Breaking
-
hal0 comfyui orchestrate-modelsis gone: usehal0 comfyui models install <id>.... There is no alias, and the command exits with the CLI's unknown-command error.hal0 comfyui models listshows the image-model catalog with each model's install state, the GB it still has to download and the pick recommended for this box (--jsonfor scripts);models installinstalls the models one at a time, following each download to the end, and exits 1 if any fails;models remove <id>andhal0 comfyui filesremove a model and list the weight files on disk.--dry-runand--log-dirare gone with the command. The vendoredget_*.shdownload scripts underinstaller/comfyui/are deleted: downloads run in hal0, with resume and a sha256 check. -
gen.capabilitiesin ahal0-setup.yamlanswer file is replaced bygen.models: a list of image model ids (for examplemodels: [sdxl-lightning];hal0 comfyui models listprints them all). A file that still hasgen.capabilitiesfails validation with a message naminggen.models, and an unknown id is refused with the list of known ones.gen.modelsis installed whengen.modeisscaffold_and_download, one model after another with a progress line every few seconds, andhal0 setup --emit-answerswrites it. Theautosetup path installs no image models; the guided path andinstall.shoffer a starter set and download nothing unless you choose one. Pick any later withhal0 comfyui models installor from the dashboard. -
POST /api/comfyui/models/fetchis gone: usePOST /api/comfyui/models/{model_id}/install. The fetch route took a capability and family (orauto); an install names one catalog model, answers 202 with its job, and is followed onGET /api/comfyui/jobs. A request the old route would have started now gets 404 or 405. -
COMFYUI_MODELS_DIR,COMFYUI_WORKFLOWS_DIRandCOMFYUI_DATA_DIRno longer do anything. There is no fallback. ComfyUI's models, workflows and data live under one root that follows the configured model store (<model store>/comfyui);HAL0_COMFYUI_DATA_ROOTmoves that root as a whole. A box that set one of the three variables to a path other than that root should setHAL0_COMFYUI_DATA_ROOTinstead. -
/v1/images/generationsanswers an image id outside the catalog with 404image.model_unknown, andimage.model_not_curatedis gone. There is no alias: a client that matched the old code should match the new one.modelis now an installed catalog image model the API can render (sdxl-turbo,sd-1.5-pruned-emaonly,sdxl-lightning,qwen-image-lightningorqwen-image), ackpt/<path>checkpoint, or empty for the model selected forimg. An emptymodelused to rendersdxl-turbowhatever was installed; a client that relied on that now gets theimgselection's model, or 422image.model_requiredwhen nothing is selected, and should sendmodelexplicitly. The 404 body lists the ids that work inerror.details.available. A catalog model that is not installed gets 409image.model_not_installed(naming the install call), one the API cannot render (video, edit, upscale) gets 422image.model_not_renderable, and an emptymodelwith nothing selected gets 422image.model_required.Flux-2-Klein-9B-GGUFused to render through a fallback to the SDXL Turbo graph and now gets 404. Install a catalog model withhal0 comfyui models install <id>. -
[image].default_sizenow defaults to"", the model's native size, instead of"1024x1024". On animgslot whose TOML does not setdefault_size(or sets"", as the shipped seed does), a request with nosizerenders at the model's native size, where it used to get1024x1024: Stable Diffusion 1.5 and SDXL Turbo now come out at512x512. A slot that sets a value keeps it. Setdefault_size = "1024x1024"in the slot's[image]table to keep the old size for every model. A box that already has animgslot is unaffected: the v1.6.x installer seededdefault_size = "1024x1024"into/etc/hal0/slots/img.toml, andhal0 updatedoes not rewrite slot files, so every model keeps rendering at1024x1024there. To opt in to native sizes, setdefault_size = ""in the[image]table of that file, or clear the Default size field on the dashboard's Capabilities page; the change applies to the next request, with no restart. -
Image models are no longer in the model registry: the curated image ids are gone from
/api/modelsandhal0 model pull.sdxl-turbo,sd-1.5-pruned-emaonly,sdxl-lightning,esrgan-4x,SD-Turbo-GGUFandFlux-2-Klein-9B-GGUFare no longer curated registry entries, so they are neither listed nor pullable there, and there is no alias. A pull of an image catalog id, or of any model with theimagecapability, answers 422model.image_assetbefore a row is seeded or a byte moves, and its message nameshal0 comfyui models install <id>. The same 422 answersPOST /api/models,PUT /api/models/{id}andPOST /api/models/add-from-path(hal0 model add) whenpathis inside ComfyUI's models folder; a scan no longer registers a file there. Migration 014 deletes the registry rows whose path is in that folder (the default location; a row under a relocated tree stays until you remove it) or whose capabilities includeimage, with their bookkeeping rows: rows only, never files, and the image catalog reads each model's state from the files on disk. No operator step is needed. A script that pulled an image id should callhal0 comfyui models install <id>. -
The GGUF image ids
SD-Turbo-GGUFandFlux-2-Klein-9B-GGUFare dropped, with no converter. The image catalog has no GGUF model, so neither can be installed again. A request that names one gets 404image.model_unknown, and so does a request with nomodelon a box whoseimgselection names one: such a box must install a catalog model first (hal0 comfyui models install <id>) and then pick it under AI Capabilities → Image or withhal0 capabilities set img img --model <id>: picking a model that is not downloaded yet is refused with 409capability.model_not_downloaded. The bundles that named them now name catalog models (see Changed). -
Open WebUI generates images through hal0's
/v1/images/generationsinstead of calling ComfyUI. hal0 sets Open WebUI's image engine to OpenAI, points it at hal0's/v1with the box client key, and sets the model to the one selected forimg. Every render from Open WebUI therefore uses the same template, step count, size and GPU switch a/v1client gets, including theimgslot's[image].default_sizeanddefault_steps. hal0 leaves Open WebUI's image size empty so that hal0 picks it. Image generation is turned on in Open WebUI only while theimgmodel is installed and the API can render it. Each render from Open WebUI switches the GPU to image mode for that render only. A chat request for a GPU-slot model that arrives while the image renders waits for the render and for hal0 to reload the chat model, so Open WebUI's reply in the same turn, right after the image, is served instead of being refused with503 gpu.image_mode(see the next entry). Chat on NPU and CPU slots is not affected.COMFYUI_BASE_URL,COMFYUI_WORKFLOWandCOMFYUI_WORKFLOW_NODESare no longer written toopenwebui.env, and the next render (onhal0 update, an image model install or removal, or animgselection change) removes the copies hal0 wrote. A value you set by hand stays. Choose the model in hal0 (hal0 capabilities set img img --model <id>): Open WebUI's image settings list OpenAI's model names, and a change made there lasts only until Open WebUI restarts. -
The GPU leaves image mode as soon as image generation stops, and
[image].idle_restore_minutesis gone. Image mode used to hold untilidle_restore_minutes(60 by default) passed with no image activity. Now it holds only while a render runs: a/v1/images/generationsrequest (Open WebUI and the omni loop use it too), or a render in ComfyUI's queue from its editor. About ten seconds after the last render hal0 frees ComfyUI's models and reloads the GPU slots it unloaded. A request for one of those slots waits for the render to finish and for that reload, up to 120 seconds, instead of getting503 gpu.image_mode; it gets the 503, withRetry-After: 15, only when image mode is pinned, ComfyUI's queue cannot be read, or the 120 seconds run out. A switch to generation withoutpin, such as the dashboard's Start button, also ends about ten seconds after the last render, or after the switch when nothing renders: pin image mode to hold it, and it holds until you unpin it or force a switch to inference. Pinning needs image mode: passpinon the switch that enters it, becausePOST /api/comfyui/pinwith{"pinned": true}while the GPU serves LLMs now answers 409comfyui.not_image_modeinstead of a pin that never held. A ComfyUI queue that never drains keeps image mode until you force a switch to inference.hal0 updateand eachhal0-apistart removeidle_restore_minutesfrom slot files, the API refuses it, and the "Idle restore" row is gone from Settings.GET /api/comfyui/statusdropsarbiter.idle_restore_at, addsarbiter.switchingandarbiter.image_jobs, and reports its top-levelmodeasgenerationonly once a switch into image mode has finished (inferencewhile hal0 switches or restores the GPU). -
ComfyUI's GPU gate refuses a prompt that hal0 cannot hand the GPU to. After updating, run
hal0 slot restart imgonce. ComfyUI'shal0_gpu_gatenode, which every prompt passes (the editor's, and hal0's own/v1renders), now waits while hal0 is still switching the GPU, asks again if an LLM request took the GPU back before the prompt was queued, and waits up to 180 seconds. When it lets a prompt through in image mode it first tells hal0 (POST /api/comfyui/prompt-handoff), which holds image mode for that prompt for about 3 seconds, until ComfyUI's queue shows it. It refuses the prompt and queues nothing, answering ComfyUI's/promptwith a 503 that carries the reason, when hal0 rejects the switch, stops answering mid-switch, still holds the GPU for the LLM slots after three switch requests, has not finished the switch within 180 seconds, has been unreachable (or silent about which mode holds the GPU) for 5 seconds, or answers 401 or 403 (refused at once, with a reason that says hal0 requires authentication). It used to let such a prompt through, and it could render next to the loaded LLM slots. The node reacheshal0-apion the port and address it binds (HAL0_PORT,HAL0_BIND_HOST) and reads a lightweightGET /api/comfyui/arbiter, so a box whose API is not on port 8080 keeps its image generation. To let prompts through whilehal0-apiis down or refuses the gate's credential, unguarded as in v1.6.5, setHAL0_COMFYUI_GATE_FAIL_OPEN=1in/etc/hal0/api.env, restarthal0-api, then runhal0 slot restart img: hal0 writes the setting into the slot's unit when it loads the slot, so a baresystemctl restartdoes not pick it up. On a box with auth enforcement on (hal0 auth require onorHAL0_REQUIRE_AUTH=1), or one whoseHAL0_BIND_HOSTis a specific address with an admin key set, the gate authenticates with a token of its own (see Security); a container started before the update has none, so restartingimgis what keeps image generation working there. After a rollback to 1.6.x the newer gate keeps working with the older hal0, reading its/api/comfyui/status, so no step is needed, except that it refuses image prompts on a box that enforces auth (1.6.x does not know the gate's token), and onceimgrestarts under 1.6.x it refuses image prompts on a box whosehal0-apidoes not answer on127.0.0.1:8080(another port, orHAL0_BIND_HOSTset to a specific address), and, unlike the 1.6.x gate, it still refuses when that hal0 is silent or slow (seedocs/guides/update-and-rollback.mdx). A runningimgcontainer keeps the gate it started with, andhal0 updatedoes not restart slots, so restart it once after updating. Until then hal0 keeps that older gate waiting while it switches or restores the GPU, but the older gate still lets a prompt through whenhal0-apiis unreachable or after its own 120 seconds, and such a prompt can render next to the LLM slots. -
GET /api/comfyui/statusdropsmemory: read thememoryblock ofGET /api/stats/hardwareinstead.memorywas{gtt_used_gb, gtt_ceil_gb, ram_used_gb, ram_ceil_gb, pressure}, folded from ComfyUI's/system_statsagainst ceilings hal0 hard-coded (an 80 GB GPU pool, 96 GB of RAM) that your box may not have. There is no alias: the key is absent from the response, so a client that readbody["memory"]["gtt_used_gb"]now fails on a missing key.GET /api/stats/hardwarecarries the one memory budget (ADR-0039), with this box's own GPU pool and RAM totals. The dashboard's old Image Gen pane and its GPU memory gauges are gone with the key: the tab is now Generate, Models and Engine.
Added
-
/api/comfyui/models,/api/comfyui/jobsand/api/comfyui/fileslist, install, cancel and remove image models, andhal0 comfyui modelsdrives them.GET /api/comfyui/modelslists the catalog with each model's install state and the size still to download and in total (missing_gbandapprox_gb; a GB is 2^30 bytes here, as in the memory budget);POST /api/comfyui/models/{model_id}/installstarts a download and answers 202 with its job;DELETE /api/comfyui/models/{model_id}removes the model's weights and workflow and keeps, and lists askeptwith the models that hold it, any weight file another complete model loads (two models can load one file set, so each keeps the other complete) unless?include_shared=truedeletes it too and lists the models it uninstalled asaffected, deleting their workflows as well;GET /api/comfyui/jobsandPOST /api/comfyui/jobs/{model_id}/cancelfollow and stop installs;GET /api/comfyui/fileslists the weight files on disk with the catalog models that own each. An install that cannot work is refused before anything downloads: 409comfyui.models_dir_not_writablefor a destination directory the hal0 user cannot write, 409comfyui.staging_cross_device, or 507comfyui.insufficient_disk. A removal that hits a file it cannot delete stops with 409comfyui.remove_failedand lists what had already gone.hal0 comfyui models list|install|removeandhal0 comfyui filesare the CLI over them;installtakes several ids and installs them one at a time, andremove --include-shareddeletes the shared files too. An install of a model whose weights are all on disk but whose workflow is missing (a sibling model completed it) writes the workflow instead of answering 409comfyui.model_installed, and the disk check counts a resumable partial download as already fetched. -
The installer offers a starter image model, and
HAL0_IMAGE_MODELSanswers it unattended. On a box with a ROCm lane for ComfyUI,install.shlists the image models the API can render with the GB each still has to download, and marks the one that suits the box's memory as recommended (Stable Diffusion 1.5 below 16 GB, SDXL Lightning from 16 GB, Qwen-Image Lightning from 96 GB). Enter downloads nothing, a bad answer is asked again, and a failed download warns while the install carries on.HAL0_IMAGE_MODELSskips the prompt:recommended, or catalog ids or menu numbers separated by commas or spaces;noneor0skips the step even on a terminal, and an install with no terminal downloads nothing unless it is set. The guidedhal0 setupasks the same question, after its question about scaffolding capability slots. -
The first image model you install that the API can render becomes the
imgmodel. From the installer,hal0 setuporhal0 comfyui models install, hal0 selects it for theimgcapability when none is selected, which starts theimgslot. A selection you made is left alone, and nothing is started. -
hal0-apiwrites the ComfyUI workflow of hand-installed image models when it starts, and a removal is remembered. A model whose weights were copied into the models folder reads as installed, so no install ever wrote its workflow. A model removed while other models still hold its weights stays installed and is not given its workflow back; installing it again restores it. -
GET /api/comfyui/modelsreportsengine: {available, reason}, and an unreadable models tree is a 409 instead of a 500.availableisfalse, with areason, on a host with no ROCm lane; the catalog is listed either way. The model list, install and removal routes, and/v1/images/generations, answer 409comfyui.models_dir_not_writablewhen the hal0 user cannot read the models tree. -
DELETE /api/comfyui/files/{subdir}/{relpath}deletes a weight file no catalog model loads, and the gated MCP toolcomfyui_file_deletecalls it.subdirandrelpathare the fields of aGET /api/comfyui/filesrow, so a file the listing shows without an owner can be deleted, and the answer is{"deleted": "<subdir>/<relpath>"}. It refuses a file a catalog model loads (409comfyui.file_catalog_owned, witherror.details.modelsnaming the owners: remove the model instead), a symlink (400comfyui.file_symlink), asubdirthat is not a model directory or arelpaththat is absolute, climbs out with.., has a hidden part or does not name a weight file such as a.yaml(400comfyui.file_path_invalid), a missing file (404comfyui.file_not_found) and a file the hal0 user may not delete (403comfyui.file_permission). Deleting the checkpoint behind theimgselection re-renders Open WebUI's image settings. EachGET /api/comfyui/filesrow now says whether its path is asymlink. -
GET /api/comfyui/modelsrows carry a memoryfit, andGET /api/comfyui/models/{model_id}/fit?size=WxHjudges the size you choose. A row'sfitis{verdict, reason, need, render_free_gb, ram_free_gb}at the model's native size:fits,tight(the need is above 0.9 of what is free) orwont_fit, judged separately against the GPU pool a render can use (render_free_gb, which does not subtract the image engine, so a back-to-back render is not refused for the weights the engine still holds) and this box's RAM (ram_free_gb, a container's limit included) and never summed, withreasonnaming the budget that decided it. A size the catalog measured is exact; another size of a measured model is an estimate that does not undershoot, and a model nobody measured needs its weight total, a lower bound (need.gpu_estimated,need.ram_estimated).fitisnull, neverwont_fit, when the box cannot say (no GPU lane or pool reading, RAM unreadable). The list also carriesstore: {free_gb, path}, the ComfyUI models folder and the free space under it (free_gbisnullwhen it cannot be measured). The/fitroute answers{"fit": {...} | null, "size": "WxH"}, wheresizeis the size it judged: the one/v1would render for thesizeyou ask about (each side clamped to 64 to 2048 and floored to a multiple of 8, so1030x1030is judged as1024x1024), or the native size when you leave it out. It answers 404comfyui.model_unknownfor an id outside the catalog and 422image.invalid_sizefor asizethat is notWIDTHxHEIGHT. Both read the memory budget (ADR-0039), so a verdict and the memory screens agree about what is free. The list also carriesrecommendation: {id, pool_gb, limited_by}, the figure the recommended model was chosen from (pool_gb, the smaller of the GPU pool and this box's RAM limit) and which of the two set it (limited_byis"ram"or"gpu"); it isnullon a box that was never probed. The fields are in the new Image readiness API reference. -
The
arbiterblock ofGET /api/comfyui/statusandGET /api/comfyui/arbitersays what the GPU is doing.phaseisdraining,unloading,starting_engineorreloadingwhile a switch or a restore runs, andhandoff,renderingorgracein image mode, elsenull(a pinned image mode with nothing rendering has no grace and readsnullbesidepinned: true).restore_atandhandoff_untilare epoch seconds, set only ingraceandhandoff;drain_in_flightcounts the requests still running on the slots being drained;unload_setlists the slots image mode unloaded, or the GPU LLM slots a switch would unload now, and isnullwhen hal0 has not read the slot configs yet (never[]for unknown);llm_waitersis{count, oldest_s}for LLM requests waiting for the GPU. -
ComfyUI's GPU gate reports its version and the prompts it refuses. The
hal0_gpu_gatenode sendsX-Hal0-Gate-Versionon every request tohal0-apiand, when it refuses a prompt, says why withPOST /api/comfyui/gate-refusal(internal: only the gate's own token admits it, from the box itself, and it is not an MCP tool; an empty or whitespace-onlyreasonis 422comfyui.invalid_gate_refusal).GET /api/comfyui/statusgainsgate: {state, version_seen}, wherestateiscurrent,stale(the engine runs an older gate: runhal0 slot restart img),unknown(the gate has not called sincehal0-apistarted) orno_credential(hal0 requires the gate to authenticate and it has no token it can send, orHAL0_BIND_HOSTis a host name), andgate_refusal: {at, reason}, the latest refusal ornull:reasonis the text the gate refused the prompt with or, for a switch request hal0 rejected, hal0's own sentence, cut to 500 characters.GET /api/comfyui/arbitercarriesgate_refusaltoo. hal0 also records a refusal itself when a/v1/images/generationsrender or a workflow launch meets one, because a gate without a credential cannot report on a box that requires one. -
GET /api/comfyui/statusreports the step progress of the render hal0 submitted.progressis{model, step, total, node, elapsed_s, prompt_id}, ornullwhen no such render is live:stepandtotalarenulluntil ComfyUI's first progress frame,nodeis the graph node it is executing, and with several renders queued it is the one that stepped most recently. hal0 follows the render on ComfyUI's websocket (thewebsocketspackage, now a declared dependency,>=13.0) while it polls for the result; a socket that cannot open or drops costs only the read-out, never the render. A render queued from ComfyUI's editor has no entry; readqueuefor it. -
GET /api/config/urlssays whether the ComfyUI link is one you declared. It gainscomfyui_public, true only whenHAL0_COMFYUI_PUBLIC_URLis set. ComfyUI listens on the box's loopback, so thehttp://<host>:8188link hal0 derives from the request host answers on the box itself and nowhere else; the dashboard opens the editor only on the box, or whencomfyui_publicis true, and shows anssh -Lcommand otherwise. -
The Image Gen tab is Generate, Models and Engine. Generate renders from the dashboard: a prompt, a model (the one selected for
imgfirst, then every installed catalog model the API renders, then your own checkpoints), a size (the model's own sizes or a custom one, each side 64 to 2048 and floored to a multiple of 8) and, under More options, a negative prompt, steps, CFG, one to four images and a seed. The cost to chat sits under the button, a run shows its steps with a progress bar and Cancel, and the last 12 results keep Open, Download and Reuse seed. Models lists the image-model catalog by task with Install, Cancel, Remove, Set as default, Generate and Open in ComfyUI, the filters All, Fits this box and Installed, and the free space under the model store; with no model installed it opens on Set up image generation, which installs the recommended model in one click, says why it was chosen (chosen from N GB of RAM (container limit)) and offers two lighter models. Engine has the engine card (state, container, GPU gate, Restart engine, Logs, Stop image mode), the editor card, the queue (n running · n waiting, with hal0's own render) and the GPU device's temperature and clock. The sub-tabs open at#slots/image/generate,#slots/image/modelsand#slots/image/engine, and the tab is reachable on a box with no slots, where the Slots page used to show only "No slots configured". -
A GPU status line heads the Image Gen tab and says what a render costs chat. It names what the GPU is doing (
GPU · chat mode,Switching to image mode,Image mode · hand-off,Rendering · <model> · step n/N,Image mode ends in N s,Reloading GPU slots · k of n ready,Image mode pinned), what the next image pauses (Pauses a, b and c (N GB). They reload about 10 s after the last image.), how many chat requests wait, and the GPU gate's state, and offers the buttons that fit: Hold image mode and Keep image mode (each asks first, because a pin refuses GPU chat until you unpin), Unpin, Stop image mode (it asks before it drops a running render), Cancel, Cancel render, Restart engine, Open editor and Dismiss. A render queued from ComfyUI's editor shows too. On the Inference tab, the card of a slot that image mode paused carries apaused · image modechip, and the banner shown while the GPU is in image mode follows the GPU arbiter and links to the Engine tab. The line is hidden on a host with no GPU pool reading or no ROCm lane. -
Fit meters show whether a model fits before you install or run it. Each model card on the Models tab, and the Generate tab for the model and size you picked, draw two bars (the GPU pool and RAM · this box) with the model's need against what is free, a chip (
Fits · N GB to spare,Tight,Won't fit) and the server's reason. The verdict is always the server's; a box the server cannot judge shows no chip, and a model that won't fit offers one that does. The Generate tab saysjudged at WxHwhen the server clamped the size you asked for, andFit is checked when you generate.for a checkpoint of your own, which has no catalog row. -
Settings → AI Capabilities → Image has a Default size picker.
Model native(the empty value, still the default),512x512,768x768,1024x1024,1328x1328orCustom…; a custom size must be a lower-caseWxHwith each side 64 to 2048 and a multiple of 8. A size saved before stays and does not block saving the other fields: it opens as Custom with a note on the size the engine renders.
Changed
-
/api/comfyui/statusinventory.lorasnow counts LoRAs in subdirectories, and a failed scan hides the counts. The old count read only the top level ofloras/, so Wan's LoRAs, which live in a directory of their own, were missed; every category now comes from one recursive scan of the models tree. When that scan fails,inventoryis null instead of a row of zeros. -
The installer no longer creates the ComfyUI directories, and
HAL0_SKIP_COMFYUIno longer does anything. The block hardcoded/mnt/ai-models/comfyui, ignored[models].storeandHAL0_COMFYUI_DATA_ROOT, and made root-owned directories thehal0user could not write. The ComfyUI provider now creates every directory under the configured store when theimgslot starts, and the installer has 15 steps instead of 16. -
/v1/images/generationsrenders every installed catalog image model the API can render and your own SDXL and SD 1.5 checkpoints, and takes its defaults from the model. A checkpoint you put in the ComfyUIcheckpoints/folder whose safetensors header reads as SDXL or SD 1.5 is addressed asckpt/<path>, the file's path undercheckpoints/without.safetensors(ckpt/afora.safetensors,ckpt/sub/aforsub/a.safetensors); a checkpoint a catalog model loads gets nockpt/id and is addressed by that model's id. An emptymodeluses the model selected forimg. A request that sets nosizegets the slot'sdefault_sizewhen it is not empty, otherwise the model's native size:1024x1024for SDXL,512x512for Stable Diffusion 1.5 and SDXL Turbo,1328x1328for Qwen-Image.stepsfollowextra_body, then the slot'sdefault_stepswhen above 0 (0 is the workflow template's own count), then the template's. The_hal0block of the response carriesmodelandtemplate. Video, edit and upscale models stay in the ComfyUI editor. -
Bundles install image models through the image catalog.
hal0-Pronamessdxl-lightning(7.1 GB) in place ofSD-Turbo-GGUF(1.3 GB), andhal0-MaxandLMX-Omni-52B-Halonameqwen-image-lightning(47.9 GB) in place ofFlux-2-Klein-9B-GGUF(8.4 GB), about 39.5 GB more to download. These are the GB the API andhal0 comfyui models listshow (a GB is 2^30 bytes, as in the memory budget). Applying a bundle, or animgselection, through/api/install/applyor/api/install/apply-selections(whichhal0 setupcalls when the API is up) no longer pulls an image model into the registry: it creates theimgslot with no model when there is none (an existing slot is left alone) and starts the catalog install, the jobPOST /api/comfyui/models/{model_id}/installstarts, which runs on/api/comfyui/jobs. Both responses carry animage_installslist with a{model_id, job_id}row per image model, orjob_id: nullandskippedset to the catalog's error code when it refuses one (for examplecomfyui.install_running), or tocomfyui.install_start_failedwhen the start itself breaks (an I/O error from the models store, say); a refusal never fails the apply. A host with no ROCm lane for ComfyUI reports the slot as skipped withimage_engine_unavailableand creates none, and an id the catalog does not know is skipped withimage_model_unknown. -
Models → Image lists ComfyUI's files by component type, and the
imgslot points at AI Capabilities → Image for its model. The Image / ComfyUI tab of the Models view is the file inventory (GET /api/comfyui/files): checkpoints, diffusion models, text encoders, VAE, LoRAs, upscalers, CLIP vision and latent upscalers, each file marked with the catalog models that load it or as unmanaged, and an unmanaged file that is not a symlink has a Delete button. Image models are not registry rows, so theimgslot drawer's Model field and the slot card's swap popover no longer list/api/models: they show the current model and link to AI Capabilities → Image. That page's Image panel has a new Model row that picks the image model from the catalog models the API can render (marking those not downloaded yet) and your ownckpt/<path>checkpoints; on a host with no ComfyUI image engine it shows a read-only line with the reason instead. -
Memory figures come from one backend owner (ADR-0039).
GET /api/stats/hardwaregains amemoryblock with two budgets that are never summed: the GPU pool (gpu: its kind, total, used and free, what this box's slots and the image engine hold, and the rest asother_guests_gb;image_engine_slotnames the slot that hosts the engine andimage_engine_estimatedsays when its figure is that slot's estimate, which it always is on a discrete card, where ComfyUI's reading covers the whole card) and this box's RAM (ram: limit, used and free, read live), plusrender_free_gb, the pool a render can use once image mode has unloaded this box's GPU slots: the pool less other guests, slots still starting and a 2 GB margin, with the image engine's own memory counted as room, since a model's need is measured above an idle engine and the engine renders inside its own allocation.gpuandrender_free_gbare null when the box has no GPU lane or the kernel counter is missing, and RAM reads as zeros when/proc/meminfocannot be read; the budget uses no fixed ceiling. The Slots page's telemetry bar and memory ruler, the Overview page's health strip and Unified memory card, Settings → Overview and the Slots page's free figure all take their pool, RAM and per-owner figures from that block instead of combining their own counters, so one screen shows one free number. The pool is named "GPU pool · shared" on unified memory and "VRAM" on a discrete card. The block is documented in the Memory budget API reference, and the dashboard guide has a Memory figures section.cgroup_max_mbin the hardware probe follows the same cgroup walk, so the installer sizes models against a limit set on a systemd slice. The Image Gen pane has no gauge of its own; the image fit verdicts and the/v1preflight read it (see Added and Changed); andPOST /api/models/feasibilitykeeps its own fits, tight and exceeds verdicts. -
/v1/images/generationsrefuses a render that cannot fit before any slot unloads. A render whose model needs more GPU pool or RAM than the box has free, judged at the size it will render (the request'ssize, else the slot'sdefault_size, else the model's native size, clamped as the workflow clamps it), answers 422image.wont_fitwith theneed, the two free figures, themodeland thesizeinerror.details, before the GPU arbiter unloads a slot: the LLM slots stay loaded and nothing about the GPU changes. Before, such a render unloaded the LLM slots first and could then run out of memory in the engine. Onlywont_fitrefuses;tight,fits, an unknown budget and a check that itself fails let the render go ahead. Ackpt/<path>checkpoint is judged from its file size.extra_body.hal0_skip_fit_check: trueskips the check. -
API image sizes are GB at 2^30 bytes. The download sizes of image models (
approx_gbandmissing_gbonGET /api/comfyui/models, the image rows'size_gbin the capability picker,missing_gbin 409image.model_not_installed) and the figureshal0 comfyui models list, the installer's starter menu,hal0 setupand the install and disk messages print count a GB as 2^30 bytes, like the memory budget, where the catalog's decimal figure used to leak out: Qwen-Image Lightning reads 47.9 where it read 51.4, SDXL Lightning 7.1 where it read 7.6. The word stays "GB". The plan table ofhal0 setupsays "GB free" and "GB threshold" where it said "GiB", with the same values. Bundle manifests'size_gbstill holds the decimal figure. -
GET /api/comfyui/statusreplacesit_s,etaandstepwithprogress. The three fields were alwaysnull. A client that read them should readprogress.step,progress.totalandprogress.elapsed_s; hal0 reports no rate and no estimate of the time left. -
A prompt the GPU gate refuses answers 503
image.gate_refused, and a refused workflow launch is a 503. Both routes took any non-200 from ComfyUI's/promptfor a failure of their own:POST /v1/images/generationsanswered 502dispatch.upstream_failed, andPOST /api/comfyui/workflows/{name}/launchanswered 202queuedwith anullprompt_id. A 503 in the gate's shape now answers 503image.gate_refusedon both, with the gate's reason inerror.message, and is recorded asgate_refusalonGET /api/comfyui/status; any other non-200 answer is unchanged.
Removed
-
The Image Gen tab's old "render hero" pane is gone, with its GTT and system RAM gauges and placeholder values. The old pane drew a GTT and a system RAM gauge, a frame counter, a hard-coded workflow strip and model-count pills, and filled any figure the status did not carry with a placeholder. A figure the status does not carry is left out, not replaced. The tab's Generate, Models and Engine sub-tabs replace it (see Added); memory figures are the top bar ruler's and the fit meters'.
-
The Services page's ComfyUI Queue drawer is gone, and the ComfyUI link opens only from a page on the box itself. The drawer fetched ComfyUI's
/queueand/historyfrom your browser at:8188, which answers on the box only; the queue is the Engine tab's Queue card now, fromGET /api/comfyui/status. ComfyUI listens on the box's loopback, so thehttp://<host>:8188link the Services page, its board card and the Overview Services widget opened is dead from another machine. There the button is disabled, and the Services page showsOnly reachable on the box itselfand the commandssh -L 8188:127.0.0.1:8188 <user>@<host>. A public URL declared withHAL0_COMFYUI_PUBLIC_URLstill opens from anywhere.
Security
-
ComfyUI's GPU gate authenticates with a token of its own, never the admin key (#3058). The
hal0_gpu_gatenode calls three ADMIN routes before every image prompt (and reports a prompt it refused to a fourth), so a box that enforces auth (hal0 auth require on), or one whoseHAL0_BIND_HOSTis a specific address with an admin key set, refused it, and every image prompt with it. hal0 now mints the gate a token in/var/lib/hal0/comfyui-gate/token(0600in a0700directory, owned by the service user) when theimgslot loads, and mounts that directory read-only into the ComfyUI container; the token is never in the unit file or the container's environment.hal0-apiaccepts it on exactly the gate's four requests (GET /api/comfyui/arbiter,POST /api/comfyui/prompt-handoff,POST /api/comfyui/switchoverasking for image mode withoutforceorpin, where anything else answers403 auth.forbidden, andPOST /api/comfyui/gate-refusal, the reason the gate refused a prompt), only from the box itself (loopback or the addresshal0-apibinds), and on no other route, WebSocket or stream. The gate reads the token on every request and sends it only tohal0-apiby address: through no proxy, never to a host name, and never on to a redirect. A token whose file others could have read is replaced at the nextimgload. Runhal0 slot restart imgonce after updating so the container gets its token. To replace the token, delete the file and restartimg. -
An approved admin MCP call now acts on exactly the resource its approval card names. The gated tools that take a path argument (
slot_delete,model_delete,upstream_delete,stack_applyand the rest) pasted that argument into the REST URL raw. After you approved,..climbed out of the tool's route onto a sibling route, a?or#cut the path short (junk?xdeletedjunk), and a%6Descape was decoded by the server (%6Dy-modelacted onmy-model), so a prompt-injected agent could get an approval for a card that did not name what the call changed. Path arguments are now sent literally: each one is percent-encoded as a single URL segment, so?,#,%, spaces,:and non-ASCII reach the server as typed. A path argument must now be a string (or an integer below 2^53):null, booleans, floats, lists and objects are refused, as are an empty value,.,.., a value containing/(only an argument its route declares{name:path}may contain/between valid segments, each checked on its own: today that iscomfyui_file_delete'srelpath), and a value with a control or invisible formatting character (a zero-width space or a bidi override can make a card read as one name while the value is another). They are refused withmcp.invalid_argbefore any approval card exists (and again if the call is run), instead of after you approved. A missing path argument on a gated tool now returnsmcp.missing_argthe same way, with no card. (#3051) -
ComfyUI model downloads no longer spawn
hfwith the API process environment (they use hal0's own HTTPS downloader).
Fixed
-
The image fit check no longer refuses a large model on a bare-metal unified-memory host because its LLM slots hold the RAM. On such a host the GTT the slots hold comes out of system RAM, so a 128 GB box with about 85 GB of LLM slots loaded refused
qwen-image-lightningwith 422image.wont_fit, although image mode's unload frees that memory before the render. The memory budget gainsrender_ram_free_gb:ram.free_gbplus the GTT the slots hold, credited only on a shared pool whose RAM reading is the host's own, capped at the pool used outside the image engine and never above the RAM limit. The fit verdicts and the/v1/images/generationspreflight judge the RAM need against it, sofit.ram_free_gbcarries it, and a refusal that counted it says "free with the chat slots paused". Containers, LXC guests included, are judged as before: their RAM is not charged for GTT.ram.sourcealso readscontainerwhen lxcfs is mounted over/proc/meminfo, and the Image Gen fit meter labels its RAM figure "free for a render" like the GPU one. (#3072) -
An LLM slot that is still loading when image mode starts no longer finishes loading beside ComfyUI. The switch into image mode unloaded only the LLM slots ready for dispatch, so a slot in a cold start or a wake-on-request was in neither the unload set nor the saved set: it came up on the GPU while ComfyUI rendered and nothing unloaded it. The switch now also takes slots that are pulling, starting or warming. It waits up to 120 seconds for a load in progress, then unloads the slot with the rest and puts it in the saved set, so the restore brings it back. A slot left in
warmingby a load that timed out is unloaded straight away. A load that outlasts the wait leaves the GPU in LLM mode and fails the image request with 503gpu.slot_still_loading.unload_setonGET /api/comfyui/arbiterlists a slot still loading in LLM mode. (#3056) -
A failed unload during the switch into image mode rolls back to LLM mode instead of leaving the LLM slot on the GPU beside ComfyUI. The arbiter records image mode before it unloads the LLM slots, so a slot whose stop outlived its budget stayed loaded while the mode said image, the ComfyUI gate let prompts through, and a render could run next to a loaded LLM until the grace period ended. Now the first failed unload reloads the slots already unloaded, puts the GPU back in LLM mode (dropping a pin) and fails the request with the unload's error; the next render tries the switch again. After an api restart in the middle of a switch, the next switch unloads any slot of the saved set that is still loaded before the engine renders, and rolls back the same way if that fails. (#3059)
-
hal0 no longer announces ComfyUI over mDNS, and no longer calls it LAN-reachable. ComfyUI listens on the box's loopback, so the
_http._tcpannouncement of:8188that Announce services wrote led other machines to a refused connection. The Discovery card announces Open WebUI only (see Migrations for clearing one an earlier hal0 wrote), and the Services page shows ComfyUI no.locallink while that old announcement is still on disk. The services registry, theerror.details.hintof the 422 for a video, edit or upscale model (it now says to open the editor on the box itself or through an SSH tunnel) and the docs say loopback. -
The image starter recommendation uses the guest's RAM limit, so a capped LXC guest is no longer offered a model it cannot load. On a unified-memory host a guest limited to 48 GB still read the host's 128 GB pool and was offered
qwen-image-lightningin the install-time offer, inhal0 setupand inGET /api/comfyui/models; the first render then OOM-killed the image engine. The pick now follows the smaller of the pool and the guest's RAM limit (ram_mborcgroup_max_mb), with the same thresholds (#3057). -
/api/comfyui/statusand/logsuse the right container and unit name on a box with id-keyed slots. Both looked forhal0-slot@img.serviceand thehal0-slot-imgcontainer, but a box migrated to id-keyed slots names them after the slot's id (for examplehal0-slot@10.service), so a running engine reported as absent and the log tail read nothing (Refs #2302). -
The ComfyUI provider no longer launches with an empty
extra_model_paths.yaml. It wrote an empty file whenever the installer's copy was missing, and an empty file hides every model from ComfyUI. It now writes the canonical file when the existing one is absent or blank, and leaves a file the operator filled in alone. -
The Qwen-Image 2512 4-step workflow loads the 2512 Lightning LoRA, not the Edit one.
Qwen-Image-2512-BF16-4-Step-LoRAreferenced the Qwen-Image-Edit Lightning LoRA, a file the Qwen-Image install never fetched and a LoRA made for the edit model, so the workflow failed on a missing file. -
The image capability picker was empty on AMD ROCm hosts. Each image row was checked against a llama.cpp chat profile and all of them failed. Image rows now skip that check, and the picker lists the API-renderable models of the image catalog and your own SDXL and SD 1.5 checkpoints. Applying a model that is not installed names
POST /api/comfyui/models/<id>/install. -
Loading the
imgslot could start a registry pull of a LoRA. The slot's model is a catalog image model such assdxl-lightning, which the LLM registry does not know, so the load asked the registry to pull it. A ComfyUI slot load no longer goes through the registry pull; the weights come from the ComfyUI install route. -
Choosing an image model on the shipped
imgslot (device = "gpu-rocm") was refused. Applying an installed image model with that device, or with none, failed withcapability.illegal_backend_model_pair, because the picker's one ComfyUI row is labelledgpu-vulkan. The seededgpu-rocmis now read as the same GPU lane. -
openwebui.envis written in the format podman's--env-filereads. hal0 quoted values for systemd, but podman keeps the quotes, so any value that was empty or held a space, a quote,$or#reached Open WebUI quoted, andCOMFYUI_WORKFLOW_NODESarrived as a string that made Open WebUI's image settings answer 500. Values are now written as they are, and a value with a line break is refused. A value you set by hand that an older hal0 wrote in quotes keeps its quotes; remove them by hand.HAL0_OWUI_BIND_HOSTis now written first and must be a plain IP address or hostname. A bind host you set by hand in quotes, or with trailing blanks, is refused: the file is left as it was, the installer warns that it failed to writeopenwebui.env, and later renders fail the same way, and logcomponents.owui_env_render_failedin the hal0-api journal, until you remove the quotes or blanks by hand. (#3045) -
Open WebUI no longer dials ComfyUI on an address the slot does not listen on. ComfyUI listens on loopback only, which the Open WebUI container cannot reach, so every image request from Open WebUI was refused. Open WebUI now reaches images through hal0's
/v1, the address it already uses for chat. (#3046) -
Open WebUI renders SDXL-Lightning and other fixed-step models at their own step count. hal0 never set
IMAGE_STEPS, so Open WebUI wrote its default of 50 into the workflow. Open WebUI's OpenAI engine sends no step count, so hal0 uses the slot'sdefault_stepsor the model's own, as for any/v1client. (#3047) -
/v1/images/generationsno longer answers 404 after theimgslot goes down while the GPU stays in image mode. The idle reaper stoppedimgafter the global idle timeout but left the GPU in image mode, so the next request answered 404dispatch.no_routeuntil someone loaded the slot by hand. The reaper now leavesimgalone (#3050). Image mode also starts the slot whenever it finds it down, for example after a crash or a manual unload. (#3048) -
The idle reaper no longer unloads the
imgslot while you work in ComfyUI's editor. It counted only requests that went through hal0, so after the globalidle_timeout_s(300 s by default) it stoppedimgin the middle of an editor session, even with a job running. The GPU arbiter never stops the ComfyUI container once it is up (idle, it holds next to no GPU memory), but the reaper did not follow that rule. Idle-timeout, memory-pressure and pre-load eviction now never unload theimgslot.idle_timeout_sdoes not apply to it.hal0 slot unload imgstill stops it by hand. (#3050) -
The top bar's CPU cell divides RAM by the RAM limit instead of by the GPU pool. The "CPU · RAM" cell of the Slots page's telemetry bar (labelled "CPU · Memory" before) showed this box's RAM in use over the GPU pool's cap on a unified-memory box (for example 16.1 / 116 GB), a denominator that says nothing about the RAM the box may use. It now divides by this box's RAM limit and prints "/ N GB limit" under the gauge when hal0 runs in a container or under a cgroup limit, or "/ N GB" on a plain machine. When RAM cannot be read it shows no figure. The Overview page's "RAM" health cell and Settings → Overview read the same figure.
-
A capped LXC guest's RAM limit is read live and shown. The limit came from the leaf cgroup's
memory.maxand from the install-time hardware probe. Inside an LXC guest that file readsmax, and so does a service's own file when the limit sits on its systemd slice, and the probe is a snapshot taken at install, so the cap on a guest limited to 48 GB was never shown as a limit. The RAM limit is now the smaller ofMemTotal(which lxcfs sets to the guest limit) and the tightest finitememory.maxormemory.limit_in_byteson the process's cgroup chain, read on every poll. -
The memory ruler shows other guests on a shared GPU instead of folding them into this box. Pool usage that none of this box's slots accounted for was drawn as a striped "system" block (its tooltip read "system · KV + runtime"), which on a GPU shared with another guest is that guest's memory. The ruler now draws it as "other guests", with the image engine under its own name, and takes its total from the kernel counter. Per-slot figures are estimates, and the tooltip says so; when they add up to more than the counter, the ruler scales its segments to fit the bar and the API reports the difference as
attribution_excess_gb. -
A non-string
sizeon/v1/images/generationsis a 422, not a 500."size": 1024, a list or an object made the workflow translator raise, and the route answered 500. It now answers 422image.invalid_sizebefore the model is resolved or the GPU is touched, anderror.details.size_typenames the type that was sent as Python spells it (int,float,bool,list,dict), never the value.nulland""still mean no size, and a string the translator cannot read still falls back to the default size. -
A
/v1/images/generationsrequest cancelled during the GPU switch no longer reaches ComfyUI. The switch can take long enough for the client to give up, and the route went on to submit the render once it ended. A client that has gone now gets an empty 499 and nothing is submitted; the arbiter restores the GPU as usual.
Migrations
- Toggle the Discovery card once to clear ComfyUI's old mDNS announcement. A box that announced services on an earlier version keeps its
hal0-addon-comfyuiavahi service after the update, and other machines on the network keep resolving a ComfyUI that refuses them: hal0 no longer announces ComfyUI and does not remove the file by itself. Turn the Discovery (mDNS) card on the Services page on or off once; either removes it (turn it back on if you want Open WebUI announced). The same update also needshal0 slot restart imgonce, for ComfyUI's GPU gate (see Breaking).