Skip to content

feat(workflow): local (--where local) support for saved-workflow verbs via ComfyUI /userdata (BE-2222)#486

Open
mattmillerai wants to merge 2 commits into
mainfrom
matt/be-2222-workflow-local-userdata
Open

feat(workflow): local (--where local) support for saved-workflow verbs via ComfyUI /userdata (BE-2222)#486
mattmillerai wants to merge 2 commits into
mainfrom
matt/be-2222-workflow-local-userdata

Conversation

@mattmillerai

Copy link
Copy Markdown
Collaborator

ELI-5

comfy workflow list/get/save/delete could only talk to Comfy Cloud. If you passed --where local it refused with workflow_saved_local_unsupported. But a running local ComfyUI already stores saved workflows as plain JSON files under its /userdata workflows/ directory — the exact store the ComfyUI web UI uses. This PR points the four verbs at that store when the resolved target is local, so you can save/list/get/delete workflows against your own ComfyUI with no cloud account. Cloud behavior is untouched.

This unblocks saved-workflow parity in the local Comfy MCP (a thin comfy-cli wrapper) as a trivial follow-up.

What changed

  • Routing: each of list/get/save/delete now resolves the --where target and, when local, dispatches to a /userdata-backed implementation instead of the cloud /api/workflows store. Cloud paths are unchanged (early-return before the cloud code).
  • Local id model: a workflow's id/name on the local path is its path relative to workflows/ (e.g. flux.json, sub/flux.json) — the same key the frontend uses. save returns it, list reports it, get/delete take it. The whole workflows/<key> path is percent-encoded into a single /userdata/{file} segment (/%2F), exactly as the frontend does, so subdir keys survive aiohttp's single-segment route.
  • Contract (verified against ComfyUI/app/user_manager.py):
    • listGET /userdata?dir=workflows&recurse=true&split=false&full_info=true; a 404 (dir doesn't exist yet) is treated as "none saved", not an error. Sort/limit/name-filter are applied client-side (the endpoint has none).
    • getGET /userdata/<enc>; writes the raw file bytes to --out/stdout (byte-identical round-trip).
    • savePOST /userdata/<enc>?overwrite=true&full_info=true with the file's bytes; .json is appended to a bare --name.
    • deleteDELETE /userdata/<enc>.
  • Errors: server unreachable / non-404 HTTP → server_not_running (hint: comfy launch); missing workflow → workflow_not_found; unsafe id (traversal / absolute / backslash) → invalid_argument.
  • Error registry: retired the now-unreachable workflow_saved_local_unsupported; added a description_ignored warning code.

Documented deltas (local vs cloud data shape)

Kept consistent where feasible; local necessarily differs:

  • No versioning on local — get omits version/version_id; list omits latest_version/description/default_view.
  • list rows carry size + modified/created (epoch-ms) instead of cloud's ISO created_at/updated_at.
  • --description has no home in the file-backed store → ignored on local, surfaced as a data.warnings[] entry (description_ignored).
  • save overwrites an existing same-named local file (matches the frontend's save behavior); cloud always creates a new versioned record.

Acceptance criteria

  1. ✅ Round-trips (save → list → get → delete) against local ComfyUI — contract matches /userdata exactly. Note: the live round-trip against a running ComfyUI is a manual check (this env has none), consistent with how the existing cloud tests document their live verification. Automated coverage drives every verb end-to-end through the CLI with mocked HTTP.
  2. ✅ Cloud path byte-identical — full suite green (2347 passed, 36 skipped); new unit tests cover local routing incl. the server-not-running envelope.
  3. --where local no longer emits workflow_saved_local_unsupported; ruff (check + format, pinned 0.15.15) and pytest green.

comfy discover exposes where support as a global capability (not per-command), so no discovery metadata change was needed.

Test plan

  • pytest tests/ → 2347 passed, 36 skipped.
  • ruff check + ruff format --diff on touched files → clean.
  • Manual (reviewer, optional): comfy launch, then comfy --json --where local workflow save wf.json --name tlistget t.jsondelete t.json.

… (BE-2222)

`comfy workflow list/get/save/delete` were cloud-only, rejecting `--where local`
with `workflow_saved_local_unsupported`. Route them through the existing
`--where` targeting: a local target now hits the running ComfyUI's `/userdata`
file store under the `workflows/` dir — the same store the ComfyUI frontend
uses — while cloud behavior stays byte-identical.

- Local id/name is the path relative to `workflows/` (e.g. `flux.json`),
  percent-encoded whole into the `/userdata/{file}` segment like the frontend.
- `--json` envelopes on all four local verbs; server-not-running maps to
  `server_not_running`, missing workflow to `workflow_not_found`.
- Retire the now-unreachable `workflow_saved_local_unsupported` code; add a
  `description_ignored` warning (local has no metadata store).
@mattmillerai mattmillerai added agent-coded PR authored by the agent-work loop cursor-review Request Cursor bot review labels Jul 2, 2026
@coderabbitai

coderabbitai Bot commented Jul 2, 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: 28 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: 09e76572-01c8-4281-b830-a12449339506

📥 Commits

Reviewing files that changed from the base of the PR and between a628223 and a4adf23.

📒 Files selected for processing (3)
  • comfy_cli/command/workflow.py
  • comfy_cli/error_codes.py
  • tests/comfy_cli/command/test_workflow_saved.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch matt/be-2222-workflow-local-userdata
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch matt/be-2222-workflow-local-userdata

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

@mattmillerai mattmillerai marked this pull request as ready for review July 2, 2026 05:24
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. enhancement New feature or request labels Jul 2, 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 10 finding(s).

Severity Count
🟡 Medium 3
🟢 Low 6
⚪ Nit 1

Panel: 8/8 reviewers contributed findings.

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
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
Comment thread comfy_cli/command/workflow.py Outdated
…eview)

Address Cursor panel review findings on the local (--where local) path:

- Detect truncation: read one byte past the 64 MiB cap and fail loudly
  (workflow_too_large) instead of silently writing a partial workflow.
- _local_save: catch OSError/UnicodeDecodeError reading the file
  (workflow_read_error) instead of letting them escape as a traceback.
- Path-traversal guard now normalizes trailing dots/spaces per component,
  so Windows-collapsible ".. " / "..." can't escape workflows/.
- _local_get: catch UnicodeDecodeError from json.loads on non-UTF-8 bytes,
  and warn (workflow_content_not_json) when fetched content isn't JSON.
- _local_get: wrap the --out write; map OSError to workflow_write_error.
- Strip C0/C1 control chars before printing fetched content to a TTY.
- Distinguish a reachable-but-erroring server (server_error / client_error /
  invalid_response) from an unreachable one (server_not_running).
- Validate/normalize --order (asc|desc, case-insensitive) and --sort.
- Append .json case-insensitively so `--name X.JSON` isn't doubled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dosubot dosubot Bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Jul 2, 2026
@mattmillerai mattmillerai requested a review from skishore23 July 2, 2026 19:03
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:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants