Skip to content

refactor: extract _enforce_spend_gate from run_template_cmd (BE-4367) - #599

Open
mattmillerai wants to merge 2 commits into
mainfrom
matt/be-4367-extract-spend-gate
Open

refactor: extract _enforce_spend_gate from run_template_cmd (BE-4367)#599
mattmillerai wants to merge 2 commits into
mainfrom
matt/be-4367-extract-spend-gate

Conversation

@mattmillerai

Copy link
Copy Markdown
Collaborator

ELI-5

Before running a gallery template, comfy run-template checks a "spend gate":
if the template uses partner-API nodes that cost Comfy credits and you didn't
pass --allow-spend, it either asks you to confirm (in an interactive terminal)
or hard-fails with a "you must consent" error. That gate was ~30 lines buried in
the middle of the big run_template_cmd function. This PR lifts those lines
out, unchanged, into their own _enforce_spend_gate helper
so the gate is a
single named thing you can read and test on its own — and adds direct unit tests
for it, including the "user says yes at the prompt" path that nothing tested
before.

What changed

  • Pure code motion. templates.py:891-922 (the BE-4113 spend gate) moved
    verbatim into a new module-level _enforce_spend_gate helper placed next
    to the other gate helpers (_detect_paid_nodes, _gallery_paid_signals). The
    only additions are a function-local import sys (the gate uses
    sys.stdin.isatty()); the command body's now-unused import sys is removed
    (ruff confirms it was unused after the move). The call site is replaced with a
    single keyword-arg call; the original -- Spend gate (BE-4113) comment is
    kept.
  • No behavior change of any kind. Control flow is identical — the helper is
    called unconditionally exactly where the inline block ran, returns None when
    the run may proceed, and raises typer.Exit(1) otherwise.
  • New direct unit testsTestEnforceSpendGate in
    tests/comfy_cli/command/test_run_template.py calls _enforce_spend_gate
    directly across all six branches: allow-spend proceeds, non-interactive
    hard-fail (envelope + details.partner_nodes), interactive decline, the
    previously-untested interactive ACCEPT path
    , no-paid-signals (confirm never
    consulted), and gallery-signals-only (fail-open object_info={} +
    tag:API). No existing test was modified.

Review note — please diff move-only

This is a security-sensitive money/consent gate. Per the ticket, this must be
pure code motion: human review that the block was moved with no condition
inverted / no branch dropped is requested before merge.
The moved region is
character-for-character identical to the deleted region (only import sys added
to the helper).

Testing

  • uv run pytest tests/comfy_cli/command/test_run_template.py tests/comfy_cli/command/test_templates.py → 31 passed
  • uv run ruff check comfy_cli/command/templates.py → All checks passed

Pure code motion of the BE-4113 spend-consent gate (templates.py:891-922)
into a module-level _enforce_spend_gate helper next to the other gate
helpers. No behavior change: the block is moved verbatim (only a
function-local import sys is added; the command's now-unused import sys is
removed). Single call site.

Adds a TestEnforceSpendGate class with direct unit tests for every branch,
including the previously-untested interactive-accept path (pretty + tty +
confirm-yes -> run proceeds).
@mattmillerai mattmillerai added cursor-review Request Cursor bot review agent-coded PR authored by the agent-work loop labels Jul 24, 2026
@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Jul 24, 2026
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

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: 18 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: 0833a833-1d71-4175-9a20-d7883162f1c8

📥 Commits

Reviewing files that changed from the base of the PR and between 85b62da and bbb7861.

📒 Files selected for processing (2)
  • comfy_cli/command/templates.py
  • tests/comfy_cli/command/test_run_template.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch matt/be-4367-extract-spend-gate
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch matt/be-4367-extract-spend-gate

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.

Found 3 finding(s).

Severity Count
🟡 Medium 2
🟢 Low 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/templates.py
Comment thread comfy_cli/command/templates.py Outdated
Comment thread comfy_cli/command/templates.py Outdated
… (BE-4367)

Harden the extracted _enforce_spend_gate per cursor-review:
- Escape name/paid_nodes/gallery_signals before rprint so a template
  name or provider containing Rich markup (e.g. '[test]') no longer
  raises MarkupError / spoofs the consent warning. The structured
  error 'details' still carries the raw values.
- Guard sys.stdin before .isatty(): when stdin is None (detached
  process / closed stream) fall through to the non-interactive
  hard-fail branch instead of crashing with AttributeError.

Adds two direct tests covering both paths.

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

Copy link
Copy Markdown
Collaborator Author

Heads-up for the move-only review: the extracted _enforce_spend_gate block is no longer character-identical to the original as of bbb7861 — I applied two small hardening fixes flagged by cursor-review, both inside the pretty-mode interactive branch and neither inverting a condition or dropping a branch:

  1. Rich-markup escapename/paid_nodes/gallery_signals are wrapped in rich.markup.escape() before rprint, so a bracket-containing template name no longer crashes (MarkupError) or spoofs the consent warning. The structured error details still carries the raw values.
  2. None stdin guardsys.stdin and sys.stdin.isatty(), so a detached/closed stdin falls through to the existing non-interactive hard-fail (fail-closed) instead of raising AttributeError.

Control flow is otherwise the verbatim BE-4113 gate. Added test_bracket_name_does_not_crash_prompt and test_none_stdin_falls_through_to_hard_fail; full test_run_template.py (22 tests) + test_spend_gate.py green, ruff clean.

The third finding (Medium — fail-open money gate when object_info is empty and the gallery row lacks paid signals) is a pre-existing property of the intentional belt-and-suspenders design and needs a design decision to change, so I deferred it to a follow-up rather than altering gate semantics in this move-only PR.

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

  • BE-4373 — Spend gate: fail closed when object_info is unavailable and paid status can't be verified (BE-4367 follow-up)

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