Compare commits

...

9 Commits

Author SHA1 Message Date
Matt Miller
5b6c6e7e85
Merge d224df799b into 985fb9d6ad 2026-07-05 03:56:43 -07:00
Alexander Piskun
985fb9d6ad
[Partner Nodes] fix(logs-auth): mask authorization headers in logs (#14774)
Some checks are pending
Detect Unreviewed Merge / detect (push) Waiting to run
Python Linting / Run Ruff (push) Waiting to run
Python Linting / Run Pylint (push) Waiting to run
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Waiting to run
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Waiting to run
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Waiting to run
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Waiting to run
Execution Tests / test (macos-latest) (push) Waiting to run
Execution Tests / test (ubuntu-latest) (push) Waiting to run
Execution Tests / test (windows-latest) (push) Waiting to run
Test server launches without errors / test (push) Waiting to run
Unit Tests / test (macos-latest) (push) Waiting to run
Unit Tests / test (ubuntu-latest) (push) Waiting to run
Unit Tests / test (windows-2022) (push) Waiting to run
Signed-off-by: bigcat88 <bigcat88@icloud.com>
2026-07-05 13:55:29 +03:00
Alexis Rolland
7f287b705e
fix: Bug when setting transparency in color picker (#14764)
Some checks are pending
Detect Unreviewed Merge / detect (push) Waiting to run
Python Linting / Run Ruff (push) Waiting to run
Python Linting / Run Pylint (push) Waiting to run
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Waiting to run
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Waiting to run
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Waiting to run
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Waiting to run
Execution Tests / test (macos-latest) (push) Waiting to run
Execution Tests / test (ubuntu-latest) (push) Waiting to run
Execution Tests / test (windows-latest) (push) Waiting to run
Test server launches without errors / test (push) Waiting to run
Unit Tests / test (macos-latest) (push) Waiting to run
Unit Tests / test (ubuntu-latest) (push) Waiting to run
Unit Tests / test (windows-2022) (push) Waiting to run
2026-07-04 19:13:38 -04:00
Matt Miller
d224df799b chore(assets): log when assets disabled due to missing DB deps
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Addresses CodeRabbit review: the missing-optional-dependency fallback in
setup_database() silently disabled the assets system with no startup
message, unlike the exception path which logs. Add a warning so operators
can tell why /api/assets/* returns 503.
2026-07-01 00:39:43 -07:00
Matt Miller
a5ff07fe63 Merge remote-tracking branch 'origin/master' into matt/be-786-phase-1-remove-enable-assets-from-oss-assets-always-on-cloud 2026-07-01 00:38:27 -07:00
Matt Miller
b369532116 fix(assets): fail safe to 503 when DB unavailable; keep --enable-assets as no-op
Address automated review feedback on the always-on assets change:
- setup_database(): on DB init failure or missing dependencies, call
  disable_assets_routes() + asset_seeder.disable() so /api/assets/* returns a
  clean 503 instead of 500s against an uninitialized DB (restores the fail-safe
  the removed else-branch used to provide).
- feature_flags: derive the advertised 'assets' capability from live backend
  availability instead of hardcoding True, so clients degrade gracefully.
- cli_args: re-add --enable-assets as a hidden deprecated no-op so existing
  launchers still passing the flag don't abort argparse.
- routes: add assets_enabled() accessor for the feature-flag derivation.
2026-06-30 15:25:26 -07:00
Matt Miller
5bcb2b085c
Merge branch 'master' into matt/be-786-phase-1-remove-enable-assets-from-oss-assets-always-on-cloud 2026-06-30 14:56:56 -07:00
Matt Miller
a5446a0001
Merge branch 'master' into matt/be-786-phase-1-remove-enable-assets-from-oss-assets-always-on-cloud 2026-06-30 14:28:25 -07:00
Matt Miller
50f33ab900 feat(assets): enable asset system by default; remove --enable-assets
The asset system is now always on. Removes the --enable-assets opt-in
flag and the conditional registration it gated:

- Asset API routes are always registered (with the user manager)
- The background asset scanner always runs (hashing stays opt-in via
  --enable-asset-hashing, default off — no new blake3 cost on a default launch)
- /upload/image asset registration and output enrichment always active
- The "assets" server feature flag is now unconditionally true
- A database-init failure degrades gracefully instead of hard-exiting,
  matching the prior default-path behavior
2026-06-30 14:19:29 -07:00
10 changed files with 77 additions and 68 deletions

View File

@ -54,7 +54,7 @@ def _require_assets_feature_enabled(handler):
return _build_error_response(
503,
"SERVICE_DISABLED",
"Assets system is disabled. Start the server with --enable-assets to use this feature.",
"Assets system is unavailable.",
)
return await handler(request)
@ -102,6 +102,15 @@ def disable_assets_routes() -> None:
_ASSETS_ENABLED = False
def assets_enabled() -> bool:
"""Return whether the asset routes are currently serving requests.
Reflects live backend availability: False once disable_assets_routes() has
been called (e.g. after a database init failure or missing dependencies).
"""
return _ASSETS_ENABLED
def _build_error_response(
status: int, code: str, message: str, details: dict | None = None
) -> web.Response:

View File

@ -239,7 +239,9 @@ database_default_path = os.path.abspath(
os.path.join(os.path.dirname(__file__), "..", "user", "comfyui.db")
)
parser.add_argument("--database-url", type=str, default=f"sqlite:///{database_default_path}", help="Specify the database URL, e.g. for an in-memory database you can use 'sqlite:///:memory:'.")
parser.add_argument("--enable-assets", action="store_true", help="Enable the assets system (API routes, database synchronization, and background scanning).")
# Deprecated no-op: the asset system is now always enabled. Kept (hidden) so that
# existing launchers/containers still passing --enable-assets don't fail argparse.
parser.add_argument("--enable-assets", action="store_true", help=argparse.SUPPRESS)
parser.add_argument("--enable-asset-hashing", action="store_true", help="Compute blake3 content hashes when scanning assets. Hashing enables future asset-portability features (deduplication, cross-machine model resolution) but adds startup cost and per-output cost on large models directories. Off by default; enable to opt in.")
parser.add_argument("--feature-flag", type=str, action='append', default=[], metavar="KEY[=VALUE]", help="Set a server feature flag. Use KEY=VALUE to set an explicit value, or bare KEY to set it to true. Can be specified multiple times. Boolean values (true/false) and numbers are auto-converted. Examples: --feature-flag show_signin_button=true or --feature-flag show_signin_button")
parser.add_argument("--list-feature-flags", action="store_true", help="Print the registry of known CLI-settable feature flags as JSON and exit.")

View File

@ -103,7 +103,7 @@ _CORE_FEATURE_FLAGS: dict[str, Any] = {
"max_upload_size": args.max_upload_size * 1024 * 1024, # Convert MB to bytes
"extension": {"manager": {"supports_v4": True}},
"node_replacements": True,
"assets": args.enable_assets,
"assets": True,
}
# CLI-provided flags cannot overwrite core flags
@ -162,4 +162,13 @@ def get_server_features() -> dict[str, Any]:
Returns:
Dictionary of server feature flags
"""
return SERVER_FEATURE_FLAGS.copy()
features = SERVER_FEATURE_FLAGS.copy()
# Advertise the assets capability based on live backend availability rather
# than a static default, so clients degrade gracefully when the database
# failed to initialize or its dependencies are missing.
try:
from app.assets.api.routes import assets_enabled
features["assets"] = assets_enabled()
except Exception:
features["assets"] = False
return features

View File

@ -9,6 +9,7 @@ from typing import Any
import folder_paths
logger = logging.getLogger(__name__)
_SENSITIVE_HEADERS = {"authorization", "x-api-key"}
def get_log_directory():
@ -73,6 +74,10 @@ def _format_data_for_logging(data: Any) -> str:
return str(data)
def _redact_headers(headers: dict) -> dict:
return {k: ("***" if k.lower() in _SENSITIVE_HEADERS else v) for k, v in headers.items()}
def log_request_response(
operation_id: str,
request_method: str,
@ -101,7 +106,7 @@ def log_request_response(
log_content.append(f"Method: {request_method}")
log_content.append(f"URL: {request_url}")
if request_headers:
log_content.append(f"Headers:\n{_format_data_for_logging(request_headers)}")
log_content.append(f"Headers:\n{_format_data_for_logging(_redact_headers(request_headers))}")
if request_params:
log_content.append(f"Params:\n{_format_data_for_logging(request_params)}")
if request_data is not None:

View File

@ -6,15 +6,11 @@ import os
def enrich_output_with_assets(output_ui: dict) -> dict:
"""Register file-type output entries as assets and inject their ``id``.
Runs at output-processing time, once per produced output, when
--enable-assets is set. Returns a new dict; entries without a resolvable
on-disk file path are left unchanged. Errors are caught per-entry so a
failure never blocks execution or the other entries.
Runs at output-processing time, once per produced output. Returns a new
dict; entries without a resolvable on-disk file path are left unchanged.
Errors are caught per-entry so a failure never blocks execution or the
other entries.
"""
from comfy.cli_args import args
if not args.enable_assets:
return output_ui
import folder_paths
from app.assets.services.ingest import register_file_in_place, DependencyMissingError

View File

@ -16,23 +16,30 @@ class ColorToRGBInt(io.ComfyNode):
],
outputs=[
io.Int.Output(display_name="rgb_int"),
io.Color.Output(display_name="hex")
io.Color.Output(display_name="hex"),
io.Float.Output(display_name="alpha"),
],
)
@classmethod
def execute(cls, color: str) -> io.NodeOutput:
# expect format #RRGGBB
if len(color) != 7 or color[0] != "#":
raise ValueError("Color must be in format #RRGGBB")
# expect format #RRGGBB or #RRGGBBAA
if len(color) not in (7, 9) or color[0] != "#":
raise ValueError("Color must be in format #RRGGBB or #RRGGBBAA")
try:
int(color[1:], 16)
except ValueError:
raise ValueError("Color must be in format #RRGGBB") from None
raise ValueError("Color must be in format #RRGGBB or #RRGGBBAA") from None
alpha = 1.0
if len(color) == 9:
alpha = int(color[7:9], 16) / 255.0
color = color[:7]
r, g, b = hex_to_rgb(color)
rgb_int = r * 256 * 256 + g * 256 + b
return io.NodeOutput(rgb_int, color)
return io.NodeOutput(rgb_int, color, alpha)
class ColorExtension(ComfyExtension):

27
main.py
View File

@ -20,6 +20,7 @@ from app.logger import setup_logger
setup_logger(log_level=args.verbose, use_stdout=args.log_stdout)
from app.assets.seeder import asset_seeder
from app.assets.api.routes import disable_assets_routes
from app.assets.services import register_output_files
import itertools
import utils.extra_config
@ -457,9 +458,15 @@ def setup_database():
try:
if dependencies_available():
init_db()
if args.enable_assets:
if asset_seeder.start(roots=("models", "input", "output"), prune_first=True, compute_hashes=args.enable_asset_hashing):
logging.info("Background asset scan initiated for models, input, output")
if asset_seeder.start(roots=("models", "input", "output"), prune_first=True, compute_hashes=args.enable_asset_hashing):
logging.info("Background asset scan initiated for models, input, output")
else:
# Optional DB dependencies are missing, so init_db() is skipped and the
# asset backend has no database. Disable assets so /api/assets/* returns
# a clean 503 instead of 500s against an uninitialized DB.
logging.warning("Optional database dependencies are missing; assets system disabled.")
disable_assets_routes()
asset_seeder.disable()
except Exception as e:
if "database is locked" in str(e):
logging.error(
@ -468,16 +475,10 @@ def setup_database():
" --database-url sqlite:///path/to/another.db"
)
sys.exit(1)
if args.enable_assets:
logging.error(
f"Failed to initialize database: {e}\n"
"The --enable-assets flag requires a working database connection.\n"
"To resolve this, try one of the following:\n"
" 1. Install the latest requirements: pip install -r requirements.txt\n"
" 2. Specify an alternative database URL: --database-url sqlite:///path/to/your.db\n"
" 3. Use an in-memory database: --database-url sqlite:///:memory:"
)
sys.exit(1)
# The database is unusable. Fail safe by disabling assets so endpoints
# return 503 (service unavailable) rather than 500s on every request.
disable_assets_routes()
asset_seeder.disable()
logging.error(f"Failed to initialize database. Please ensure you have installed the latest requirements. If the error persists, please report this as in future the database will be required: {e}")

View File

@ -252,11 +252,7 @@ class PromptServer():
else args.front_end_root
)
logging.info(f"[Prompt Server] web root: {self.web_root}")
if args.enable_assets:
register_assets_routes(self.app, self.user_manager)
else:
register_assets_routes(self.app)
asset_seeder.disable()
register_assets_routes(self.app, self.user_manager)
routes = web.RouteTableDef()
self.routes = routes
self.last_node_id = None
@ -438,20 +434,19 @@ class PromptServer():
resp = {"name" : filename, "subfolder": subfolder, "type": image_upload_type}
if args.enable_assets:
try:
tag = image_upload_type if image_upload_type in ("input", "output") else "input"
result = register_file_in_place(abs_path=filepath, name=filename, tags=[tag])
resp["asset"] = {
"id": result.ref.id,
"name": result.ref.name,
"asset_hash": result.asset.hash,
"size": result.asset.size_bytes,
"mime_type": result.asset.mime_type,
"tags": result.tags,
}
except Exception:
logging.warning("Failed to register uploaded image as asset", exc_info=True)
try:
tag = image_upload_type if image_upload_type in ("input", "output") else "input"
result = register_file_in_place(abs_path=filepath, name=filename, tags=[tag])
resp["asset"] = {
"id": result.ref.id,
"name": result.ref.name,
"asset_hash": result.asset.hash,
"size": result.asset.size_bytes,
"mime_type": result.asset.mime_type,
"tags": result.tags,
}
except Exception:
logging.warning("Failed to register uploaded image as asset", exc_info=True)
return web.json_response(resp)
else:

View File

@ -109,7 +109,6 @@ def comfy_url_and_proc(comfy_tmp_base_dir: Path, request: pytest.FixtureRequest)
"main.py",
f"--base-directory={str(comfy_tmp_base_dir)}",
f"--database-url={db_url}",
"--enable-assets",
"--listen",
"127.0.0.1",
"--port",

View File

@ -1,16 +1,9 @@
"""Tests for enrich_output_with_assets in comfy_execution/asset_enrichment.py."""
import os
import types
import unittest
from unittest.mock import MagicMock, patch
def _make_args(enable_assets: bool):
a = types.SimpleNamespace()
a.enable_assets = enable_assets
return a
def _make_register_result(ref_id="ref-id-2"):
result = MagicMock()
result.ref.id = ref_id
@ -22,9 +15,8 @@ def _make_register_result(ref_id="ref-id-2"):
_DEFAULT_BASE = os.path.join(__import__("tempfile").gettempdir(), "asset-enrichment-test-base")
def _mocked_modules(*, enable_assets=True, register_file_in_place=None, directory=_DEFAULT_BASE):
def _mocked_modules(*, register_file_in_place=None, directory=_DEFAULT_BASE):
return {
"comfy.cli_args": MagicMock(args=_make_args(enable_assets)),
"folder_paths": MagicMock(get_directory_by_type=MagicMock(return_value=directory)),
"app.assets.services.ingest": MagicMock(
register_file_in_place=register_file_in_place or MagicMock(return_value=_make_register_result()),
@ -33,10 +25,9 @@ def _mocked_modules(*, enable_assets=True, register_file_in_place=None, director
}
def _call(output_ui, *, enable_assets=True, file_exists=True, register_result=None, directory=_DEFAULT_BASE):
def _call(output_ui, *, file_exists=True, register_result=None, directory=_DEFAULT_BASE):
register_mock = MagicMock(return_value=register_result or _make_register_result())
mocked = _mocked_modules(
enable_assets=enable_assets,
register_file_in_place=register_mock,
directory=directory,
)
@ -53,11 +44,6 @@ def _call(output_ui, *, enable_assets=True, file_exists=True, register_result=No
class TestEnrichOutputWithAssets(unittest.TestCase):
def test_disabled_returns_unchanged(self):
output = {"images": [{"filename": "a.png", "subfolder": "", "type": "output"}]}
result = _call(output, enable_assets=False)
self.assertNotIn("id", result["images"][0])
def test_non_list_value_passed_through(self):
output = {"text": "hello"}
result = _call(output)