refactor(output): route remaining raw rich.print imports through the JSON-mode-aware rprint shim#523
refactor(output): route remaining raw rich.print imports through the JSON-mode-aware rprint shim#523mattmillerai wants to merge 3 commits into
Conversation
…JSON-mode-aware rprint shim Eight production modules still imported `from rich import print`, bypassing `comfy_cli.output.rprint`. The shim is byte-identical to rich.print in pretty mode and redirects stdout->stderr in --json/agent mode so the NDJSON envelope on stdout stays parseable. Swap each remaining site per the migration policy documented in comfy_cli/output/__init__.py; cm_cli_util.py imports as `print` (shadowing the builtin) to match the models.py precedent. Function-local imports in uv.py and output/preview.py stay function-local.
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 29 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThe changes replace direct ChangesContext-aware output import migration
Suggested reviewers: 🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Comment |
There was a problem hiding this comment.
🔍 Cursor Review — Consolidated panel
Triggered by @mattmillerai.
✅ No high-signal findings.
Panel: 6/8 reviewers contributed findings.
Reviewers that did not contribute: kimi-k2.5:adversarial (empty), kimi-k2.5:edge-case (empty)
… shim
The shim sends human text to stderr whenever the renderer is in JSON mode,
and Renderer.resolve() auto-selects JSON mode when stdout isn't a TTY. That
is only correct for call sites whose text is a *log*, because JSON mode
reserves stdout for the envelope emitted via renderer.emit().
`generate` and `pr` never emit an envelope on their main paths -- `generate`
has its own local --json flag (_emit_result -> output.print_json) and `pr list`
renders through a plain module-level Console. Migrating them therefore moved
their primary result off stdout and put nothing back. Verified on the branch
before this commit (CliRunner, non-TTY):
comfy generate flux-pro --prompt x --async
exit=0 stdout='' stderr='Submitted: flux-pro\n job id: job-xyz\n ...'
stdout was empty, so `comfy generate ... > out.txt` wrote an empty file. This
was the root cause of the 25 failing tests in tests/comfy_cli/command/generate/.
Revert those three modules to `from rich import print`, each with a comment
explaining why, so the mechanical migration isn't simply redone. The five
log/hint modules (cm_cli_util, preview, pr_cache, resolve_python, uv) stay on
the shim -- stderr in JSON mode is exactly right for them.
Giving `generate`/`pr` real envelopes is tracked as a follow-up; it needs a
call on how `generate`'s existing --json reconciles with the envelope contract.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI triage + scope correctionNo unresolved review threads (CodeRabbit ✅, Cursor panel: no high-signal findings). CI was red for two unrelated reasons; only one was this PR's. 1. 25 failing
|
CI result after e365aa3
The 9 that remain are exactly the pre-existing |
|
Updated the PR description to match the final state of the branch — it still described all 8 modules as migrated, which stopped being true in e365aa3. Final state: 5 log/hint modules migrated to the shim, 3 result-producing modules intentionally left on raw Re-verified this pass: full suite 2573 passed, 37 skipped; Both red checks are pre-existing and not from this PR — @coderabbitai review |
|
✏️ Learnings added
✅ Action performedReview finished.
|
The Phase 1 policy said "Every existing `from rich import print as rprint` becomes `from comfy_cli.output import rprint`", which is the instruction that motivated migrating generate/pr in the first place and would motivate redoing it later. `Renderer.resolve()` picks JSON mode whenever stdout is not a TTY, so migrating a call site that prints a command's primary result routes that result to stderr and leaves stdout empty. Scope the rule to log/hint output, state the result-path carve-out, and name the three intentionally un-migrated modules so the in-file comments have a policy to point back to. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Independent re-verification + one docstring fix (73bd066)Re-checked this branch from a clean worktree rather than taking the earlier triage on faith. Both red checks reproduce on
Full suite here: 2564 passed, 37 skipped, 9 failed (exactly the tomlkit set). The non-migration decision holds up empirically. One thing I did change. The Phase 1 policy in |
|
Independent re-verification pass (clean worktree). No code changes — nothing actionable found.
Both red checks re-confirmed as not this PR's, by direct comparison rather than inspection:
New this pass — the unblock path is verified, not assumed: I checked out #533 and ran those same files in the same tomlkit-0.15.1 venv: 87 passed, 0 failed. So #533 genuinely clears all 9, and #523 goes green the moment #533 merges. Suggested ordering: land #533 first, then this. Also re-checked the riskiest edit adversarially — Follow-ups already tracked: the tomlkit fix is #533; the |
ELI-5
comfy-cli speaks to two audiences: humans (pretty Rich-formatted text) and agents (a machine-readable JSON/NDJSON envelope on stdout). To keep those apart there's a small shim —
comfy_cli.output.rprint— that behaves exactly likerich.printfor humans but, in--json/agent mode, sends the pretty output to stderr so stdout stays a clean, parseable envelope.Eight modules still imported the raw
from rich import print. This PR migrates the five whose output is a log/hint (stderr in JSON mode is exactly right for those), and deliberately leaves the three result-producing modules alone — migrating those would move a command's primary result off stdout with nothing put back. See "Why three modules are intentionally not migrated" below.What changed
Per the migration policy documented in
comfy_cli/output/__init__.py:Migrated (5) — log/hint output
comfy_cli/resolve_python.pyfrom comfy_cli.output import rprintcomfy_cli/pr_cache.pyfrom comfy_cli.output import rprintcomfy_cli/uv.pyfrom comfy_cli.output import rprintcomfy_cli/command/custom_nodes/cm_cli_util.pyfrom comfy_cli.output import rprint as print(shadows builtin, matchesmodels.pyprecedent)comfy_cli/output/preview.pyfrom comfy_cli.output import rprintIntentionally NOT migrated (3) — result-producing
comfy_cli/command/generate/app.pygenerate's primary result (job ids, image URLs); has its own--jsoncomfy_cli/command/generate/output.pyprint_urls/print_savedaregenerate's primary resultcomfy_cli/command/pr_command.pypr listrenders via a module-levelConsole; migrating only therprintcalls would split one command across two streamsEach carries an in-file comment explaining why, so the mechanical migration isn't blindly redone later.
Why three modules are intentionally not migrated
The shim routes to stderr whenever the renderer is in JSON mode, and
Renderer.resolve()auto-selects JSON mode when stdout isn't a TTY (renderer.py, precedence rule 6). That's correct only for call sites whose text is a log — JSON mode reserves stdout for the envelope fromrenderer.emit().generateandprnever emit an envelope on their main paths. Migrating them moved their primary result to stderr and put nothing back on stdout. Verified on the branch before the fix (CliRunner, non-TTY):stdout was completely empty —
comfy generate ... > out.txtwould have written an empty file. 25 tests caught this; commit e365aa3 reverts those three modules. They should be migrated together withrenderer.emit(...)envelope support forgenerate/pr, not before.Why it's safe
Renderer(stdout) and calls the realrich.print.get_renderer()has a safe default (a prettyRendererwhen none is installed), so the two top-level modules (resolve_python.py,pr_cache.py) that import the shim at module top can't blow up on early/ad-hoc import.cm_cli_util.pycallsprint(..., file=sys.stderr)in several places;Renderer.printusessetdefault("file", ...), so an explicitfile=is preserved and those calls are unchanged, while file-less calls now correctly route to stderr in JSON mode (the intended improvement).Testing
ruff check+ruff format --checkclean on all touched files.generate's primary result confirmed back on stdout on a non-TTY.CI note — the two red checks are NOT from this PR
build— 9 failures intest_config_parser.py(8) +test_node_init.py(1):ValueError: Comment cannot contain line breaks.tomlkitis unpinned (pyproject.toml:49); CI resolves 0.15.1, which rejects line breaks inItem.comment(), andconfig_parser.py:75passes a multi-line comment. Already being fixed by fix(registry): emit pyproject hint blocks as single-line tomlkit comments #533. This PR touches nothing nearconfig_parser.py.test(Windows Specific Commands) — fails in Install Dependencies, before any of this code runs:failed to remove file ...pydantic_core/_pydantic_core.cp312-win_amd64.pyd: Access is denied (os error 5), leaving a corrupted venv →ImportError: cannot import name '__version__' from 'pydantic_core'. Reproduces on every recent branch (4/4 checked) and survived a re-run — a repo-wide Windows workflow break, not this PR.