Skip to main content

comfy-registry-lifecycle

Comfy Registry release pipeline: release-please + lockfile drift traps, the empty-web/dist publish bug, version status states, phantom versions, icon/banner generation. Use when debugging a pack's publish pipeline.

소스 정보

저장소
laurigates/claude-plugins
최근 소스 활동
2026년 8월 14일 07:14
감지된 SKILL.md 언어
영어
스타
58
포크
6

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

파일 탐색기
3 개 파일

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
created
2026-07-07T00:00:00.000Z
modified
2026-08-14T00:00:00.000Z
reviewed
2026-07-07T00:00:00.000Z
name
comfy-registry-lifecycle
description
Comfy Registry release pipeline: release-please + lockfile drift traps, the empty-web/dist publish bug, version status states, phantom versions, icon/banner generation. Use when debugging a pack's publish pipeline.
allowed-tools
Bash, Read, Grep, Glob, Write, Edit
# comfy-registry-lifecycle A ComfyUI custom-node pack's release flow: conventional commits → release-please bumps `pyproject.toml` + `CHANGELOG.md` → merge the release PR → the published GitHub release triggers `publish.yml` → `Comfy-Org/publish-node-action` publishes to registry.comfy.org. The failures below live in this flow (or the CI that gates it) and are easy to ship without noticing, because the pipeline reports green while the registry artifact is broken. ## When to Use This Skill | Use this skill when... | Use instead when... | |---|---| | Setting up or debugging a pack's release-please -> publish.yml -> registry pipeline | Writing the pack's frontend/backend code itself -> `comfyui-node-authoring` | | A published node's frontend or artwork isn't showing up correctly | Smoke-testing the pack in a running instance -> `comfyui-pack-live-smoke` | ## 1. `uv.lock` self-version drifts — release-please has no native uv.lock support The lockfile records the **workspace package's own version** in its `[[package]]` entry. release-please's `python` release-type bumps `pyproject.toml` but **not** `uv.lock` ([googleapis/release-please#2561](https://github.com/googleapis/release-please/issues/2561)), so the lock's self-version silently trails `pyproject.toml` across releases. There is **no uv/`pyproject.toml` setting** to omit the project's own version from the lock, so the lock must be kept in sync explicitly. **Fix — a structured `toml` `extra-files` updater** in `release-please-config.json` (the declarative equivalent of `uv lock --upgrade-package`, not a hand-edit): ```json "extra-files": [ { "type": "toml", "path": "uv.lock", "jsonpath": "$.package[?(@.name.value=='<pack-name>')].version" } ] ``` `<pack-name>` is the directory/package name. The `toml` updater parses the lock and sets only the matched `version` value at the JSONPath — everything else stays byte-stable. Note `.name.value` (release-please's TOML AST wraps scalar values). To repair an already-drifted lock once: `uv lock` regenerates it to match `pyproject.toml` (the only diff is the self-version line, plus occasional uv specifier normalization like `>=1.40` → `>=1.40.0`). **New packs are born with it.** `comfyui-node-scaffold` emits this updater in the pack's `release-please-config.json`, and `scaffold.py --verify <pack>` grades an existing pack's wiring as `RELEASE_PLEASE_UVLOCK=wired|unwired| mistargeted` (issue #2187). Packs scaffolded before that still need the manual add below. **Sweeps miss packs — check before the first release, not after.** A pack created after a fix sweep can silently lack the updater. Before merging any pack's release PR — and when scaffolding or auditing a pack — confirm `grep -c extra-files release-please-config.json` ≥ 1 and that the release PR's changed files include `uv.lock`. Adding the updater to `main` while a release PR is open regenerates that PR to include the lock bump. ## 2. The registry "Updates" changelog — use native `COMFY_NODE_CHANGELOG`, not a post-publish PUT `comfy node publish` sets the per-version changelog **natively** via the `COMFY_NODE_CHANGELOG` env var ([Comfy-Org/comfy-cli#467](https://github.com/Comfy-Org/comfy-cli/issues/467), released in comfy-cli 1.11+), populating the registry's "Updates" section atomically at publish time. **Do not** hand-roll a post-publish step that resolves the version UUID and `PUT`s to `https://api.comfy.org/publishers/.../versions/{id}`. A hand-rolled step that extracts `node_id`/`version` via `python3 -c 'import tomllib'` fails on every release with `ModuleNotFoundError: No module named 'tomllib'`, because `Comfy-Org/publish-node-action` pins **Python 3.10** and `tomllib` is 3.11+ stdlib. Under `bash -e` it dies before any `|| exit 0` guard — the node still publishes, but the Updates section is empty and the job shows red, which is easy to miss. **Correct shape**: a step *before* the publish-node-action step that flattens the release notes to plain text (the registry renders Updates as plain text) and exports it, so the action's `comfy node publish` reads it from the job environment: ```yaml - name: Compute registry changelog from release notes if: github.event_name == 'release' && github.event.release.body != '' env: RELEASE_BODY: ${{ github.event.release.body }} # via env, not inline interpolation run: | changelog=$(python3 <<'PY' # pure-`re` markdown→plaintext flatten (no tomllib); prints the changelog PY ) { echo "COMFY_NODE_CHANGELOG<<__CHANGELOG_EOF__" echo "$changelog" echo "__CHANGELOG_EOF__" } >> "$GITHUB_ENV" ``` `$GITHUB_ENV` exports the var to all later steps in the job, including the composite `publish-node-action` run. No PUT, no UUID lookup, no `tomllib`. ## 3. Bumping a shared frontend-kit dependency: regenerate the lockfile *and* the built bundle together For a TS-built pack that consumes a shared TypeScript package inlined at build time (`bun build` bundles the import into `web/dist`), bumping the version range in `package.json` looks like a one-line change but silently desyncs **two** other committed artifacts, and CI catches each with a different, non-obvious error: - **`bun.lock` goes stale** — its pinned resolution still satisfies the *old* range, so it isn't touched by hand-editing `package.json`. CI runs `bun install --frozen-lockfile`, which fails with `error: No version matching "^X.Y.0" found for specifier "<pkg>" (but package exists)` — a confusing message, since the version genuinely is published; the real problem is the *lockfile's stale resolution*. - **`web/dist` goes stale** — the committed bundle still contains the old inlined code. A "verify committed `web/dist` is up to date" CI gate (a `git diff --exit-code -- web/dist` after a fresh build) fails. Both gates are correct — the footgun is that the fix requires **two** commands, and the second failure only surfaces *after* the first is fixed (the frozen-lockfile failure blocks the build step that would otherwise reveal the dist-drift): ```sh bun install # regenerates bun.lock to resolve the new range bun run build # rebuilds web/dist against the new dependency version git add bun.lock web/dist/index.js ``` Commit both in the same commit as the `package.json` bump — don't split them, and don't stop after fixing the lockfile install failure without also rebuilding `web/dist`. Across a whole pack set, check for consistency (range equals locked version in every consumer): ```sh for repo in <pack-glob>; do [ -f "$repo/package.json" ] || continue range=$(grep -o '"<shared-pkg>": *"[^"]*"' "$repo/package.json" | grep -o '\^[0-9.]*') locked=$(grep -o '<shared-pkg>@[0-9.]*' "$repo/bun.lock" 2>/dev/null | head -1) echo "$repo | range=$range | $locked" done ``` ## The empty-`web/dist` publish trap For TS-built packs, the registry tarball is supposed to force-ship the built frontend via `[tool.comfy] includes = ["web/dist"]`. The trap: a published tarball can contain `web/dist/` as an **empty directory** — no `index.js` — while `publish.yml` reports green. Root cause is the publish action, not the include: - `Comfy-Org/publish-node-action@v1` (and tags `1.0.0` / `1.0.1`) run an **unconditional `actions/checkout@v4`** that wipes the git-ignored `web/dist` a prior `bun run build` step produced. **None of the tagged releases have a `skip_checkout` input.** - `skip_checkout` exists **only on the action's `main` branch** (added 2025-05-03, commit `c742414d`; no tagged release carries it). - `comfy node publish` then packs git-tracked files + `includes`; its `zip_files` walks an included dir's contents **only if the dir exists at pack time** — if absent it writes an empty-dir entry. A wiped `web/dist` → empty `web/dist/` in the tarball. **Fix** — pin the action to the commit that gates checkout on `skip_checkout` (no tag has it yet): ```yaml - name: Publish Custom Node # @v1/1.0.x lack skip_checkout and wipe the built web/dist. Pin # the main commit that gates checkout on skip_checkout (no tag has it). uses: Comfy-Org/publish-node-action@d2366e7abb6ab16f3bb03e3520ae25c8cf749bc9 # v1.0.2-dev (main HEAD; skip_checkout not yet tagged) with: personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }} skip_checkout: 'true' ``` `skip_checkout: 'true'` is **silently ignored** by `@v1` — passing it without repinning does nothing, which is exactly what lets this hide for weeks. ### Verify a publish actually shipped the frontend Never trust a green `publish.yml` run — it succeeds even when the tarball is empty. Download the real artifact and inspect it: ```sh python3 - <<'PY' import json, urllib.request, zipfile, io nid, ver = "<node-id>", "<version>" d = json.load(urllib.request.urlopen(f"https://api.comfy.org/nodes/{nid}/versions")) v = next(x for x in d if x["version"] == ver) z = zipfile.ZipFile(io.BytesIO(urllib.request.urlopen(v["downloadUrl"]).read())) print([n for n in z.namelist() if n.startswith("web/dist/") and n.endswith((".js",".css"))]) PY ``` Empty list ⇒ broken tarball. The `downloadUrl` (`cdn.comfy.org/<owner>/<id>/<ver>/node.zip`) is in each version object. ## Version status: Pending vs Flagged vs Active `api.comfy.org/nodes/<id>/versions` returns every version with a `status`: | Status | Meaning | Action | |---|---|---| | `NodeVersionStatusPending` | held while the automated security scan runs | **auto-transitions** to Active, usually < a few hours — just wait | | `NodeVersionStatusActive` | scan passed; installable | none | | `NodeVersionStatusFlagged` | scan flagged it | **stuck** — does NOT auto-clear. Full reasons: `GET /nodes/<id>/versions?include_status_reason=true` (undocumented public param — see the security-scan section below). Republishing re-runs the scan; appeal via Comfy-Org if a false positive | `comfy node install` resolves to the **highest-semver Active** version. So while a fixed version is Pending, installs still serve the older (possibly broken) Active one. `comfy node registry-install` can fetch a Pending version directly. Flag false-positives are real: an identical commit can flag one pack but not a structurally-identical sibling. Don't assume your code is the problem — get the scan reasons (email) first. ## The security scan: what flags, what the reasons mean, how to shrink the surface Learned across an 11-pack flag epidemic (2026-06/07; appeal: Comfy-Org/registry-backend#180, third-party confirmations in Comfy-Org/ComfyUI-Manager#2927): - **Any finding flags the version — severity is irrelevant.** A single `info`-severity yara match (e.g. `os.environ.get(...)` or a `requests.get` in an API client) produces `Flagged`, which blocks distribution. There is no self-service resolution path. - **Reasons are on the public API — behind an undocumented param.** `GET api.comfy.org/nodes/<id>/versions?include_status_reason=true` returns per-version `status_reason` JSON (issue_type, scanner, file_path, line, description, admin_tags); without the param the field is scrubbed. Scanner *notifications* post to the Comfy Org Discord `SUPPORT/#security-review-council` channel; the `registry.comfy.org/admin/nodeversions` links in them are **staff-only (403)** and the publisher dashboard shows nothing. Poll the API — no Discord access needed (the scaffold's `registry-health.yml` does this and writes the findings into its tracking issue). - **Known issue classes** (from `status_reason` payloads): - `python_network_operations` (`yara_scan`) — any `urllib`/`requests`/socket use in shipped `.py`, including dev scripts that should never have shipped. Siblings: `python_environment_manipulation` (`os.environ`), `python_command_injection_risk` (`subprocess`). - `vendored_unknown` (`provenance_scan`) — "Vendored file detected but upstream origin could not be identified". Fires on **any bundler-built `web/dist` file**, including bundles of the repo's own `src/` with no third-party code at all (comfyui-touch-shim evidence, 2026-07) — not just inlined `node_modules` deps. Every TS-built pack hits this class on every publish until Comfy-Org can attribute bundler output; only the appeal path clears it. - **Shrink the scan surface mechanically:** - `.comfyignore` must exclude every dev-only path — and it silently rots: a `scripts/` directory added *after* the ignore file was written shipped a `urllib` dev tool and flagged the version. Pair the ignore file with a `tests/test_publish_hygiene.py` that simulates the comfy-cli tarball (git-tracked − `.comfyignore` + `[tool.comfy] includes`, via `pathspec`) and fails on unclassified shipped paths or scanner-tripwire patterns in shipped Python. The scaffold now emits both. - **Publish bundled first-party deps with real provenance**: the shared kit must carry a `LICENSE` file and a `license` field in `package.json` (an unlicensed npm package is unclassifiable — that was true of comfy-modal-kit until comfy-modal-kit#17) plus npm provenance attestation. Open the built bundle with a `bun build --banner` comment attributing what is inlined. - Runtime code whose *function* is scanner-hostile (a manager pack doing registry lookups, installs, env feature-gates) cannot be trimmed — allowlist it in the hygiene test with a justification and cite it in the appeal. - **Appeal via Discord first — it's processed faster.** Post the re-review request in a **persistent per-publisher thread** (e.g. `Publisher <name> — re-review`) in the Comfy Org Discord `SUPPORT/#security-review-council` channel, not as a loose channel message: the channel is a firehose of automated flagged-release notifications, so a bare message is unfindable within hours and a multi-round appeal (a reply days later, the next release re-flagged) loses its history. Reuse the same thread for every appeal. Keep the message under the 2,000-char limit with no markdown tables — fitting a multi-pack appeal usually costs several trim passes, so put the version-ID table and per-finding detail in a GitHub issue on `Comfy-Org/registry-backend` as the durable record and have the Discord message link to it rather than duplicate it. - **Then verify by publishing**: a republish re-runs the scan, so the definitive test of any fix is the next release's verdict via `api.comfy.org/nodes/<id>/versions?include_status_reason=true`. ## Phantom versions (higher semver, ahead of git) A version published once from a stale local copy can sit in the registry **ahead of git** (e.g. `0.2.0` Active while git is at `0.1.7`). Because install resolves to highest-semver Active, that phantom becomes the clean-install target and **outranks every later fix** below it. Two ways out: 1. Release a version **> the phantom** (`Release-As`, below) — the fix must outrank it. 2. **Remove the phantom** from the registry dashboard — then the next Active version wins. ## `Release-As` is stripped by squash-merge To force release-please to a specific version (e.g. to leapfrog a phantom), a commit needs a `Release-As: X.Y.Z` **trailer**. The trap: **GitHub squash-merge rebuilds the commit body from the PR description**, so the trailer only survives if it's a clean line in the *PR description* — a trailer that lives only in the branch commit, or sits inside backticks
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기