Skip to content

fix(registry): emit pyproject hint blocks as single-line tomlkit comments#533

Open
mattmillerai wants to merge 3 commits into
mainfrom
matt/be-3285-tomlkit-multiline-comment
Open

fix(registry): emit pyproject hint blocks as single-line tomlkit comments#533
mattmillerai wants to merge 3 commits into
mainfrom
matt/be-3285-tomlkit-multiline-comment

Conversation

@mattmillerai

@mattmillerai mattmillerai commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

ELI-5

comfy node init writes a pyproject.toml with two blocks of commented-out TOML in it, as hints you can uncomment (a requires-comfyui line, and a big classifiers block). We built those hints by handing tomlkit one long string with newlines in it. tomlkit 0.15 started rejecting that — a comment isn't allowed to contain line breaks — so the hint code now throws. We build the same hints one comment line at a time instead, which is what tomlkit always meant you to do.

Problem

registry/config_parser.py called .comment() with multi-line strings in two places:

  • create_comfynode_config — the requires-comfyui hint on [tool.comfy].includes
  • initialize_project_config — the ~15-line classifiers hint on [project].license

tomlkit is unpinned, and tomlkit 0.15.0+ rejects comments containing line breaks, so both calls raise ValueError: Comment cannot contain line breaks. These multi-line comments were always outside tomlkit's contract; 0.15 just started enforcing it.

This is not only a CI break. Any user who installs comfy-cli fresh today resolves tomlkit 0.15.1 and gets a hard failure from comfy node init / project-config initialization. It also fails 9 tests on the build job of every open PR, since CI installs with pip install -e . and resolves tomlkit fresh.

Reproduced on this branch's base: fresh uv pip install -e '.[dev]' resolves tomlkit 0.15.1, and pytest tests/comfy_cli/registry/test_config_parser.py gives 8 failed, 73 passed.

Fix

Emit each hint block as one tomlkit.comment() item per line, appended to the containing table — no embedded \n, so it is within tomlkit's contract on 0.13.x and 0.15.x. The two hint blocks move to module constants (_REQUIRES_COMFYUI_HINT, _CLASSIFIERS_HINT) with a shared _append_hint() helper.

No pin was added. The ticket proposed pinning tomlkit<0.15 as an immediate unblock and dropping the pin once the code was fixed. The code fix landed cleanly, so the pin would have been added and removed in the same PR — pyproject.toml is untouched. Note CI installs via pip install -e . and never reads uv.lock, so the code fix alone is what unblocks it.

Verification

  • tomlkit 0.15.1 (fresh resolution): full suite 2576 passed, 18 skipped, 0 failed (pytest tests/ -q --deselect tests/e2e).
  • tomlkit 0.13.3 (the lockfile's version): tests/comfy_cli/registry/test_config_parser.py84 passed. The fix is correct on both, which is why the pin is unnecessary.
  • ruff check . → all checks passed; ruff format --check . → 235 files already formatted.
  • Grepped repo-wide: these were the only two multi-line .comment() call sites. The one remaining tomlkit.comment(...) call was already single-line and is untouched.

Tests added

The generated hint blocks had no test coverage at all — the 8 failing tests only failed because the call raised, not because they assert on the hints. Three tests now cover the rendered output directly: hint content, placement relative to the field each hint documents, and that the classifiers hint parses as valid TOML once uncommented (its entire purpose).

Judgment call — rendered output is content-identical, whitespace differs

The ticket asked to keep the rendered output byte-identical, and said that if step 2 required changing the output, to do the pin alone and open a child ticket. The generated file is content-identical, but three whitespace-trivia bytes differ, so calling this out explicitly rather than silently:

-includes = []
+includes = []
-# "requires-comfyui" = ">=1.0.0"  # ComfyUI version compatibility
-
+# "requires-comfyui" = ">=1.0.0"  # ComfyUI version compatibility

-license = "MIT"
+license = "MIT"
+
 # classifiers = [
 ...
 # ]
-

Concretely: (1) the trailing space after includes = [] and license = "MIT" is gone — it was an artifact of the multi-line trivia hack; (2) the blank line that separated license from [project.urls] now sits above the classifiers hint rather than below it; (3) the trailing blank line after the requires-comfyui hint is gone.

I read the ticket's fallback as guarding against a change to the generated file's content — every hint line is byte-for-byte what it was, in the same place, and both the existing tests and the new ones are green. Dropping back to the pin alone would leave comfy node init broken for fresh installs, which seemed strictly worse than losing two trailing spaces. Happy to be overruled if byte-identity was meant literally.

Resolved (was disclosed as a caveat): blank hint lines previously rendered as # (trailing space) on tomlkit <0.15, because tomlkit.comment("") prefixes "# " unconditionally. Fixed in ee4ccdd by emitting bare # separators via Comment(Trivia(...)). Verified: the generated file now carries bare # separators on both 0.13.3 and 0.15.1, matching main byte-for-byte on those lines.

uv.lock is deliberately untouched: uv lock --upgrade-package tomlkit also pulls in ~280 lines resolving the bench extra that the lock on main is already stale on. That's unrelated churn and belongs in its own change.

Correction: an intermediate commit (ee4ccdd) inadvertently swept in exactly that uv.lock churn (+285/-2: anthropic, pydantic, pydantic-core, jiter, …), contradicting the paragraph above. Reverted in 623fa5fuv.lock is now byte-identical to main, and the PR diff is once again just config_parser.py plus its tests.

Self-review notes

  • Riskiest line: del project["urls"] before appending the hint, then re-adding it. Table.add() appends to the end, so appending the hint while urls was present would have rendered it after the [project.urls] sub-table instead of under license. It's safe because project here is always parsed back from create_comfynode_config's own output, where urls is the last key — so delete-and-re-append reproduces the identical key order, which the rendered-output diff above confirms. The if "urls" in project guard covers the absent case.
  • Capability-denial check: not applicable — this diff adds no deny/dead-end path or "not supported" branch; it restores a capability that 0.15 broke.

…ents

tomlkit 0.15.0 started enforcing that a comment cannot contain line breaks,
so the two multi-line `.comment()` calls in registry/config_parser.py now
raise `ValueError: Comment cannot contain line breaks`. tomlkit is unpinned,
so every fresh install resolves 0.15.x — this breaks `comfy node init` for
new users and fails 9 tests on every PR's CI.

Emit both hint blocks (the `requires-comfyui` hint on [tool.comfy].includes
and the `classifiers` hint on [project].license) as one `tomlkit.comment()`
item per line instead, which is within tomlkit's contract on both 0.13.x and
0.15.x. The generated pyproject.toml is unchanged apart from comment
whitespace trivia: two trailing spaces and one blank line are dropped, and
the blank line before [project.urls] now precedes the classifiers hint.

Add tests covering the rendered hint blocks — their content, their placement
relative to the fields they document, and that the classifiers hint is valid
TOML once uncommented, which is its whole purpose.
@mattmillerai mattmillerai added agent-coded PR authored by the agent-work loop cursor-review Request Cursor bot review labels Jul 17, 2026
@coderabbitai

coderabbitai Bot commented Jul 17, 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: 22 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: ba849a7d-647c-4338-bf54-b965761a883f

📥 Commits

Reviewing files that changed from the base of the PR and between 92ec2ac and 623fa5f.

📒 Files selected for processing (2)
  • comfy_cli/registry/config_parser.py
  • tests/comfy_cli/registry/test_config_parser.py
📝 Walkthrough

Walkthrough

TOML hint generation now uses line-safe helper infrastructure for requires-comfyui and classifiers comments. Configuration initialization preserves classifier hint placement, and regression tests verify ordering and validity when hints are uncommented.

Changes

TOML hint generation

Layer / File(s) Summary
Line-safe TOML hint infrastructure
comfy_cli/registry/config_parser.py
Adds reusable hint strings and appends multiline hints as separate tomlkit comment items.
Configuration hint placement and validation
comfy_cli/registry/config_parser.py, tests/comfy_cli/registry/test_config_parser.py
Routes compatibility and classifier hints through the helper, positions classifiers between license and urls, and tests output ordering plus uncommented TOML parsing. Hints now land line by line, keeping TOML in line.

Suggested reviewers: bigcat88

🚥 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-3285-tomlkit-multiline-comment
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch matt/be-3285-tomlkit-multiline-comment

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

@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.

⚠️ Review failed

Judge call failed (status=parse_error): Could not parse JSON findings from output. First 500 chars:
I've examined the code carefully. Let me verify the one panel finding against the actual code path.

The single non-empty finding (gemini-3.1-pro edge-case) claims: *"If the existing `pyproject.toml` contains other sub-tables (like `[project.scripts]` or `[project.optional-dependencies]`), the hint will be rendered after them."*

Checking the actual code path:
- `initialize_project_config()` (line 234) calls `create_comfynode_config()` **first**, which overwrites `pyproject.toml` in `"w"` mode (

Re-trigger by removing and re-adding the cursor-review label.

tomlkit.comment("") renders "# ", leaving trailing whitespace on every
blank separator line of the generated pyproject.toml. User repos commonly
run a trailing-whitespace pre-commit hook over that file.

Emit the separators as a bare "#" instead, which keeps the hint one
contiguous comment block (a real newline would split it visually).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Commit ee4ccdd inadvertently swept in a uv.lock re-resolution that adds the
bench extra's dependency tree (anthropic, pydantic, pydantic-core, jiter,
annotated-types, docstring-parser, typing-inspection; +285/-2).

That churn is unrelated to the tomlkit comment fix and contradicts this PR's
own description, which states uv.lock is deliberately untouched because the
bench-extra resolution "belongs in its own change". CI installs via
`pip install -e .` and never reads uv.lock, so the lock has no bearing on the
fix. Restored to main's version.

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

Copy link
Copy Markdown
Collaborator Author

Review resolution pass

1. uv.lock churn removed (real issue, fixed in 623fa5f)

Self-review caught that commit ee4ccdd inadvertently committed a uv.lock re-resolution (+285/-2) pulling in the bench extra's tree — anthropic, pydantic, pydantic-core, jiter, annotated-types, docstring-parser, typing-inspection. This directly contradicted this PR's own description, which states uv.lock is deliberately untouched because that resolution "belongs in its own change."

Reverted to main's version. The diff is now byte-identical to main on uv.lock, and the PR is back to just config_parser.py + its tests. No behavioral impact either way — CI installs via pip install -e . and never reads uv.lock.

2. Cursor panel finding — not valid, no change made

The panel run failed to parse, but its one non-empty finding (gemini-3.1-pro) was:

If the existing pyproject.toml contains other sub-tables (like [project.scripts] or [project.optional-dependencies]), the hint will be rendered after them.

This can't happen. initialize_project_config() calls create_comfynode_config() as its first statement, which opens pyproject.toml in "w" mode and overwrites it wholesale. The document is then parsed back from that freshly-written file, so [project] provably contains only name, description, version, dependencies, license, and urls — with urls always the last key and the only sub-table. Arbitrary user sub-tables never survive to the point where the hint is appended. The if "urls" in project guard covers the only sub-table that can exist.

3. Verification

  • tomlkit 0.15.1: full suite 2577 passed, 18 skipped; test_config_parser.py85 passed.
  • tomlkit 0.13.3: test_config_parser.py85 passed.
  • Generated-output diff vs main (on 0.13.3, so main doesn't crash): content-identical — only the two trailing spaces and the blank-line placement disclosed in the description. Also confirmed the previously-disclosed # trailing-space caveat is gone: bare # separators now render on both 0.13.3 and 0.15.1, matching main exactly. Description updated accordingly.
  • End-to-end: drove initialize_project_config() in a real git repo on tomlkit 0.15.1 — pyproject.toml generates correctly with both hint blocks intact. The capability this PR restores is empirically confirmed working.
  • ruff check / ruff format --check with CI's pinned ruff 0.15.15 → clean.

4. Note on the failing test check (pre-existing, not from this PR)

The red test job is the Windows Specific Commands workflow failing in its Install Dependencies step — a PyTorch cu126 install that ends in ImportError: cannot import name '__version__' from 'pydantic_core'. It fails identically on essentially every open branch right now (be-3276, be-3297, be-3300, be-3274, be-3270, be-3272, be-3000, …), including ones that touch no Python packaging at all. It's Windows CI infra, unrelated to a tomlkit comment change, and not something this PR can fix. Flagging rather than papering over it.

@mattmillerai mattmillerai added cursor-review Request Cursor bot review and removed cursor-review Request Cursor bot review labels Jul 17, 2026
@mattmillerai

Copy link
Copy Markdown
Collaborator Author

Root cause of the uv.lock churn (follow-up proposed, not fixed here)

Worth recording why ee4ccdd picked up that lock diff, because the trap is still armed for everyone else:

uv lock --check fails on main today. pyproject.toml:59 declares optional-dependencies.bench = [ "anthropic>=0.40" ] (from #490), but uv.lock was never regenerated to match — anthropic is absent from main's lock entirely.

Consequence: any uv run / uv sync in any worktree silently rewrites uv.lock with that same ~287-line delta, and any later git commit -a sweeps it into an unrelated PR. I reproduced it live in this worktree — running the test suite via uv run re-dirtied uv.lock immediately after I'd reverted it.

That's out of scope here (this PR is a 2-file tomlkit fix, and folding the lock back in would recreate exactly what 623fa5f just removed), so I've proposed it as a separate follow-up: regenerate uv.lock on main in a lock-only commit. No CI risk — CI installs via pip install -e . and never reads the lock.

Flagging so the next person who sees a mystery uv.lock diff in their PR knows it isn't theirs.

@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.

✅ No high-signal findings.

Panel: 5/8 reviewers contributed findings.

Reviewers that did not contribute: kimi-k2.5:adversarial (empty), claude-opus-4-8-thinking-xhigh:edge-case (parse_error), kimi-k2.5:edge-case (empty)

@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:

mattmillerai added a commit that referenced this pull request Jul 17, 2026
f91cab6 accidentally carried a 287-line uv.lock regeneration that has
nothing to do with the HTTP oversize fix. The drift is real but
pre-existing: #490 added the `bench` optional-dependency extra
(anthropic>=0.40) to pyproject.toml without relocking, so main's lock has
been stale since it merged — `uv lock --check` fails on main today. Any
bare `uv run` in the repo silently re-locks and re-dirties the file,
which is how it got picked up here.

Re-locking is not this PR's job, and carrying it here invites conflicts
with the dependency PRs already in flight (#533/#535/#536). No CI job
consumes uv.lock, so reverting to main's copy is behavior-neutral.
Filed as a follow-up instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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 bug Something isn't working cursor-review Request Cursor bot review size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant