Skip to content

Add Comfy-Usage-Source pass-through for API node requests#14404

Merged
alexisrolland merged 6 commits into
masterfrom
add-comfy-usage-source-header
Jun 12, 2026
Merged

Add Comfy-Usage-Source pass-through for API node requests#14404
alexisrolland merged 6 commits into
masterfrom
add-comfy-usage-source-header

Conversation

@robinjhuang

@robinjhuang robinjhuang commented Jun 10, 2026

Copy link
Copy Markdown
Member

Captures the Comfy-Usage-Source header (or extra_data.comfy_usage_source) on POST /prompt and forwards it on API nodes' requests to api.comfy.org, defaulting to comfyui-api for direct API calls that don't identify themselves. Lets partner node API usage be attributed to the originating client. Shared outbound headers (auth, Comfy-Env, Comfy-Usage-Source) are centralized in a get_comfy_api_headers() helper — note this also adds Comfy-Env to the Sonilo streaming and relative download paths, which previously only sent auth.

API Node PR Checklist

Scope

  • Is API Node Change

Pricing & Billing

  • Need pricing update
  • No pricing update

If Need pricing update:

  • Metronome rate cards updated
  • Auto‑billing tests updated and passing

QA

  • QA done
  • QA not required

Comms

  • Informed Kosinkadink
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c281ea07-095f-4278-b511-097405e8e86b

📥 Commits

Reviewing files that changed from the base of the PR and between ee2cdae and b8595c6.

📒 Files selected for processing (2)
  • execution.py
  • server.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • execution.py

📝 Walkthrough

Walkthrough

Adds a new hidden input COMFY_USAGE_SOURCE (Hidden.comfy_usage_source / comfy_usage_source) accepted by HiddenHolder and registered for API nodes. The server copies an incoming Comfy-Usage-Source request header into extra_data when missing; execution reads that into hidden inputs (V3 and legacy). get_usage_source and get_comfy_api_headers expose the value (fallback "comfyui-api"), and client, Sonilo, and cloud-download code use get_comfy_api_headers so outbound requests include a Comfy-Usage-Source header.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding Comfy-Usage-Source header pass-through for API node requests, which is the primary objective evident in the changeset.
Description check ✅ Passed The description is directly related to the changeset, explaining how the Comfy-Usage-Source header is captured and forwarded, detailing the centralization of shared outbound headers, and documenting behavioral changes to Sonilo and download paths.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Capture the Comfy-Usage-Source header (or extra_data.comfy_usage_source)
on POST /prompt and forward it on API nodes' outbound requests to
api.comfy.org, defaulting to comfyui-server when absent.
@alexisrolland

alexisrolland commented Jun 11, 2026

Copy link
Copy Markdown
Member

I am concerned this could be redundant or create confusion with the Comfy-Env header because it seems there are overlapping values:

Comfy-Env:

  • cloud
  • cli
  • local-git (which is the manual Python install)
  • local-desktop
  • local-portable

Comfy-Usage-Source:

  • comfy-cli -> seems equivalent to Comfy-Env.cli
  • comfyui-frontend -> seems covered by Comfy-Env.cloud, Comfy-Env.local-git, Comfy-Env.local-desktop, Comfy-Env.local-portable
  • comfyui-server -> Not sure what this is
  • comfy-mcp -> New value to be added to Comfy-Env.mcp?

What do you think @robinjhuang ?

@robinjhuang

Copy link
Copy Markdown
Member Author

I am concerned this could be redundant or create confusion with the Comfy-Env header because it seems there are overlapping values:

Comfy-Env:

  • cloud
  • cli
  • local-git (which is the manual Python install)
  • local-desktop
  • local-portable

Comfy-Usage-Source:

  • comfy-cli -> seems equivalent to Comfy-Env.cli
  • comfyui-frontend -> seems covered by Comfy-Env.cloud, Comfy-Env.local-git, Comfy-Env.local-desktop, Comfy-Env.local-portable
  • comfyui-server -> Not sure what this is
  • comfy-mcp -> New value to be added to Comfy-Env.mcp?

What do you think @robinjhuang ?

So usage source is the surface that the user is initiating the call from.
Env is the environment ComfyUI is installed in. So in that sense I think they are very different, despite having overlapping values. WDYT @alexisrolland ?

pull Bot pushed a commit to Mu-L/ComfyUI_frontend that referenced this pull request Jun 11, 2026
…-Org#12772)

Adds `comfy_usage_source: 'comfyui-frontend'` to the prompt body's
`extra_data`. The backend forwards this to API nodes' upstream requests
via the `Comfy-Usage-Source` header, so partner node API usage can be
attributed to the frontend.

Used in Comfy-Org/ComfyUI#14404
@alexisrolland

Copy link
Copy Markdown
Member

I am concerned this could be redundant or create confusion with the Comfy-Env header because it seems there are overlapping values:
Comfy-Env:

  • cloud
  • cli
  • local-git (which is the manual Python install)
  • local-desktop
  • local-portable

Comfy-Usage-Source:

  • comfy-cli -> seems equivalent to Comfy-Env.cli
  • comfyui-frontend -> seems covered by Comfy-Env.cloud, Comfy-Env.local-git, Comfy-Env.local-desktop, Comfy-Env.local-portable
  • comfyui-server -> Not sure what this is
  • comfy-mcp -> New value to be added to Comfy-Env.mcp?

What do you think @robinjhuang ?

So usage source is the surface that the user is initiating the call from. Env is the environment ComfyUI is installed in. So in that sense I think they are very different, despite having overlapping values. WDYT @alexisrolland ?

The definition of environment (where it's installed) vs surface (from where it's used) make sense, but then we would need to adjust the values a bit:

  • Comfy-Env: we should probably remove cli which I believe is a surface?
  • Comfy-Usage-Source: comfyui-server does not sound like a surface, is it for direct api calls?

I think it would make sense after adjusting the values:

Usage-Source / Env cloud local-git local-desktop local-portable
comfy-cli
comfyui-frontend
comfyui-api
comfy-mcp
@Kosinkadink

Copy link
Copy Markdown
Member

Overall looks fine, only was able to find that Comfy-Env is not sent to everything + the manual requirement to add the header to sonilo. Adding a helper to make sure Comfy-Env + Comfy-Usage-Source will be accounted for universally would help, in case we add more headers in the future so we don't forget anything.

@Kosinkadink

Kosinkadink commented Jun 11, 2026

Copy link
Copy Markdown
Member

Posted by an AI agent (Amp) on behalf of @Kosinkadink.

Nice clean pass-through! One suggestion: the three outbound call sites that hit the Comfy API now build an overlapping header set by hand, and they've drifted — client.py sends auth + Comfy-Env + Comfy-Usage-Source, while nodes_sonilo.py and download_helpers.py send auth + Comfy-Usage-Source (no Comfy-Env). That divergence is exactly the kind of thing that makes the next shared header easy to forget in one spot.

Consider factoring the common set into a single helper in comfy_api_nodes/util/_helpers.py:

def get_comfy_api_headers(node_cls: type[IO.ComfyNode]) -> dict[str, str]:
    """Common headers (auth, deploy environment, usage source) for Comfy API requests.

    Centralizes these headers so every Comfy API request sends a consistent set and new
    shared headers only need to be added in one place. Intended for relative/cloud URLs
    resolved against ``default_base_url()``; because the result includes auth, callers must
    not attach it to arbitrary absolute/presigned URLs.
    """
    return {
        **get_auth_header(node_cls),
        "Comfy-Env": get_deploy_environment(),
        "Comfy-Usage-Source": get_usage_source(node_cls),
    }

Then the call sites collapse to one line each:

# client.py (relative URLs only)
payload_headers.update(get_comfy_api_headers(cfg.node_cls))

# nodes_sonilo.py
headers = get_comfy_api_headers(cls)
headers.update(endpoint.headers)

# download_helpers.py (relative 'cloud' URLs only)
headers = get_comfy_api_headers(cls)

Note this also adds Comfy-Env to the Sonilo and relative-download paths, which I think is the right call for attribution consistency since both are Comfy API control-plane requests — but it's a behavior change worth a line in the description. The helper is deliberately limited to relative/cloud paths; uploads and presigned-URL PUTs should keep going without it since it carries auth.

@robinjhuang

robinjhuang commented Jun 11, 2026

Copy link
Copy Markdown
Member Author

I am concerned this could be redundant or create confusion with the Comfy-Env header because it seems there are overlapping values:
Comfy-Env:

  • cloud
  • cli
  • local-git (which is the manual Python install)
  • local-desktop
  • local-portable

Comfy-Usage-Source:

  • comfy-cli -> seems equivalent to Comfy-Env.cli
  • comfyui-frontend -> seems covered by Comfy-Env.cloud, Comfy-Env.local-git, Comfy-Env.local-desktop, Comfy-Env.local-portable
  • comfyui-server -> Not sure what this is
  • comfy-mcp -> New value to be added to Comfy-Env.mcp?

What do you think @robinjhuang ?

So usage source is the surface that the user is initiating the call from. Env is the environment ComfyUI is installed in. So in that sense I think they are very different, despite having overlapping values. WDYT @alexisrolland ?

The definition of environment (where it's installed) vs surface (from where it's used) make sense, but then we would need to adjust the values a bit:

  • Comfy-Env: we should probably remove cli which I believe is a surface?
  • Comfy-Usage-Source: comfyui-server does not sound like a surface, is it for direct api calls?

I think it would make sense after adjusting the values:

Usage-Source / Env cloud local-git local-desktop local-portable
comfy-cli ✓ ✓ ✓ ✓
comfyui-frontend ✓ ✓ ✓ ✓
comfyui-api ✓ ✓ ✓ ✓
comfy-mcp ✓ ✗ ✗ ✗

Yes! Comfy-Usage-Source: comfyui-server is for direct api calls. Updated to send as comfyui-api

@robinjhuang

Copy link
Copy Markdown
Member Author

@Kosinkadink Done in b58af42 — shared headers (auth, Comfy-Env, Comfy-Usage-Source) are now centralized in get_comfy_api_headers() in util/_helpers.py, and all three call sites use it. This also means the Sonilo streaming and relative download paths now send Comfy-Env too, which they previously missed — noted in the PR description.

@Kosinkadink

Copy link
Copy Markdown
Member

nice, lgtm

@Kosinkadink

Copy link
Copy Markdown
Member

@alexisrolland any further comments on this?

@alexisrolland alexisrolland merged commit bc5f8ec into master Jun 12, 2026
17 checks passed
@alexisrolland alexisrolland deleted the add-comfy-usage-source-header branch June 12, 2026 01:20
POWERFULMOVES added a commit to POWERFULMOVES/PMOVES-Creator that referenced this pull request Jun 17, 2026
* feat(assets): add job_ids filter to GET /api/assets (Comfy-Org#13998)

* feat(assets): add job_ids filter to GET /api/assets

Mirrors the existing cloud `job_ids` query param on the local Python server:
clients can pass a comma-separated list (or repeated query params) of UUIDs
to filter assets by their associated job.

The `AssetReference.job_id` column already exists, so no migration is
needed — this just plumbs the filter through schema → service → query.

Marks the parameter as available in both runtimes by dropping the
`[cloud-only]` description prefix and the `x-runtime: [cloud]` tag from
the OpenAPI spec, per the OSS field-drift convention (absent runtime tag
= populated by both local and cloud).

* fix(assets): tighten job_ids — array schema, max_length, narrow except

From cursor-reviews on the parent commit:

- OpenAPI: declare job_ids as `type: array, items: string format: uuid`
  with `style: form, explode: true` so it matches the documented
  contract (and matches sibling include_tags/exclude_tags shape).
  Description now states both accepted shapes explicitly.
- Schema: cap `job_ids` at 500 entries (max_length on the Pydantic
  field) so a client can't splice an unbounded list into the IN clauses.
- Schema: drop `AttributeError` from the except — `raw` only contains
  `str` items by construction, so `uuid.UUID(<str>)` raises `ValueError`
  exclusively; the second clause was dead code.

* fix(assets): tighten job_ids validator + add schema-level tests

Aligns with the parallel hardening from draft PR Comfy-Org#13848 (now closed as
a duplicate). The validator now:

- Raises ValueError on non-string list items (was: silently dropped).
- Raises ValueError on non-string / non-list top-level values like dict
  or int (was: silently passed through to Pydantic's downstream coercion).

Adds tests-unit/assets_test/queries/test_list_assets_query.py covering
the validator end-to-end: CSV canonicalization, dedup order, default
empty, invalid UUID, non-string list item, non-string non-list value,
and the max_length=500 boundary.

* feat(prompt): enforce canonical UUID prompt_id at job creation

POST /prompt previously accepted any client-supplied prompt_id verbatim,
str()-coercing even non-strings, and minting the literal job id "None"
for an explicit JSON null. The new GET /api/assets job_ids filter matches
stored job ids as canonical UUIDs exactly, so a non-UUID id minted a job
whose assets could never be filtered.

- validate_job_id (comfy_execution/jobs.py): requires a string in the
  canonical lowercase hyphenated UUID form; raises ValueError otherwise,
  including parseable-but-non-canonical spellings (uppercase, braced, URN,
  bare hex), which would otherwise be silently rewritten and then miss
  every exact-match lookup downstream (history keys, websocket
  correlation, /interrupt, the assets job_ids filter).
- POST /prompt: absent or null prompt_id means the server mints uuid4;
  invalid means 400 invalid_prompt_id on the standard error envelope.
- openapi.yaml: document the request-side prompt_id (format uuid,
  nullable) on PromptRequest.
- tests: unit matrix for validate_job_id; integration tests against the
  booted server covering rejection, acceptance, and null handling.

---------

Co-authored-by: guill <jacob.e.segal@gmail.com>

* feat(assets): include asset id in executed WebSocket message (Comfy-Org#13862)

* feat(assets): enrich executed WS message with asset metadata

When --enable-assets is set, each file-type output entry in the
`executed` WebSocket message now includes id, name, asset_hash, size,
and mime_type — matching the shape already returned by /upload/image.

The enrichment lives in comfy_execution/asset_enrichment.py (no torch
dependency) and is called from both send sites in execution.py: freshly
executed nodes register the file inline via register_file_in_place;
cached node re-sends look up the existing AssetReference by file path
to avoid re-hashing. Errors are caught per-entry so a failure never
blocks the WS message from sending.

* fix(assets): inject only id in executed WS message per Asset Identity RFC

Per the Asset Identity RFC, the executed WebSocket payload should carry
id alone — hash is already encoded in the filename, and name/preview_url/
size belong behind GET /api/assets/{id} rather than being pushed eagerly.

Simplifies the DB lookup path: we only need ref.id, so the asset.hash
null-check is no longer required as a fallback trigger.

* fix(assets): reject path traversal when resolving output abs_path

Subfolder/filename were joined and absolutized without containment check,
so '..' segments or an absolute filename could escape the type's base
directory and register an unrelated on-disk file as an asset.

Add commonpath-based containment check; skip enrichment (warn, leave
entry unchanged) when the resolved path escapes base. Catches ValueError
from cross-drive paths on Windows.

* docs(assets): drop Asset Identity RFC reference from docstring

* docs(assets): trim docstring to what enrichment does, not what it doesn't

* test(assets): use real platform paths so containment check works on Windows

The previous test setup patched os.path.abspath to identity and used a
POSIX-style '/output' base, which collided with Windows path separators
in os.path.commonpath. Drop the abspath/join patches and use a real
tempdir-rooted base so the containment check runs against actual
platform paths.

* refactor(assets): enrich at output-processing time, not in the WS send path

Per review: enrichment lived inside the client_id-guarded send sites, so a
headless run (no websocket client) never registered assets at all, and
ui_outputs/history stored the un-enriched entries.

Now output_ui is enriched once, right after the node produces it and before
it is stored in ui_outputs — so registration happens regardless of connected
clients, and the asset id flows into history and the execution cache for
free. _send_cached_ui re-sends the stored (already-enriched) dict verbatim,
which lets the DB-lookup-by-path fallback be deleted: every enrichment is
now a fresh output, and register_file_in_place re-hashes on upsert so an
overwritten path can never carry a stale id.

* revert(assets): drop job_ids filter from GET /api/assets (Comfy-Org#14408)

The job_ids query filter added in Comfy-Org#13998 has no live consumer: the
frontend Generated tab kept sourcing from GET /jobs, and the cloud side
removed its equivalent filter from the shared asset spec. Carrying it on
the local server only re-introduces Core<->Cloud drift on the shared
contract, so remove it to match.

Removed: the job_ids field + validator on ListAssetsQuery, the IN(...)
clauses in list_references_page, the service/route passthrough, and the
filter-only tests.

Kept: the canonical-UUID prompt_id enforcement at job creation (also
landed in Comfy-Org#13998). It stands on its own -- job ids are matched verbatim
by history keys, websocket correlation, and /interrupt -- and cloud
inherits it by running core for execution, so no divergence is created.

* chore(openapi): sync shared API contract from cloud@e3c52ad (Comfy-Org#14406)

* I don't think this actually works anymore. (Comfy-Org#14403)

* ops: tolerate already force casted dynamic weight (Comfy-Org#14410)

Some custom nodes .to weights completely out of load context which
can wreak havoc if its for a model that is not active. Detect this
condition and just let it fall-through to the non-dynamic loader
straight up.

* Improve context window resizing for SCAIL2 (CORE-286) (Comfy-Org#14394)

* Fix SCAIL-2 reference mask background convention (Comfy-Org#14415)

* [Partner Nodes] fix(GPT Image): handle mismatched image sizes returned when size="auto" (Comfy-Org#14414)

Signed-off-by: bigcat88 <bigcat88@icloud.com>

* [Partner Nodes] fix(KlingTextToVideoNode): validation error for "kling-v2-master" model (Comfy-Org#14418)

Signed-off-by: bigcat88 <bigcat88@icloud.com>

* Make --enable-manager-legacy-ui imply --enable-manager (Comfy-Org#14421)

* Don't crash when using flux kv cache with split batches. (Comfy-Org#14422)

* Add Comfy-Usage-Source pass-through for API node requests (Comfy-Org#14404)

* [Partner Nodes] feat: enable Bria Replace Background node (Comfy-Org#14397)

* Fix potential dtype issue with ideogram 4. (Comfy-Org#14436)

* add --high-ram option (Comfy-Org#14437)

Add this option for users who know they have so much ram they want
to pin everything or have a pagefile that outruns their disk speed.

The removes the RAM pressure caps completely and pins behind the
primary model load forcing all models to be permanently comitted
to RAM.

* [Partner Nodes] feat: add Runway Aleph2 node (Comfy-Org#14306)

Signed-off-by: bigcat88 <bigcat88@icloud.com>

* Use comfy kitchen apply rope in omnigen2 model. (Comfy-Org#14442)

* Add 10-bit video support (Comfy-Org#14452)

Create Video gets a bit_depth option (8-bit/10-bit); the selected depth is carried by the video and applied when it gets encoded. Save Video and Video Slice now keep the source bit depth instead of always quantizing to 8-bit, so 10-bit videos stay 10-bit. 10-bit uses h264 with the yuv420p10le pixel format,so there's no new codec or container.

Signed-off-by: bigcat88 <bigcat88@icloud.com>

* Expose deploy_environment in /system_stats (Comfy-Org#14402)

* Remove the comfy python path append.

* Revert last commit. Last time I use this stupid GitHub app.

* Fix nondeterministic video decode at unaligned widths (CORE-299) (Comfy-Org#14438)

* [Partner Nodes] feat(Tripo3d): add new "Import 3D" node (Comfy-Org#14466)

Signed-off-by: bigcat88 <bigcat88@icloud.com>

* bump manager version to 4.2.2 (Comfy-Org#14471)

* This is already auto enabled by default. (Comfy-Org#14476)

* chore: update embedded docs to v0.5.4 (Comfy-Org#14478)

* fix(ruff): exclude vendored ComfyUI tree from lint (scope to PMOVES code)

* fix(ruff): resolve PMOVES-owned lint in pmoves_health

Scoped ruff (after excluding vendored ComfyUI) surfaced PMOVES-owned violations:
remove unused imports (F401), fix undefined FastAPI annotation (F821) by importing
it at module scope, and # noqa intentional demo/CLI prints (T201).

* fix(ruff): resolve PMOVES-owned lint in pmoves_announcer

Scoped ruff (after excluding vendored ComfyUI) surfaced PMOVES-owned violations:
remove unused imports (F401), fix undefined FastAPI annotation (F821) by importing
it at module scope, and # noqa intentional demo/CLI prints (T201).

* fix(ruff): resolve PMOVES-owned lint in pmoves_registry

Scoped ruff (after excluding vendored ComfyUI) surfaced PMOVES-owned violations:
remove unused imports (F401), fix undefined FastAPI annotation (F821) by importing
it at module scope, and # noqa intentional demo/CLI prints (T201).

---------

Signed-off-by: bigcat88 <bigcat88@icloud.com>
Co-authored-by: Matt Miller <mattmiller@comfy.org>
Co-authored-by: guill <jacob.e.segal@gmail.com>
Co-authored-by: Comfy Org PR Bot <snomiao+comfy-pr@gmail.com>
Co-authored-by: comfyanonymous <121283862+comfyanonymous@users.noreply.github.com>
Co-authored-by: rattus <46076784+rattus128@users.noreply.github.com>
Co-authored-by: Barish Ozbay <17261091+drozbay@users.noreply.github.com>
Co-authored-by: Jukka Seppänen <40791699+kijai@users.noreply.github.com>
Co-authored-by: Alexander Piskun <13381981+bigcat88@users.noreply.github.com>
Co-authored-by: Jedrzej Kosinski <kosinkadink1@gmail.com>
Co-authored-by: Robin Huang <robin.j.huang@gmail.com>
Co-authored-by: John Pollock <pollockjj@users.noreply.github.com>
Co-authored-by: Dr.Lt.Data <128333288+ltdrdata@users.noreply.github.com>
Co-authored-by: Daxiong (Lin) <contact@comfyui-wiki.com>
Co-authored-by: pmoves-fork-sync[bot] <pmoves-fork-sync[bot]@users.noreply.github.com>
Co-authored-by: POWERFULMOVES <142271328+POWERFULMOVES@users.noreply.github.com>
zhangp365 pushed a commit to zhangp365/ComfyUI that referenced this pull request Jun 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants