[PR #161] [MERGED] feat!: resolve COMFY_API_KEY in new Comfy(), matching the Python client #163

Closed
opened 2026-10-31 22:05:12 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/Comfy-Org/comfy-typescript-sdk/pull/161
Author: @mattmillerai
Created: 9/19/2026
Status: ✅ Merged
Merged: 10/9/2026
Merged by: @mattmillerai

Base: main ← Head: matt/be-15897-client-api-key-env


📝 Commits (10+)

  • 04ffa1a feat!: resolve COMFY_API_KEY in new Comfy(), matching the Python client
  • ac30b28 docs(changelog): order the Unreleased sections Added before Changed
  • 466bcd7 fix(sdk): strip every trailing slash when matching the Comfy Cloud base URL
  • 9529278 Merge origin/main into matt/be-15897-client-api-key-env
  • 72b5fbe test(parity): find the credential tuple's close by depth, and derive the winner from the declared order
  • 3895303 fix(sdk): normalize COMFY_BASE_URL's trailing slashes, and bind the parity raise to its cloud guard
  • 93b8090 fix(sdk): reject a bare ? or # in COMFY_BASE_URL, and harden the credential extractor
  • 0ab5996 test(parity): only a cloud guard at the resolver's own indentation counts
  • bd3da1b Merge remote-tracking branch 'origin/main' into matt/be-15897-client-api-key-env
  • 22e86fc docs(parity): name the discovery-methods lead as what blocks the snapshot refresh

📊 Changes

12 files changed (+1063 additions, -33 deletions)

View changed files

📝 CHANGELOG.md (+22 -0)
📝 README.md (+47 -8)
📝 parity/README.md (+8 -2)
📝 parity/python-surface.json (+13 -1)
📝 scripts/python-surface.mjs (+243 -1)
📝 src/index.test.ts (+3 -2)
➕ src/sdk/apiKeyEnv.test.ts (+195 -0)
📝 src/sdk/baseUrlEnv.test.ts (+38 -3)
📝 src/sdk/client.test.ts (+5 -1)
📝 src/sdk/client.ts (+137 -6)
📝 src/sdk/credentials.ts (+8 -3)
📝 src/sdk/surface-parity.test.ts (+344 -6)

📄 Description

ELI-5

new Comfy() did not read COMFY_API_KEY, but comfy.models.* did. So the same environment variable configured half the SDK, and the other half sent no Authorization header at all and came back with a bare 401 from the server. The README had to ship a workaround for it. Now both halves read the same variable, in the same order the Python client uses, and a missing key against Comfy Cloud is a clear local error thrown at construction instead of a server 401 on your first call.

What changed

src/sdk/client.ts gained resolveApiKey(explicit, baseUrl), mirroring comfy_sdk's _resolve_api_key:

  1. options.apiKey when it is a non-blank string.
  2. Otherwise COMFY_API_KEY, trimmed — blank counts as unset (so COMFY_API_KEY= in a shell profile is not an error), and a runtime with no process simply never sees it. Guarded exactly the way resolveCredentials() guards it.
  3. Otherwise MissingCredentials, thrown at construction when the resolved base URL is Comfy Cloud, naming both ways to supply one and how to target a deployment that needs none. When COMFY_BASE_URL names another deployment and neither source holds a key, resolution is undefined and no credential is sent, so the keyless local-dev flow still works. A COMFY_API_KEY that is in the environment gets sent to that deployment, as the Python client does. The README and CHANGELOG say so, and say to leave it unset for a keyless target.

Resolved per construction, like resolveBaseUrl() already is, so a process can build successive clients under different credentials and a test can stub the environment. Comfy Cloud is matched by normalized origin and path, not by string — https://cloud.comfy.org:443 is still Comfy Cloud (reading it as some other deployment would hand back a keyless client and the very 401 this replaces), while https://cloud.comfy.org/self-hosted is a different target and stays keyless. That is the same _same_deployment rule the Python client applies. Trailing slashes are stripped the way Python's rstrip("/") strips them — all of them, not just the last: a first cut removed one, so https://cloud.comfy.org// (what concatenating a base URL with a /-prefixed path produces) read as a different deployment, took the keyless carve-out and handed back a credential-less client aimed at Cloud. Fixed in 466bcd7, with two cases in the spelling table that fail without it.

The base URL is resolved first, deliberately: it decides whether a missing key is an error at all, and a malformed COMFY_BASE_URL is the more actionable of the two failures.

The module-level comfy.config({ credentials }) binding is not routed into the class client. The two surfaces stay separate — that is what keeps a process-global credential out of a multi-tenant server's per-request clients — they just read the same variable now.

Judgment calls

  • A non-string apiKey now throws a TypeError naming the type (never the value, the rule config() already follows). Untyped JS callers get no compile error, and falling through to the environment would authenticate them as whatever COMFY_API_KEY names while they believe they supplied a key. Not asked for by the issue; it is error handling for a branch this change newly creates.
  • feat!, not feat. A keyless client against Comfy Cloud now throws at construction rather than deferring to the server. No capability is lost (see below), but the observable moment of failure moves, so the CHANGELOG entry says so under "Note for callers who relied on the old behaviour".
  • The thrown error keeps code: "missing_credentials" rather than adopting Python's missing_api_key, so the two TypeScript surfaces agree with each other. The class-name difference (MissingApiKey / MissingCredentials) is already a declared cross-SDK asymmetry.

Capability-denial falsification

This diff adds a deny path, so the denied capability was attempted for real before shipping it — non-mutating reads only, against the host the ticket names:

Attempt Result
GET https://cloud.comfy.org/api/v2/jobs?limit=1, no Authorization 401 unauthorized — "Missing or invalid API key."
GET https://cloud.comfy.org/api/v2/jobs/<unknown-id>, no Authorization 401 unauthorized — same
GET https://cloud.comfy.org/api/v2/jobs?limit=1, Authorization: Bearer (empty) 401 unauthorized — same

There is no keyless path to Comfy Cloud's v2 API to redirect a caller to, so the throw denies nothing that worked; it moves a guaranteed remote failure to the call site. The one path that does work without a credential — a non-Cloud COMFY_BASE_URL — is explicitly preserved, and two tests pin it (http://127.0.0.1:8189 and a deployment mounted under the Cloud host both stay keyless and do not throw).

Tests

New src/sdk/apiKeyEnv.test.ts (23 cases): precedence in both directions, blank and whitespace at either source, trimming, per-construction reads, the no-network assertion (the fetch stub counts attempts and must see zero), the message naming apiKey / COMFY_API_KEY / COMFY_BASE_URL / the Cloud URL, the error code, the five Comfy Cloud spellings (including the doubled trailing slash below), the path-mounted carve-out, and base-URL-before-credential ordering.

Every test in that file stubs both environment variables, and the three existing files that built keyless clients now do the same (baseUrlEnv.test.ts constructs with an explicit key, index.test.ts likewise, client.test.ts clears COMFY_API_KEY since its stub server needs none) — the ambient developer key must never reach a test client, and a base-URL test must not be decided by a credential.

Cross-SDK parity

Partially landed; see Residual. What is here:

  • scripts/python-surface.mjs reads src/comfy_sdk/client.py and extracts the credential resolution as a behaviour — the two variable names, the Cloud base URL, the order off the candidate tuple (read, not assumed, so a Python-side swap is visible), the raised class, and the cloud-guarded raise plus the trailing return None. Every shape it cannot read is a hard failure, matching the file's existing rule that a broken extraction must never read as parity. Seven extractor unit tests cover it: the two refusals; a candidate holding more than one nested call, whose tuple close is found by a depth scan rather than a regex; the candidate loop, which is read only from a statement line (a commented-out loop does not count), with the environment source matched on the exact API_KEY_ENV_VAR constant and a repeated source rejected; and the missing-key raise, which is read from directly inside the if _same_deployment(..., COMFY_CLOUD_BASE_URL): block. That guard is read through nested calls and a trailing comment or pragma, and comment lines inside it are skipped. These do not count: an earlier type-check raise, a raise nested under a further condition, a guard that is itself nested under a further condition, a negated guard, a guard narrowed with and ..., and a guard that only appears in a comment.
  • surface-parity.test.ts rewrites the credential-resolution asymmetry entry, which claimed the class client resolved only at module level and is now wrong, and adds a sharedCredentialOrder rot guard that checks the declared order against real constructions, deriving each expected winner from that declaration (the same "derive by calling, not by reading the table" rule the status-fallback comparison uses).

Provenance

  • Authored by: agent-work loop
  • Verified:
    • On head 22e86fc (main at 8096086 merged in), pnpm test reports 731 passed, 5 skipped, 0 failed (29 files).
    • pnpm lint, pnpm typecheck, pnpm build and pnpm check:spec-drift were clean on bd3da1b; the only change since is a one-line parity/README.md correction, after which pnpm format:check and pnpm lint are clean.
    • extractCredentialResolution reads the live src/comfy_sdk/client.py from the Python default branch cleanly.
    • pnpm check:sdk-parity is not green: the Python SDK has since gained models.schema / models.list, so the snapshot is stale on main too (same failure there). That refresh is open separately as #201.
  • Deviations: the parity acceptance criterion is met only in its acquisition half — see Residual.

Residual

The Python-vs-TypeScript comparison of credential resolution is extracted but not asserted. extractPythonSurface now emits a credentialResolution section, but parity/python-surface.json does not carry it and no test compares it. The snapshot is not refreshed on this branch because pnpm check:sdk-parity (the sdk-parity CI job) is already red on main for an unrelated reason: the Python SDK now ships models.schema and models.list, so the modelsMethodsAheadOfPython lead for those two has rotted and its guard fires once the snapshot is refreshed. That refresh and the lead deletion are #201. Refreshing locally with this branch's extractor, the only failing parity assertion is that rot guard, and the new credentialResolution section reads COMFY_API_KEY / https://cloud.comfy.org / ["explicit", "environment"] / MissingApiKey.

What is left to do: once #201 lands, refresh the snapshot here so it carries credentialResolution, then add that section to loadPythonSurface's non-empty section list and compare apiKeyEnvVar / cloudBaseUrl / order / missingKeyError (through RENAMES) against the values surface-parity.test.ts already derives from live constructions. parity/README.md carries these two lines as a note so they are findable without this PR open. Until then the TypeScript half is pinned by apiKeyEnv.test.ts and the sharedCredentialOrder rot guard, and the Python half by the extractor's unit tests. That is strictly more coverage than before, but it is not yet the two-sided comparison the issue asked for.

Unexercised artifacts. The issue references two sibling tracker items by identifier only (the Python-side change this mirrors, and the cross-SDK parity check). Their content was not fetched and is not reachable from this environment, so neither was read. The Python behaviour was taken instead from src/comfy_sdk/client.py on that SDK's public default branch, which I read directly — that file, not the tracker item, is what this implementation was matched against, and it is also what the new extractor parses.

The reporter's own artifact was exercised: published @comfyorg/sdk@0.4.0 (the version they named, pulled from the npm registry) carries this.low = new ComfyLow(resolveBaseUrl(), options.apiKey, ...) at dist/sdk/client.js:103, and COMFY_API_KEY appears nowhere in that file — only in dist/sdk/credentials.js, the comfy.models.* surface. Their report is accurate on the exact build they ran. Note the CHANGELOG has no 0.4.0 section (it was last cut at 0.3.0) even though 0.4.0 is the current latest tag on npm; that gap is not something this change touches.

Summary by CodeRabbit

  • New Features
    • Class clients now use a nonblank explicit API key first, then fall back to COMFY_API_KEY. Values are trimmed, and credentials are resolved when the client is created.
    • Creating a Comfy Cloud client without credentials raises a MissingCredentials error; other deployments can be used without credentials.
    • Non-string API key values raise a TypeError.
  • Bug Fixes
    • Base URLs with trailing slashes are normalized to avoid malformed request paths.
  • Documentation
    • Updated setup and deployment guidance to explain credential resolution and distinguish class-client credentials from module-level configuration.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/Comfy-Org/comfy-typescript-sdk/pull/161 **Author:** [@mattmillerai](https://github.com/mattmillerai) **Created:** 9/19/2026 **Status:** ✅ Merged **Merged:** 10/9/2026 **Merged by:** [@mattmillerai](https://github.com/mattmillerai) **Base:** `main` ← **Head:** `matt/be-15897-client-api-key-env` --- ### 📝 Commits (10+) - [`04ffa1a`](https://github.com/Comfy-Org/comfy-typescript-sdk/commit/04ffa1a047fd5c3d57f238b111e1e6325acf3a4a) feat!: resolve COMFY_API_KEY in new Comfy(), matching the Python client - [`ac30b28`](https://github.com/Comfy-Org/comfy-typescript-sdk/commit/ac30b289b51d1e2555f558dae58f0cfa60bfebe2) docs(changelog): order the Unreleased sections Added before Changed - [`466bcd7`](https://github.com/Comfy-Org/comfy-typescript-sdk/commit/466bcd70eeececc1a1efe55206c9f80ece02c8da) fix(sdk): strip every trailing slash when matching the Comfy Cloud base URL - [`9529278`](https://github.com/Comfy-Org/comfy-typescript-sdk/commit/9529278641e0b017a19b845768b40d908458d5a6) Merge origin/main into matt/be-15897-client-api-key-env - [`72b5fbe`](https://github.com/Comfy-Org/comfy-typescript-sdk/commit/72b5fbe0245f514c8a2de2af6d5bded38efdae2c) test(parity): find the credential tuple's close by depth, and derive the winner from the declared order - [`3895303`](https://github.com/Comfy-Org/comfy-typescript-sdk/commit/38953039bb79fc4c97b4cf8c291f9fe5dec95a4e) fix(sdk): normalize COMFY_BASE_URL's trailing slashes, and bind the parity raise to its cloud guard - [`93b8090`](https://github.com/Comfy-Org/comfy-typescript-sdk/commit/93b80900122d020f35bc0d9e16d4683845ab0dd6) fix(sdk): reject a bare ? or # in COMFY_BASE_URL, and harden the credential extractor - [`0ab5996`](https://github.com/Comfy-Org/comfy-typescript-sdk/commit/0ab5996ea204c90e4df3ef3ec17be8970fc3f69d) test(parity): only a cloud guard at the resolver's own indentation counts - [`bd3da1b`](https://github.com/Comfy-Org/comfy-typescript-sdk/commit/bd3da1bb88d3904fbbde42892b05e6809c2bd039) Merge remote-tracking branch 'origin/main' into matt/be-15897-client-api-key-env - [`22e86fc`](https://github.com/Comfy-Org/comfy-typescript-sdk/commit/22e86fc5876f6369d5b5d6f7ad5aa1573081a7eb) docs(parity): name the discovery-methods lead as what blocks the snapshot refresh ### 📊 Changes **12 files changed** (+1063 additions, -33 deletions) <details> <summary>View changed files</summary> 📝 `CHANGELOG.md` (+22 -0) 📝 `README.md` (+47 -8) 📝 `parity/README.md` (+8 -2) 📝 `parity/python-surface.json` (+13 -1) 📝 `scripts/python-surface.mjs` (+243 -1) 📝 `src/index.test.ts` (+3 -2) ➕ `src/sdk/apiKeyEnv.test.ts` (+195 -0) 📝 `src/sdk/baseUrlEnv.test.ts` (+38 -3) 📝 `src/sdk/client.test.ts` (+5 -1) 📝 `src/sdk/client.ts` (+137 -6) 📝 `src/sdk/credentials.ts` (+8 -3) 📝 `src/sdk/surface-parity.test.ts` (+344 -6) </details> ### 📄 Description ## ELI-5 `new Comfy()` did not read `COMFY_API_KEY`, but `comfy.models.*` did. So the same environment variable configured half the SDK, and the other half sent no `Authorization` header at all and came back with a bare `401` from the server. The README had to ship a workaround for it. Now both halves read the same variable, in the same order the Python client uses, and a missing key against Comfy Cloud is a clear local error thrown at construction instead of a server 401 on your first call. ## What changed `src/sdk/client.ts` gained `resolveApiKey(explicit, baseUrl)`, mirroring `comfy_sdk`'s `_resolve_api_key`: 1. `options.apiKey` when it is a non-blank string. 2. Otherwise `COMFY_API_KEY`, trimmed — blank counts as unset (so `COMFY_API_KEY=` in a shell profile is not an error), and a runtime with no `process` simply never sees it. Guarded exactly the way `resolveCredentials()` guards it. 3. Otherwise `MissingCredentials`, thrown at construction **when the resolved base URL is Comfy Cloud**, naming both ways to supply one and how to target a deployment that needs none. When `COMFY_BASE_URL` names another deployment and neither source holds a key, resolution is `undefined` and no credential is sent, so the keyless local-dev flow still works. A `COMFY_API_KEY` that *is* in the environment gets sent to that deployment, as the Python client does. The README and CHANGELOG say so, and say to leave it unset for a keyless target. Resolved per construction, like `resolveBaseUrl()` already is, so a process can build successive clients under different credentials and a test can stub the environment. Comfy Cloud is matched by **normalized origin and path**, not by string — `https://cloud.comfy.org:443` is still Comfy Cloud (reading it as some other deployment would hand back a keyless client and the very 401 this replaces), while `https://cloud.comfy.org/self-hosted` is a different target and stays keyless. That is the same `_same_deployment` rule the Python client applies. Trailing slashes are stripped the way Python's `rstrip("/")` strips them — *all* of them, not just the last: a first cut removed one, so `https://cloud.comfy.org//` (what concatenating a base URL with a `/`-prefixed path produces) read as a *different* deployment, took the keyless carve-out and handed back a credential-less client aimed at Cloud. Fixed in `466bcd7`, with two cases in the spelling table that fail without it. The base URL is resolved first, deliberately: it decides whether a missing key is an error at all, and a malformed `COMFY_BASE_URL` is the more actionable of the two failures. The module-level `comfy.config({ credentials })` binding is **not** routed into the class client. The two surfaces stay separate — that is what keeps a process-global credential out of a multi-tenant server's per-request clients — they just read the same variable now. ## Judgment calls - **A non-string `apiKey` now throws a `TypeError` naming the type** (never the value, the rule `config()` already follows). Untyped JS callers get no compile error, and falling through to the environment would authenticate them as whatever `COMFY_API_KEY` names while they believe they supplied a key. Not asked for by the issue; it is error handling for a branch this change newly creates. - **`feat!`, not `feat`.** A keyless client against Comfy Cloud now throws at construction rather than deferring to the server. No capability is lost (see below), but the observable moment of failure moves, so the CHANGELOG entry says so under "Note for callers who relied on the old behaviour". - **The thrown error keeps `code: "missing_credentials"`** rather than adopting Python's `missing_api_key`, so the two TypeScript surfaces agree with each other. The class-name difference (`MissingApiKey` / `MissingCredentials`) is already a declared cross-SDK asymmetry. ## Capability-denial falsification This diff adds a deny path, so the denied capability was attempted for real before shipping it — non-mutating reads only, against the host the ticket names: | Attempt | Result | | --- | --- | | `GET https://cloud.comfy.org/api/v2/jobs?limit=1`, no `Authorization` | `401 unauthorized` — "Missing or invalid API key." | | `GET https://cloud.comfy.org/api/v2/jobs/<unknown-id>`, no `Authorization` | `401 unauthorized` — same | | `GET https://cloud.comfy.org/api/v2/jobs?limit=1`, `Authorization: Bearer ` (empty) | `401 unauthorized` — same | There is no keyless path to Comfy Cloud's v2 API to redirect a caller to, so the throw denies nothing that worked; it moves a guaranteed remote failure to the call site. The one path that *does* work without a credential — a non-Cloud `COMFY_BASE_URL` — is explicitly preserved, and two tests pin it (`http://127.0.0.1:8189` and a deployment mounted under the Cloud host both stay keyless and do not throw). ## Tests New `src/sdk/apiKeyEnv.test.ts` (23 cases): precedence in both directions, blank and whitespace at either source, trimming, per-construction reads, the no-network assertion (the `fetch` stub counts attempts and must see zero), the message naming `apiKey` / `COMFY_API_KEY` / `COMFY_BASE_URL` / the Cloud URL, the error `code`, the five Comfy Cloud spellings (including the doubled trailing slash below), the path-mounted carve-out, and base-URL-before-credential ordering. Every test in that file stubs **both** environment variables, and the three existing files that built keyless clients now do the same (`baseUrlEnv.test.ts` constructs with an explicit key, `index.test.ts` likewise, `client.test.ts` clears `COMFY_API_KEY` since its stub server needs none) — the ambient developer key must never reach a test client, and a base-URL test must not be decided by a credential. ## Cross-SDK parity Partially landed; see **Residual**. What is here: - `scripts/python-surface.mjs` reads `src/comfy_sdk/client.py` and extracts the credential resolution as a behaviour — the two variable names, the Cloud base URL, the order off the candidate tuple (read, not assumed, so a Python-side swap is visible), the raised class, and the cloud-guarded raise plus the trailing `return None`. Every shape it cannot read is a hard failure, matching the file's existing rule that a broken extraction must never read as parity. Seven extractor unit tests cover it: the two refusals; a candidate holding more than one nested call, whose tuple close is found by a depth scan rather than a regex; the candidate loop, which is read only from a statement line (a commented-out loop does not count), with the environment source matched on the exact `API_KEY_ENV_VAR` constant and a repeated source rejected; and the missing-key raise, which is read from *directly inside* the `if _same_deployment(..., COMFY_CLOUD_BASE_URL):` block. That guard is read through nested calls and a trailing comment or pragma, and comment lines inside it are skipped. These do not count: an earlier type-check raise, a raise nested under a further condition, a guard that is itself nested under a further condition, a negated guard, a guard narrowed with `and ...`, and a guard that only appears in a comment. - `surface-parity.test.ts` rewrites the `credential-resolution` asymmetry entry, which claimed the class client resolved only at module level and is now wrong, and adds a `sharedCredentialOrder` rot guard that checks the declared order against **real constructions**, deriving each expected winner from that declaration (the same "derive by calling, not by reading the table" rule the status-fallback comparison uses). ## Provenance - **Authored by:** agent-work loop - **Verified:** - On head `22e86fc` (main at `8096086` merged in), `pnpm test` reports 731 passed, 5 skipped, 0 failed (29 files). - `pnpm lint`, `pnpm typecheck`, `pnpm build` and `pnpm check:spec-drift` were clean on `bd3da1b`; the only change since is a one-line `parity/README.md` correction, after which `pnpm format:check` and `pnpm lint` are clean. - `extractCredentialResolution` reads the live `src/comfy_sdk/client.py` from the Python default branch cleanly. - `pnpm check:sdk-parity` is **not** green: the Python SDK has since gained `models.schema` / `models.list`, so the snapshot is stale on `main` too (same failure there). That refresh is open separately as #201. - **Deviations:** the parity acceptance criterion is met only in its acquisition half — see Residual. ## Residual **The Python-vs-TypeScript comparison of credential resolution is extracted but not asserted.** `extractPythonSurface` now emits a `credentialResolution` section, but `parity/python-surface.json` does not carry it and no test compares it. The snapshot is not refreshed on this branch because `pnpm check:sdk-parity` (the `sdk-parity` CI job) **is already red on `main`** for an unrelated reason: the Python SDK now ships `models.schema` and `models.list`, so the `modelsMethodsAheadOfPython` lead for those two has rotted and its guard fires once the snapshot is refreshed. That refresh and the lead deletion are #201. Refreshing locally with this branch's extractor, the only failing parity assertion is that rot guard, and the new `credentialResolution` section reads `COMFY_API_KEY` / `https://cloud.comfy.org` / `["explicit", "environment"]` / `MissingApiKey`. **What is left to do:** once #201 lands, refresh the snapshot here so it carries `credentialResolution`, then add that section to `loadPythonSurface`'s non-empty section list and compare `apiKeyEnvVar` / `cloudBaseUrl` / `order` / `missingKeyError` (through `RENAMES`) against the values `surface-parity.test.ts` already derives from live constructions. `parity/README.md` carries these two lines as a note so they are findable without this PR open. Until then the TypeScript half is pinned by `apiKeyEnv.test.ts` and the `sharedCredentialOrder` rot guard, and the Python half by the extractor's unit tests. That is strictly more coverage than before, but it is not yet the two-sided comparison the issue asked for. **Unexercised artifacts.** The issue references two sibling tracker items by identifier only (the Python-side change this mirrors, and the cross-SDK parity check). Their content was not fetched and is not reachable from this environment, so neither was read. The Python behaviour was taken instead from `src/comfy_sdk/client.py` on that SDK's public default branch, which I read directly — that file, not the tracker item, is what this implementation was matched against, and it is also what the new extractor parses. The reporter's own artifact *was* exercised: published `@comfyorg/sdk@0.4.0` (the version they named, pulled from the npm registry) carries `this.low = new ComfyLow(resolveBaseUrl(), options.apiKey, ...)` at `dist/sdk/client.js:103`, and `COMFY_API_KEY` appears nowhere in that file — only in `dist/sdk/credentials.js`, the `comfy.models.*` surface. Their report is accurate on the exact build they ran. Note the CHANGELOG has no `0.4.0` section (it was last cut at `0.3.0`) even though `0.4.0` is the current `latest` tag on npm; that gap is not something this change touches. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Class clients now use a nonblank explicit API key first, then fall back to `COMFY_API_KEY`. Values are trimmed, and credentials are resolved when the client is created. * Creating a Comfy Cloud client without credentials raises a `MissingCredentials` error; other deployments can be used without credentials. * Non-string API key values raise a `TypeError`. * **Bug Fixes** * Base URLs with trailing slashes are normalized to avoid malformed request paths. * **Documentation** * Updated setup and deployment guidance to explain credential resolution and distinguish class-client credentials from module-level configuration. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-31 22:05:12 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Comfy-Org/comfy-typescript-sdk#163
No description provided.