Skip to content

feat(workflow): add comfy workflow notes — read Note/MarkdownNote text offline (BE-4776) - #611

Merged
bigcat88 merged 3 commits into
mainfrom
matt/be-4776-workflow-notes
Jul 29, 2026
Merged

feat(workflow): add comfy workflow notes — read Note/MarkdownNote text offline (BE-4776)#611
bigcat88 merged 3 commits into
mainfrom
matt/be-4776-workflow-notes

Conversation

@mattmillerai

Copy link
Copy Markdown
Collaborator

ELI-5

ComfyUI template authors leave sticky notes on the canvas — "use ohwx man to trigger this LoRA", "download the model from here", "steps=30 for finals". Until now the only way for an agent (or a person) to read those was to cat the workflow JSON and eyeball it. This adds comfy workflow notes <file>, which just prints them.

What

A new read-only subcommand: comfy workflow notes <file>. It pulls the Note / MarkdownNote nodes out of a frontend-format workflow and emits {workflow, count, notes[]}, where each note carries {id, type, title, text, pos, size, subgraph}. It scans the top-level graph and every definitions.subgraphs[] definition (subgraph notes get a subgraph: {id, name} ref; top-level ones get null).

Origin: the QA report row "Read a template's LoRA 'trigger words' note" (#pillar5-mcp, 2026-07-27) — no CLI/MCP surface exposed note text.

Why a separate verb instead of extending slots

slots needs an object_info graph (_get_graph — a live server or an --input dump) because slots derive from node schemas. Notes are UI-only virtual nodes with no schema at all, so reading them is pure JSON parsing. Keeping them separate means notes works fully offline, and slots payloads stay free of multi-KB markdown blobs.

Verified

ruff check / ruff format --check clean repo-wide (with the CI-pinned ruff==0.15.15), and the full pytest suite is green: 2798 passed, 37 skipped. 24 new tests in tests/comfy_cli/command/test_workflow_notes.py cover the payload shape, subgraph notes, the six malformed-widgets_values shapes, the three envelope error codes, pretty-mode rendering, and schema conformance.

Live smoke against a real in-repo template fixture (comfy_cli/bench/fixtures/txt2img_seed.json, no ComfyUI running, no object_info cache):

$ comfy workflow notes comfy_cli/bench/fixtures/txt2img_seed.json
Note: Prompt #15
**Prompts usually have two types:**
...

comfy --json workflow notes on that file returns count: 4 with all four MarkdownNote bodies intact.

Two things I added beyond the ticket plan, and why

1. Schema registration. The plan didn't mention it, but the repo's own test_every_emitted_command_registers_a_schema fails on any renderer.emit(command=...) that isn't in COMMAND_SCHEMAS. So comfy workflow notes is registered (→ workflow), and I documented the notes[] shape in comfy_cli/schemas/workflow.json. Both additions are additive — no required / additionalProperties constraints changed, so existing slots / set-slot / vary payload validation is untouched.

pos and size are deliberately left type-unconstrained in the schema (description only): we pass them through verbatim, and litegraph has serialized pos as both [x, y] and {"0": x, "1": y} across versions. Constraining to array would have made a real-world template fail its own schema.

2. Pretty-mode output hardening. The plan's snippet did rprint(n["text"]) directly. Note text is untrusted file content, and this module already has a _strip_terminal_controls helper for exactly that reason (used on /userdata responses). A note containing a literal [/] would raise MarkupError and crash the command; one containing \x1b[… could drive the terminal. So every interpolated field goes through a new _safe_console_text() (strip control chars → rich.markup.escape). Verified: a note whose body is \x1b[31mRED\x1b[0m and [bold]markup[/bold] and [/] renders literally, with no ESC bytes reaching the terminal. JSON mode is unaffected — it emits the raw text.

Negative-claim falsification (API-format rejection)

This diff reuses _load_workflow_or_fail, so an API-format file gets workflow_not_frontend_format and exit 1, and I added a test asserting that dead-end. Per the falsification rule I went and checked the premise empirically rather than trusting it:

>>> from comfy_cli.workflow_to_api import convert_ui_to_api
>>> convert_ui_to_api(wf_with_3_notes, object_info)
{'6': {'inputs': {'seed': 42}, 'class_type': 'KSampler', '_meta': {'title': 'KSampler'}}}

The conversion output contains none of 'ohwx', 'Note', 'MarkdownNote', 'inner doc', 'Trigger words', or 'steps=30' — every note node and all note text is dropped (workflow_to_api._UI_ONLY_NODE_TYPES includes both note types, and there is no _meta/extra carve-out that preserves them). I also grepped for any other comfy-cli surface that reads note text from an API file: none exists. So there is genuinely nothing to read from an API export — the rejection is correct, not a wrongly-shipped dead-end, and the user is already pointed at File > Save (As) by the existing hint.

Scope guards honored

Only Note and MarkdownNote are surfaced — PrimitiveNode / GetNode / SetNode / Reroute and groups[] titles are explicitly excluded (both covered by tests). The command never writes the file (also tested), and never touches object_info or the network: no _get_graph, no --host/--port/--input.

Unmet criteria

None.

…ext offline (BE-4776)

Extracts documentation-note content from a frontend-format workflow so agents
can read a template's authored notes (LoRA trigger words, model links, usage
caveats) without cat/grep-ing raw JSON.

A separate verb rather than an extension of `slots`: notes are UI-only virtual
nodes with no schema, so extraction is pure JSON parsing — no object_info, no
running server — and `slots` payloads stay free of multi-KB markdown blobs.

Scans the top-level graph plus every `definitions.subgraphs[]` entry, emitting
{id, type, title, text, pos, size, subgraph} per note. Reuses
`_load_workflow_or_fail` for the missing-file / bad-JSON / API-format envelope
errors; API-format rejection is correct because the conversion strips note
nodes entirely.

Registers the command in COMMAND_SCHEMAS (the repo's discovery test enforces
this) and documents the `notes[]` shape in schemas/workflow.json. Pretty-mode
output routes note text/titles through _safe_console_text() so untrusted file
content can't emit ANSI escapes or rich markup, matching the existing
_strip_terminal_controls convention in this module.
@mattmillerai mattmillerai added agent-coded PR authored by the agent-work loop cursor-review Request Cursor bot review labels Jul 27, 2026
@mattmillerai
mattmillerai marked this pull request as ready for review July 27, 2026 23:37
@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Jul 27, 2026
@coderabbitai

coderabbitai Bot commented Jul 27, 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: 46 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: 8e7cf983-3d84-4c9f-8d63-efe45d5b5229

📥 Commits

Reviewing files that changed from the base of the PR and between 366ec3f and f9d60d5.

📒 Files selected for processing (2)
  • comfy_cli/command/workflow.py
  • tests/comfy_cli/command/test_workflow_notes.py
📝 Walkthrough

Walkthrough

The CLI adds comfy workflow notes for offline extraction of frontend workflow notes, including subgraph notes. It registers and validates the output schema, handles malformed input, and sanitizes pretty-rendered workflow content against terminal and markup controls.

Changes

Workflow Notes

Layer / File(s) Summary
Notes command contract
comfy_cli/command/workflow.py, comfy_cli/discovery.py, comfy_cli/schemas/workflow.json
Documents the new command, registers its workflow schema, and defines the notes payload containing note metadata, text, positions, sizes, and subgraph ownership.
Workflow note extraction
comfy_cli/command/workflow.py, tests/comfy_cli/command/test_workflow_notes.py
Extracts Note and MarkdownNote nodes from top-level and subgraph node lists, validates frontend input, coerces untrusted fields, and handles malformed structures and file errors.
Terminal-safe note rendering
comfy_cli/command/workflow.py, tests/comfy_cli/command/test_workflow_notes.py
Removes terminal-spoofing characters and carriage returns, escapes Rich markup, and verifies safe pretty output for titled, untitled, and nested notes.

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant WorkflowFile
  participant NoteExtractor
  participant Renderer
  CLI->>WorkflowFile: read frontend workflow JSON
  WorkflowFile-->>CLI: parsed workflow
  CLI->>NoteExtractor: collect top-level and subgraph notes
  NoteExtractor-->>CLI: note payload
  CLI->>Renderer: sanitize and render pretty output
  Renderer-->>CLI: safe console output
Loading
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch matt/be-4776-workflow-notes
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch matt/be-4776-workflow-notes

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

@dosubot dosubot Bot added the enhancement New feature or request label Jul 27, 2026

@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 6 finding(s).

Severity Count
🟡 Medium 2
🟢 Low 3
⚪ 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/command/workflow.py Outdated
Comment thread comfy_cli/command/workflow.py Outdated
Comment thread comfy_cli/command/workflow.py Outdated
Comment thread comfy_cli/command/workflow.py
Comment thread comfy_cli/command/workflow.py Outdated
Comment thread comfy_cli/command/workflow.py Outdated
…776)

Addresses the six cursor-review panel findings on PR #611.

Malformed-but-parseable input must degrade to an envelope, never a crash.
`_is_frontend_format` only vouches for the top-level `nodes` list, so
`definitions.subgraphs`, a subgraph's own `nodes`, and each node's `type`
are all attacker-shaped. Previously `for x in v or []` over a truthy
non-iterable (`"subgraphs": 1`) raised an uncaught TypeError, and an
unhashable `type` (list/dict) blew up the frozenset membership test.
Each container is now isinstance-checked before it is walked.

`notes[].title` and `notes[].subgraph.name` are declared `string|null` in
schemas/workflow.json but were emitted verbatim, so a non-string value
produced a payload failing the command's own schema. Both now coerce via
`_str_or_none`.

`_strip_terminal_controls` promised to block output spoofing but kept
`\r` and every codepoint >= U+00A0 — so a lone carriage return could
overwrite already-printed text, and bidi overrides/isolates could reorder
it Trojan-Source style. It now drops `\r` (lossless for CRLF, which keeps
its `\n`) plus the invisible bidi/zero-width codepoints. This is a pretty
-console path only; the `--json` envelope still carries note text exactly
as authored.

Pretty mode no longer renders a literal `#None` / `(subgraph None)` when
a note has no id or a subgraph def has neither name nor id.
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:M This PR changes 30-99 lines, ignoring generated files. labels Jul 27, 2026

@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/command/workflow.py`:
- Around line 594-623: Update _strip_terminal_controls to use
unicodedata.category(ch) and reject all Unicode format characters (category
“Cf”), while explicitly excluding U+2028 and U+2029 as line and paragraph
separators. Remove the now-redundant _SPOOFING_CODEPOINTS filtering and add the
required unicodedata import, preserving the existing tab/newline and
printable-character handling.

In `@tests/comfy_cli/command/test_workflow_notes.py`:
- Around line 319-324: Add an assertion in test_rejects_invalid_json that the
invocation exit_code equals 1, matching the sibling workflow-notes tests while
preserving the existing envelope and error-code assertions.
🪄 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: 249b7a05-8785-46bc-b8b8-36be3febd524

📥 Commits

Reviewing files that changed from the base of the PR and between 6c88dd7 and 366ec3f.

📒 Files selected for processing (4)
  • comfy_cli/command/workflow.py
  • comfy_cli/discovery.py
  • comfy_cli/schemas/workflow.json
  • tests/comfy_cli/command/test_workflow_notes.py

Comment thread comfy_cli/command/workflow.py Outdated
Comment thread tests/comfy_cli/command/test_workflow_notes.py
CodeRabbit review follow-ups on #611.

Replace the hand-rolled _SPOOFING_CODEPOINTS set with a category-based
filter over Cf/Zl/Zp. Verified against unicodedata 16.0.0 that the new
rule is a strict superset of the old set (nothing previously stripped is
now kept), and that it additionally catches U+061C (Arabic Letter Mark),
U+2028/U+2029 line+paragraph separators, the invisible math operators,
and the U+E0020-U+E007F tag block -- 155 codepoints in all.

The category lookup is gated behind the existing ord(ch) >= 0xA0 branch,
so ASCII still settles on the range check alone and large `workflow get`
payloads (the helper's other call site) don't pay a per-char lookup.

Both call sites are pretty-renderer only: JSON mode emits note text
verbatim, confirmed empirically, so the broader sweep costs no fidelity
for machine consumers -- it only stops invisible characters from lying
about what the text says on a terminal.

Tests: 5 new spoofing params for the newly-covered codepoints, a guard
that visible non-ASCII (Zs/Pd/Mn) survives the sweep, and the missing
exit_code == 1 assertion on test_rejects_invalid_json.

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

@bigcat88 bigcat88 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approving.

CI failure is not yours. test_model / test_run fail on model download ... failed after 3 attempts: the server returned HTTP 429 Too Many Requests — HuggingFace rate-limiting the e2e checkpoint fetch. Same failure is showing on #609, #596 and #574 right now, and main is green. Nothing to fix; a re-run when HF calms down should clear it.

Feature works, verified against a real in-repo template. comfy workflow notes comfy_cli/bench/fixtures/txt2img_seed.json returns count: 4 with all four MarkdownNote bodies intact — including the one with fenced code and box-drawing characters — and note keys are exactly {id, type, title, text, pos, size, subgraph}.

Subgraph traversal is right. Hand-built a workflow with one top-level Note and one MarkdownNote inside definitions.subgraphs[]: both come back, the inner one carrying subgraph: {"id": "sg-abc", "name": "My Subgraph"} and the top-level one null. That's the shape you documented.

The pretty-mode hardening you added beyond the plan is the right call, and it holds. I fed it a note whose body is <ESC>[2J<ESC>]0;PWNED<BEL><ESC>[31mRED<ESC>[0m and [bold]markup[/bold] and [/]:

raw ESC bytes: 0 | CSI-2J: False | MarkupError: False
evil #1
[2J]0;PWNED[31mRED[0m and [bold]markup[/bold] and [/]

Renders literally, no screen clear, no crash on the unbalanced [/]. And JSON mode still carries the raw bytes (raw ESC preserved: True, [/] preserved) — which is the correct split: strip at the terminal, never mutate what an agent parses. Worth saying plainly: template JSON is untrusted input that arrives over the network, so rprint(n["text"]) as the plan had it would have been a real defect, not a hypothetical one.

API-format rejection verified, and the premise behind it too. comfy workflow notes on an API-format file returns ok: false / workflow_not_frontend_format. I confirmed the reason independently: workflow_to_api._UI_ONLY_NODE_TYPES is {"Note", "MarkdownNote", "PrimitiveNode", "GetNode", "SetNode", "Reroute"} and the converter continues past all of them at line 158, so note text genuinely does not survive conversion. The dead-end is correct rather than an oversight.

Design call — separate verb over extending slots — is right. slots needs an object_info graph because slots derive from node schemas; notes are UI-only virtual nodes with no schema, so the parse is pure JSON. Folding them together would have made an offline-capable read require a live server, and pushed multi-KB markdown into every slots payload. Keeping them apart is the better factoring.

Leaving pos / size type-unconstrained in the schema is also correct and worth the comment you gave it — litegraph really has serialized pos as both [x, y] and {"0": x, "1": y}, so an array constraint would fail real templates.

Full suite: 2900 passed, 7 skipped, 1 failed — test_non_fast_deps_uses_global_python, the sandbox artifact that fails on plain main here too. ruff check clean.

One forward-looking note, not blocking: the _safe_console_text() treatment you applied here is exactly what command/run/execution.py:203 is missing — I've flagged that on #614, where a hostile server body still reaches the terminal with live escape sequences. Same pattern, same fix; worth reusing this helper's approach there.

@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label Jul 28, 2026
@bigcat88

Copy link
Copy Markdown
Contributor

Re-verified against current main (e6965d0, 10 commits newer than when I approved). Still good — merged locally, 3030 passed, and comfy workflow notes still returns count: 4 with all bodies intact on the real template. Approval stands.

Two failures in that run, neither yours:

Your red CI check is still just the HuggingFace HTTP 429 on the e2e model download, not a real failure.

@bigcat88
bigcat88 merged commit 11e75de into main Jul 29, 2026
19 of 20 checks passed
@bigcat88
bigcat88 deleted the matt/be-4776-workflow-notes branch July 29, 2026 10:39
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 29, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants