Skip to content

feat: emit envelope/1 from launch --background and stop under --json#510

Open
mattmillerai wants to merge 3 commits into
mainfrom
matt/be-2955-launch-stop-json
Open

feat: emit envelope/1 from launch --background and stop under --json#510
mattmillerai wants to merge 3 commits into
mainfrom
matt/be-2955-launch-stop-json

Conversation

@mattmillerai

Copy link
Copy Markdown
Collaborator

ELI-5

When you run a comfy command with --json, you're telling it "give me a machine-readable answer, not pretty text for a human." Every command honors that by printing one envelope/1 JSON object — except launch and stop, which quietly ignored --json and printed human text with a success exit code and no JSON. A program watching for the envelope (the local MCP, CI) couldn't tell a successful launch/stop from a broken response, so it treated success as failure and retried actions that shouldn't be retried. This PR makes launch --background and stop emit the envelope like everyone else.

Fixes #509 (the root cause behind half of the "stop/launch report failure while the action succeeded" bug).

What changed

  • comfy --json stop now emits {ok, command: "stop", data: {host, port, stopped}}.
  • comfy --json launch --background now emits {ok, command: "launch", data: {host, port, pid, background}} at the success marker (right before os._exit, using the renderer's flushed write so the line survives the hard exit).
  • The in-band error paths of both commands (nothing to stop, already running, bad --port, port in use, startup failed, no workspace) also emit a schema-valid error envelope in JSON mode instead of exiting with no document.
  • Ships comfy_cli/schemas/stop.json and launch.json, registers comfy stop / comfy launch in COMMAND_SCHEMAS (so comfy discover advertises the shapes), and adds the corresponding lifecycle error codes to the registry.

Pretty (non---json) output is unchanged — every new emit is JSON-mode-only (renderer.emit is a no-op in pretty mode; error envelopes are guarded by is_json(), keeping the original rprint red text).

Scope notes / judgment calls

  • Foreground comfy launch is intentionally not touched. It blocks on the ComfyUI process and exits with the child's return code — there is no discrete "success" moment to emit an envelope at. Only --background, which returns control to the caller, gets one.
  • No capability is being denied by the new error codes — every error path they cover already existed as red text + a non-zero exit before this PR; this only gives those existing failures a structured envelope. The behavioral addition is that success paths now emit at all.
  • changed is set to stopped/True for the mutating success paths, matching the envelope's changed convention.

Tests

tests/comfy_cli/command/test_launch_stop_json.py:

  • Pins both success shapes and validates them against envelope.json + stop.json/launch.json.
  • The launch success test drives the real launch_and_monitor success branch (fake child writes the "To see the GUI go to:" marker; os._exit patched) so the emit-before-exit path is exercised end-to-end.
  • Pins the error shapes (no_background, launch_failed, background_already_running, invalid_port).
  • Asserts pretty mode writes no envelope.

Full suite green (2583 passed, 37 skipped); ruff format/check clean. The existing registration/error-code ratchet tests pass with the new commands + codes registered.

`comfy --json launch --background` and `comfy --json stop` accepted the
global `--json` flag but ignored it: they printed human text with exit 0
and never emitted an envelope/1 document, unlike every other wrapped
command. Programmatic callers that require the envelope (the local MCP
`_run_comfy`, CI scripts) then read a successful lifecycle action as a
malformed/failed response and retry non-idempotent operations.

Emit the standard envelope in JSON mode from both commands:
- `stop`  -> data {host, port, stopped}
- `launch --background` -> data {host, port, pid, background} (emitted at
  the success marker, right before os._exit so the flushed line survives)

Both success and the in-band error paths now emit a schema-valid envelope
in JSON mode; pretty (non-JSON) output is unchanged. Ships stop.json /
launch.json data schemas, registers both commands in COMMAND_SCHEMAS, and
adds six lifecycle error codes. Tests pin both success shapes, the error
shapes, schema validity, and that pretty mode emits no envelope.

Foreground `comfy launch` is intentionally excluded: it blocks on the
server and exits with ComfyUI's return code, so there is no success moment
to emit.

Closes #509
@mattmillerai mattmillerai added agent-coded PR authored by the agent-work loop cursor-review Request Cursor bot review labels Jul 14, 2026
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 39 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6824dedb-63a9-4fb4-9e56-e5734a864ffb

📥 Commits

Reviewing files that changed from the base of the PR and between 2cddfdf and 1259dbf.

📒 Files selected for processing (5)
  • comfy_cli/cmdline.py
  • comfy_cli/command/launch.py
  • comfy_cli/error_codes.py
  • comfy_cli/schemas/stop.json
  • tests/comfy_cli/command/test_launch_stop_json.py
📝 Walkthrough

Walkthrough

Lifecycle commands now honor JSON rendering. comfy launch --background and comfy stop emit structured success and error envelopes, register launch/stop schemas for discovery, and add schema-validation tests covering JSON and pretty output modes.

Changes

Lifecycle JSON output

Layer / File(s) Summary
Schemas and lifecycle error contracts
comfy_cli/error_codes.py, comfy_cli/schemas/*.json, comfy_cli/discovery.py
Adds launch/stop payload schemas, lifecycle error codes, and command schema registrations.
Renderer-aware launch and stop flows
comfy_cli/command/launch.py, comfy_cli/cmdline.py
Routes launch and stop success and failure paths through JSON or pretty renderers while retaining background process handling.
Envelope and discovery validation
tests/comfy_cli/command/test_launch_stop_json.py
Validates success and error envelopes, schema conformance, pretty-mode output, monitoring behavior, and discovery registrations.

Sequence Diagram(s)

sequenceDiagram
  participant background_launch
  participant launch_and_monitor
  participant Renderer
  participant ComfyUI
  background_launch->>launch_and_monitor: start background process
  ComfyUI->>launch_and_monitor: write GUI success marker
  launch_and_monitor->>Renderer: emit launch payload
  Renderer-->>background_launch: return JSON envelope
  background_launch->>ComfyUI: exit after successful startup
Loading

Suggested reviewers: bigcat88

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR adds envelope/1 JSON output for launch --background and stop in JSON mode, matching issue #509 and including the requested structured data.
Out of Scope Changes check ✅ Passed The added schemas, error codes, command registration, and tests all support the lifecycle JSON-envelope objective and do not appear unrelated.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch matt/be-2955-launch-stop-json
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch matt/be-2955-launch-stop-json

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

@mattmillerai
mattmillerai marked this pull request as ready for review July 14, 2026 04:05
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. enhancement New feature or request labels Jul 14, 2026
@coderabbitai
coderabbitai Bot requested a review from bigcat88 July 14, 2026 04:06

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@comfy_cli/cmdline.py`:
- Around line 1130-1148: Prevent JSON stdout pollution at all three sites: in
comfy_cli/cmdline.py lines 1130-1148, gate both stop-status rprint calls behind
renderer.is_json(); in comfy_cli/command/launch.py lines 276-291, gate the Panel
and “Execution error” prints so JSON mode only uses renderer.error(); and in
comfy_cli/command/launch.py lines 394-420, gate the success banner with
get_renderer().is_json() or a shared renderer reference so only
get_renderer().emit() runs in JSON mode. Strengthen tests in
tests/comfy_cli/command/test_launch_stop_json.py to assert JSON responses
produce exactly one stdout line.

In `@tests/comfy_cli/command/test_launch_stop_json.py`:
- Around line 51-61: The _validator_for function still constructs the deprecated
jsonschema.RefResolver; replace it with a referencing.Registry populated with
the discovered schema resources and pass that registry to Draft202012Validator.
Preserve resolution for both each schema’s $id and its filename while removing
the RefResolver usage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6ad785ac-9891-4f42-87ec-9749cbd837d7

📥 Commits

Reviewing files that changed from the base of the PR and between a732f5c and 2cddfdf.

📒 Files selected for processing (7)
  • comfy_cli/cmdline.py
  • comfy_cli/command/launch.py
  • comfy_cli/discovery.py
  • comfy_cli/error_codes.py
  • comfy_cli/schemas/launch.json
  • comfy_cli/schemas/stop.json
  • tests/comfy_cli/command/test_launch_stop_json.py

Comment thread comfy_cli/cmdline.py
Comment thread tests/comfy_cli/command/test_launch_stop_json.py Outdated

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Cursor Review — Consolidated panel

Triggered by @mattmillerai.

Found 5 finding(s).

Severity Count
🟠 High 1
🟡 Medium 1
🟢 Low 2
⚪ Nit 1

Panel: 6/8 reviewers contributed findings.

Reviewers that did not contribute: kimi-k2.5:adversarial (empty), kimi-k2.5:edge-case (empty)

Comment thread comfy_cli/cmdline.py
Comment thread comfy_cli/cmdline.py
Comment thread comfy_cli/command/launch.py
Comment thread comfy_cli/discovery.py
Comment thread comfy_cli/command/launch.py Outdated
mattmillerai and others added 2 commits July 13, 2026 21:17
…solver

Address CodeRabbit review on #510:

- Harden `_last_envelope` to assert JSON mode emits exactly one stdout line,
  so a future pretty-print leak into the machine channel fails loudly instead
  of hiding behind "grab the last line". (The gate-rprint code change CodeRabbit
  suggested is unnecessary: rprint/print already route to stderr in JSON mode,
  so stdout is not polluted — this assertion pins that contract.)
- Migrate the schema-validator helper from the deprecated jsonschema.RefResolver
  to referencing.Registry, resolving by both $id and filename.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ge; guard emit

Address the cursor-review panel findings on #510:

- stop (High/Medium): `remove_background()` no longer runs unconditionally. When
  `kill_all` fails AND the server is still running, keep the PID record and emit
  an `ok=false` `stop_failed` error with a non-zero exit, so a later stop/logs
  can still target it and callers see the failure. An already-gone process
  (kill_all False, not running) stays a success: clear the stale record, emit
  `stopped=false`/`changed=false`. Killed → `stopped=true`/`changed=true`.
- launch (Low): reject `--port` values outside 1-65535 with the clear
  `invalid_port` signal instead of a generic downstream launch failure.
- launch (Nit): guard the success `emit()` before `os._exit(0)` so a stdout
  write failure (e.g. BrokenPipeError) still reaches the clean exit.

Adds a `stop_failed` error code, updates stop.json's `stopped` description, and
extends the contract tests (genuine-failure vs already-gone stop, out-of-range
port).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mattmillerai

Copy link
Copy Markdown
Collaborator Author

Heads-up for the merger: the red Windows Specific Commands check is an environmental dependency-install failure, not a defect in this PR.

The Install Dependencies step fails inside comfy install --fast-depsuv pip install --requirement requirements.compiled with:

error: failed to remove file `...\venv\Lib\site-packages\pydantic_core\_pydantic_core.cp312-win_amd64.pyd`: Access is denied. (os error 5)
...
ImportError: cannot import name "__version__" from "pydantic_core"

This is the classic Windows locked-DLL problem: uv tries to reinstall pydantic_core while the running comfy process still has it loaded (cmdline → tracking → mixpanel → pydantic → pydantic_core), so the .pyd can't be removed and pydantic_core is left half-uninstalled.

Evidence it is not from this PR:

  • The diff touches only cmdline.py, command/launch.py, discovery.py, error_codes.py, schemas/*.json, and the test file — no import-path, uv.py, tracking.py, requirements, or pyproject changes.
  • requirements.compiled is fetched fresh from ComfyUI at CI time, so the pydantic bump that triggers the reinstall is identical for every PR run today.
  • This workflow passed on branches from 2026-07-10 and started failing repo-wide around 07-14; reproduced identically across 3 runs (deterministic, a rerun does not clear it).
  • It is not a required status check on main.

Local verification on this branch: tests/comfy_cli/command/test_launch_stop_json.py 14/14 pass (including under -W error::DeprecationWarning), ruff check clean. All 7 review threads are resolved. Ready to merge modulo this unrelated Windows infra flake.

@mattmillerai

Copy link
Copy Markdown
Collaborator Author

Update: I re-ran the failed Windows Specific Commands job (run 29305977176) and it reproduced the identical failure —

error: failed to remove file `...\venv\Lib\site-packages\pydantic_core\_pydantic_core.cp312-win_amd64.pyd`: Access is denied. (os error 5)

So this is a deterministic Windows runner file-lock, not a transient flake a re-run will clear. Root cause: comfy install --fast-deps spawns uv pip install to upgrade pydantic_core into the same venv the running comfy process uses — and comfy has pydantic_core loaded at startup (trackingmixpanel 5.2.0 → pydantic), so the .pyd is locked and uv cannot overwrite it (classic Windows locked-DLL / AV-scan behavior).

This is unrelated to this PR: the diff only touches the stop/launch command bodies, JSON schemas, and tests — nothing in dependencies, the install command, uv.py, or the module-level import chain. The real fix belongs in CI (install into a separate venv, or retry the removal in uv.py) and should be a separate change. All CodeRabbit and Cursor review threads on this PR are resolved.

@mattmillerai

Copy link
Copy Markdown
Collaborator Author

CI note: the red "Windows Specific Commands" check is a pre-existing uv-on-Windows infra flake, not caused by this diff.

It fails in the comfy install --fast-deps step — before any launch/stop code runs — with:

error: failed to remove file `...\venv\Lib\site-packages\pydantic_core/_pydantic_core.cp312-win_amd64.pyd`: Access is denied. (os error 5)

That is uv unable to replace pydantic_core.pyd while the running comfy process holds the DLL open (classic install-into-own-venv file lock on Windows). Evidence it is unrelated to this PR:

  • The failure is in install.py/uv.py, which this PR does not touch.
  • pydantic_core is eager-loaded via tracking on main too (cmdline.py:12), and the only production import this PR adds (comfy_cli.output in launch.py) was already in the import graph via 20+ other command modules — so the diff cannot change whether the .pyd is locked.
  • The pytest-based Run Tests on Multiple Platforms (windows-latest, 3.10) job passes; only the full-install job hits the lock.

All review threads are resolved and the rest of CI is green. Re-ran the flaky job 3× without it clearing (Windows runners appear consistently affected today). Fixing the underlying uv/Windows install lock is out of scope for this launch/stop-JSON PR — this is safe to merge once the flake clears on a re-run.

@mattmillerai

Copy link
Copy Markdown
Collaborator Author

🤖 The reviews loop filed Linear follow-up ticket(s) for review thread(s) deferred as out of scope for this PR:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-coded PR authored by the agent-work loop cursor-review Request Cursor bot review enhancement New feature or request size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

launch/stop ignore --json: no envelope output for lifecycle commands

1 participant