Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b3751d9
feat(run): add --client-id so a run can submit as an already-connecte…
Oct 4, 2026
587d0e0
fix(run): redact client_id from telemetry; make withheld watches honest
Oct 4, 2026
eb5e12c
fix(run): stamp the borrowed-client marker on the prompt, not just on…
Oct 4, 2026
8611328
docs(jobs): warn against forcing --client-id on a borrowed run
Oct 4, 2026
a2adb5d
fix(run): close borrowed client id guard gaps
christian-byrne Oct 4, 2026
83eda0b
docs(run): document every client_id_rejected reason and both sources
Oct 4, 2026
6ed449a
test(jobs): prove borrowed watch opens no socket
christian-byrne Oct 4, 2026
1b91e80
fix(jobs): poll borrowed watches without a socket
christian-byrne Oct 4, 2026
c157928
fix(jobs): preserve borrowed poll failures
christian-byrne Oct 4, 2026
ec887c3
fix(jobs): make borrowed client selection atomic
christian-byrne Oct 4, 2026
5dff0c6
fix(jobs): don't read a failed lookup as an unmarked client id
Glary-Bot Oct 6, 2026
a055232
fix(jobs): require the prompt's own record before honouring --client-id
Glary-Bot Oct 6, 2026
c68f4e4
fix(jobs): validate --client-id before the paths that drop it; true docs
Glary-Bot Oct 6, 2026
41ed58b
fix(jobs): key the borrowed check on the id, not only on the prompt
Glary-Bot Oct 6, 2026
9cb5d8b
fix(jobs): a census nobody ran is not a census of nobody
Glary-Bot Oct 6, 2026
b5dd436
fix(jobs): say which refusal this is, and what dropping the flag does
Glary-Bot Oct 7, 2026
0ce680e
test: avoid public hygiene false positive on client-id carrier (#979)
christian-byrne Oct 7, 2026
b2c8d5c
docs(json-output): execution_error and poll_reason on a watch envelope
Glary-Bot Oct 7, 2026
9adbb5b
fix(jobs): URL-encode prompt_id in every /history/{prompt_id} request
christian-byrne Oct 6, 2026
232ddcc
fix(run): harden the borrowed-client-id guard against edge cases
christian-byrne Oct 7, 2026
c1169ac
style: restore blank line before _SubmittedRecord (ruff format)
christian-byrne Oct 7, 2026
f20872f
Merge main into client-id carrier
christian-byrne Oct 9, 2026
198a700
Merge remote-tracking branch 'origin/main' into glary/run-share-clien…
Glary-Bot Oct 14, 2026
722446f
fix(jobs): promote a live watch's execution_error event to execution_…
Glary-Bot Oct 14, 2026
5288907
Merge remote-tracking branch 'origin/main' into glary/run-share-clien…
christian-byrne Oct 20, 2026
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
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,29 @@ history.
validate said the graph was valid.
- `comfy workflow validate --full-options` and `comfy nodes show --all-choices`
(also on `nodes search --expand-top`) list a long option list in full.
- `comfy run --client-id <id>` submits as an already-connected WebSocket client
instead of a freshly minted one, so ComfyUI addresses the run's execution
events — node highlights, progress, outputs — to that client. This is what
lets a tool run a workflow on a user's behalf and still have the user's open
canvas light up. Local only, and refused with `client_id_rejected` alongside
`--wait` or an effective `cloud` target (the flag, `COMFY_WHERE`, a project
`comfy.yaml` default, a saved default, or auto-detection from configured
cloud credentials): ComfyUI evicts whatever socket already holds an
incoming clientId, so this invocation must not attach as the borrowed one.
Such a run is marked both in its job state file and in the submitted
`extra_data`, so `comfy jobs watch` declines to re-attach to it — from any
machine, including one that cannot see the submitter's state file — and
polls status instead. An override is honoured only once one of those two
sources has positively vouched for the run: with no readable state file and
no readable server record for the prompt, `--client-id` is refused with
`reason: "indeterminate"` rather than risking the eviction, and the watch
polls. The refusal is keyed on the id too, so naming an id another queued run
borrowed is refused even when the prompt being watched is ordinary — and
refused as `indeterminate` when the `/queue` read that would show those ids
did not come back whole, rather than read as nobody borrowing. A blank
`--client-id` is refused (`reason: "empty"`) as it is on `comfy run`. The
watch envelope carries `poll_reason` (`borrowed`, `indeterminate` or null) so
a `--json` consumer can tell why no socket was opened.

### Changed

Expand Down Expand Up @@ -135,6 +158,18 @@ history.

### Fixed

- `comfy jobs watch` no longer dies with a traceback when the ComfyUI server
accepts a connection and hangs up before answering (a restarting server, a
reset connection). That surfaced as a raw `http.client.RemoteDisconnected`,
which `urllib` does not wrap, past the `RuntimeError` net every caller in
`jobs` catches.
- A failed `comfy jobs watch` carries the node's failure under
`error.details.execution_error` on both of its exits — instead of
`error.details.details.error` on the live path, and instead of
`error.details.error` on the one that short-circuits because the prompt had
already finished. Neither old key is published any more. The second exit was
also skipping the trimming and redaction that key drives, so it emitted the
full traceback and a `current_inputs` that can hold an api_key.
- `comfy workflow validate` (and `comfy run`'s pre-submit check) accepts a
literal the server converts before checking it: an integer string on an INT
input (`"1536"`), a numeric string on a FLOAT input, a number on a STRING
Expand Down
43 changes: 43 additions & 0 deletions comfy_cli/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -2002,6 +2002,20 @@ def run(
),
),
] = False,
client_id: Annotated[
str | None,
typer.Option(
"--client-id",
show_default=False,
help=(
"Local-only: submit as this already-connected WebSocket clientId instead of a fresh "
"one. ComfyUI sends a prompt's execution events only to the socket that submitted it, "
"so passing a browser tab's clientId is what makes that tab show node highlights, "
"progress and outputs for a run it did not start. This invocation then cannot watch "
"the run itself, so it cannot be combined with --wait."
),
),
] = None,
):
# Snapshot kwargs before the body mutates api_key/host/port — analytics should record what user actually supplied.
_track_props = tracking.filter_command_kwargs(dict(locals()))
Expand Down Expand Up @@ -2047,6 +2061,34 @@ def run(
# `where` kwarg is None then). Rides on the execution_success/_error events.
_track_props["target"] = "cloud" if decision.target is where_module.WhereTarget.CLOUD else "local"

if client_id is not None and not client_id.strip():
renderer.error(
code="client_id_rejected",
message="--client-id must not be empty",
hint="pass the connected browser client's id, or omit --client-id",
details={"reason": "empty"},
)
raise typer.Exit(code=1)
client_id = client_id.strip() if client_id is not None else None
if client_id:
rejection = None
if decision.target is where_module.WhereTarget.CLOUD:
rejection = ("cloud", "--client-id addresses a local ComfyUI socket and has no meaning on cloud")
elif wait:
rejection = (
"wait",
"--client-id hands this run's events to another client, so --wait would watch a silent socket",
)
if rejection is not None:
reason, message = rejection
renderer.error(
code="client_id_rejected",
message=message,
hint="drop --client-id, or drop the conflicting flag",
details={"reason": reason},
)
raise typer.Exit(code=1)

# Default for --notify: on when a human is at the terminal, off for
# agents (they shouldn't get surprise side-channel processes they didn't
# ask for). The user can override either way with --notify/--no-notify.
Expand Down Expand Up @@ -2144,6 +2186,7 @@ def run(
print_prompt=print_prompt,
preloaded=preloaded,
allow_spend=allow_spend,
client_id=client_id,
)
except typer.Exit as e:
if (e.exit_code or 0) == 0:
Expand Down
Loading
Loading