Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:

steps:
- name: Check out code
uses: actions/checkout@v7
uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
Expand Down
56 changes: 0 additions & 56 deletions .github/workflows/ci-cursor-review.yml

This file was deleted.

41 changes: 6 additions & 35 deletions .github/workflows/cla.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,50 +8,22 @@ on:

permissions:
actions: write
contents: read # 'read' is enough because signatures live in a REMOTE repo
contents: read # 'read' is enough because signatures live in a REMOTE repo
pull-requests: write
statuses: write

jobs:
cla-assistant:
runs-on: ubuntu-latest
steps:
# The CLA action normally requires every commit author in a PR to sign.
# We only want the PR author to sign, so we allowlist all other committers
# by computing them from the PR's commits and excluding the PR author.
- name: Build author-only allowlist
id: allowlist
if: >
github.event_name == 'pull_request_target' ||
(github.event_name == 'issue_comment' && github.event.issue.pull_request && (
github.event.comment.body == 'recheck' ||
github.event.comment.body == 'I have read and agree to the Contributor License Agreement'
))
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
PR_AUTHOR: ${{ github.event.pull_request.user.login || github.event.issue.user.login }}
BASE_ALLOWLIST: action@github.com,actions-user,ampagent,claude,comfy-pr-bot,GitHub Action,github-actions,github-actions[bot],Glary Bot,Glary-Bot,*[bot]
run: |
others=$(gh api "repos/${{ github.repository }}/pulls/${PR_NUMBER}/commits" --paginate \
--jq '.[] | (.author.login // empty), (.committer.login // empty)' \
| sort -u | grep -vix "${PR_AUTHOR}" | paste -sd, -)
if [ -n "$others" ]; then
echo "allowlist=${BASE_ALLOWLIST},${others}" >> "$GITHUB_OUTPUT"
else
echo "allowlist=${BASE_ALLOWLIST}" >> "$GITHUB_OUTPUT"
fi

- name: CLA Assistant
# Run on PR events, on "recheck" comment, or when someone posts the exact signing phrase.
# IMPORTANT: this phrase must match `custom-pr-sign-comment` below.
if: >
github.event_name == 'pull_request_target' ||
(github.event_name == 'issue_comment' && github.event.issue.pull_request && (
github.event.comment.body == 'recheck' ||
github.event.comment.body == 'I have read and agree to the Contributor License Agreement'
))
uses: contributor-assistant/github-action@ca4a40a7d1004f18d9960b404b97e5f30a505a08 # v2.6.1
github.event.comment.body == 'recheck' ||
github.event.comment.body == 'I have read and agree to the Contributor License Agreement'
uses: contributor-assistant/github-action@v2.6.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# PAT required to write to the centralized signatures repo.
Expand All @@ -66,10 +38,9 @@ jobs:
path-to-signatures: signatures/cla.json
branch: main

# Only the PR author must sign: bots plus every non-author committer
# are allowlisted via the "Build author-only allowlist" step above.
# Allowlist bots so they don't need to sign (optional, comma-separated).
# *[bot] is a catch-all for any GitHub App bot account.
allowlist: ${{ steps.allowlist.outputs.allowlist }}
allowlist: ampagent,claude,coderabbitai[bot],comfy-pr-bot,dependabot[bot],github-actions[bot],copilot-swe-agent[bot],devin-ai-integration[bot],*[bot]

# Custom PR comment messages
custom-notsigned-prcomment: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish_package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
permissions:
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6

- name: Set up Python 3.12
uses: actions/setup-python@v6
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ruff_check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
ruff_check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/run-on-gpu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:

steps:
- name: Check out code
uses: actions/checkout@v7
uses: actions/checkout@v6

- name: Check Nvidia
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-mac.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:

steps:
- name: Check out code
uses: actions/checkout@v7
uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:

steps:
- name: Check out code
uses: actions/checkout@v7
uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
Expand Down
8 changes: 8 additions & 0 deletions comfy_cli/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -912,6 +912,14 @@ def validate(
except Exception:
pass

# env var fallback (same resolution as `comfy run`)
from comfy_cli.env_backend import get_backend_from_env
env_h, env_p = get_backend_from_env()
if not host and env_h:
host = env_h
if not port and env_p is not None:
port = env_p

try:
graph = Graph.load(mode=mode, input_path=input_path, host=host or "127.0.0.1", port=port or 8188)
except LoadError as e:
Expand Down
102 changes: 102 additions & 0 deletions comfy_cli/env_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
"""Shared environment-variable backend resolver for ComfyUI host/port.

Supports four env vars (highest to lowest precedence within the env layer):
COMFY_BACKEND=host:port (combined, colon-separated)
COMFY_HOST_PORT=host:port (combined, same as COMFY_BACKEND)
COMFY_HOST + COMFY_PORT (separate)

CLI flags always take precedence over env vars; this helper is a fallback.
"""
from __future__ import annotations

import os
import re
from typing import Optional

# IPv6 address detection: contains multiple colons or starts with [
_IPV6_RE = re.compile(r"^\[.+\]$|^([0-9a-fA-F]{0,4}:){2,}")


def get_backend_from_env() -> tuple[Optional[str], Optional[int]]:
"""Resolve host/port from environment variables.

Returns (host, port) where either may be None if not set.
Handles IPv6 bracketed hosts (e.g. ``[::1]:8188``).
Logs a warning on invalid port values instead of silently swallowing.
"""
be = os.environ.get("COMFY_BACKEND") or os.environ.get("COMFY_HOST_PORT")
if be:
b = be.replace("http://", "").replace("https://", "").strip("/")
host, port = _split_host_port(b)
if port is not None and port is not None and not _valid_port(port):
import logging
logging.getLogger(__name__).warning(
"Invalid port %r in COMFY_BACKEND/COMFY_HOST_PORT, ignoring", port
)
return host, None
return host, port

h = os.environ.get("COMFY_HOST")
p = os.environ.get("COMFY_PORT")
if h or p:
port: Optional[int] = None
if p:
try:
port = int(p)
except ValueError:
import logging
logging.getLogger(__name__).warning(
"Invalid port %r in COMFY_PORT, ignoring", p
)
return h, None
if not _valid_port(port):
logging.getLogger(__name__).warning(
"Port %d out of range in COMFY_PORT, ignoring", port
)
return h, None
return h, port

return None, None


def _split_host_port(s: str) -> tuple[Optional[str], Optional[int]]:
"""Split a host:port string, handling IPv6 bracketed hosts.

Examples:
"127.0.0.1:8188" -> ("127.0.0.1", 8188)
"[::1]:8188" -> ("[::1]", 8188)
"[::1]" -> ("[::1]", None)
"localhost:8188" -> ("localhost", 8188)
"::1" -> ("::1", None) (bare IPv6, no port)
"""
# Bracketed IPv6: [host]:port or [host]
if s.startswith("["):
end = s.find("]")
if end == -1:
return s, None # malformed, return as-is
host = s[:end + 1] # keep brackets
rest = s[end + 1:]
if rest.startswith(":"):
try:
return host, int(rest[1:])
except ValueError:
return host, None
return host, None

# Bare IPv6 (multiple colons, no brackets) — no port
if s.count(":") > 1:
return s, None

# Normal host:port
if ":" in s:
h, p = s.rsplit(":", 1)
try:
return h, int(p)
except ValueError:
return h, None
return s, None


def _valid_port(port: int) -> bool:
"""Check if a port number is in valid range."""
return 1 <= port <= 65535
23 changes: 20 additions & 3 deletions comfy_cli/host_port.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,26 @@ def _to_port(s: str, original: str) -> int:


def resolve_host_port(host: str | None, port: int | None) -> tuple[str, int]:
"""Fill host/port from ``config.background`` then defaults; validate and
bracket IPv6 literals so callers building ``'http://{host}:{port}'`` get a
well-formed URL (e.g. ``'::1'`` -> ``'[::1]'``)."""
"""Fill host/port from environment variables, then ``config.background``,
then defaults; validate and bracket IPv6 literals so callers building
``'http://{host}:{port}'`` get a well-formed URL (e.g. ``'::1'`` ->
``'[::1]'``).

Resolution order (highest to lowest):
1. Explicit ``host`` / ``port`` arguments (from CLI flags).
2. Environment variables: ``COMFY_BACKEND``, ``COMFY_HOST_PORT``,
``COMFY_HOST`` / ``COMFY_PORT``.
3. Persisted ``config.background`` server.
4. Built-in defaults (``127.0.0.1:8188``).
"""
from comfy_cli.env_backend import get_backend_from_env

env_h, env_p = get_backend_from_env()
if not host and env_h:
host = env_h
if not port and env_p is not None:
port = env_p

cfg = ConfigManager()
bg = cfg.background
if not host and bg is not None:
Expand Down
Loading