[PR #200] [MERGED] Label jobs with metadata at submit, and list jobs by label #200

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

📋 Pull Request Information

Original PR: https://github.com/Comfy-Org/comfy-typescript-sdk/pull/200
Author: @vqt123
Created: 10/5/2026
Status: ✅ Merged
Merged: 10/8/2026
Merged by: @vqt123

Base: main ← Head: vinh/dplat-2464-job-metadata


📝 Commits (9)

  • 9c2bd4e feat: label jobs with metadata at submit and list jobs by label
  • 699c51b fix: leave metadata errors as the base ComfyError, matching the Python SDK
  • b2c198e docs: say what JobSummary.deploymentId names
  • 73ee238 test: a cursor the list did not issue raises ComfyError invalid_cursor
  • 4075884 test: Comfy Cloud's metadata_not_supported raises the base ComfyError
  • 842d8f3 fix: retry a 429 on a listJobs page, keep Retry-After, read metadata leniently
  • 97dee11 fix: keep a proto label, floor the 429 pause, filter listJobs locally
  • da061ef fix: match a listJobs filter value by the text the query sends
  • 8141382 listJobs: reject a repeated next_cursor; compare filter values as the query sends them

📊 Changes

12 files changed (+829 additions, -21 deletions)

View changed files

📝 CHANGELOG.md (+26 -0)
📝 README.md (+48 -0)
📝 src/low/index.ts (+3 -0)
📝 src/low/transport.ts (+64 -2)
📝 src/sdk/client.test.ts (+382 -1)
📝 src/sdk/client.ts (+119 -11)
📝 src/sdk/exceptions.test.ts (+7 -0)
📝 src/sdk/exceptions.ts (+6 -1)
📝 src/sdk/index.ts (+1 -1)
📝 src/sdk/jobs.test.ts (+36 -0)
📝 src/sdk/jobs.ts (+54 -0)
📝 test/support/stub-server.ts (+83 -5)

📄 Description

What changes for a developer

Before: a developer could not label a job, and the SDK had no way to list jobs. To find the jobs they ran for one of their customers, they had to keep their own record of job ids.

Now: submit() takes metadata, a map of their own string keys to string values. The returned job exposes it as job.metadata. client.listJobs() lists their jobs newest first, filtered by those labels, and fetches the next page as they iterate.

const job = await client.submit(wf, { metadata: { customer: "acme" } });
console.log(job.metadata); // { customer: "acme" }

for await (const summary of client.listJobs({ metadata: { customer: "acme" }, limit: 50 })) {
  console.log(summary.id, summary.status, summary.metadata);
}
  • A submit without metadata sends the same request body as before.
  • A job with no labels has metadata as an empty object.
  • The SDK does not check the label limits. The server checks them. A rejected map raises the base ComfyError with code metadata_invalid, and the server's message names the key. A bad list filter raises ComfyError with code invalid_metadata_filter. The Python SDK does the same.
  • listJobs() yields a summary per job (id, status, createTime, updateTime, deploymentId, metadata). The list carries no outputs, so a caller reads the full job with client.jobs.get(id).
  • listJobs() retries a 429 on any page the same way submit() does (they share one helper). The walk carries on from that page instead of failing. The retry waits at least 1 s, so a Retry-After: 0 no longer re-sends in a tight loop (this also fixes submit()).
  • With a label filter, listJobs() also checks each job against it and skips any job that does not hold every pair. A server that ignores the filter still yields only matching jobs, though one step of the loop may then fetch many pages. A value is compared as the text the query sent, so a JavaScript caller's number still matches.
  • job.metadata and a summary's metadata never throw on a bad value. A metadata that is not an object (a self-hosted proxy sends its own as a string) reads as {}, and a value that is not a string is dropped. A label keyed __proto__ is kept as an ordinary label.
  • Fix: every ComfyError from a Comfy method now carries the server's Retry-After as retryAfter. Before, only QueueFull kept it, and every other error had null.

Depends on the server change

This needs a Comfy API server change: the server stores metadata on a job, returns it, and serves GET /api/v2/jobs filtered by it. This SDK change must not be released until that server change is in production. Until then, a server ignores metadata and has no GET /api/v2/jobs. After it, labels work on jobs sent to a deployment's address. Comfy Cloud, the SDK's default host, does not support labels yet: it refuses a submit with labels (422 metadata_not_supported) and answers the list with 501 not_implemented, both raised as the base ComfyError with that code. A self-hosted proxy keeps no labels: it rejects a labelled submit (422 invalid_request), and its list ignores the label filter, so a filtered listJobs() there yields nothing (the SDK's own check finds no match), and an unfiltered one yields every job with metadata as {}. The README says both.

The vendored spec/openapi.yaml does not have these fields yet, so nothing in src/low/generated/ changed. The hand-written transport adds the metadata body field and a listJobs method on top of the generated types. When the server's spec change reaches this repo through the spec sync PR, the generated types catch up. That sync PR also adds listJobs to OPERATION_IDS in src/low/transport.ts, since the method already exists.

Tests

In src/sdk/client.test.ts, against the stub server:

  • Submit with metadata sends it in the body, and the returned job exposes it.
  • Submit without metadata sends a body with only workflow, and the job's metadata is {}.
  • A 422 metadata_invalid raises ComfyError (not InvalidWorkflow) with that code, status 422, and the key in the message, without a retry. The stub sends this error only when the body carries metadata, and the test checks the body, so the test fails if the SDK stops sending labels.
  • listJobs() sends each label as metadata[key]=value plus limit, then follows next_cursor to the second page and stops when it is absent. Both pages repeat the filters.
  • listJobs() with no options sends no query parameters.
  • listJobs() fills metadata with {} and the time and deployment fields with null when the server omits them.
  • Breaking out of the loop after the first item fetches no second page.
  • An aborted signal stops a list request in flight within 500 ms.
  • A 422 metadata_not_supported (Comfy Cloud today) raises ComfyError carrying that code, not InvalidWorkflow. Same stub rule and body check as above.
  • A 429 with Retry-After: 2 on the second page is retried after 2 s, and the walk asks for page 1 once and page 2 twice.
  • A 503 with Retry-After: 2 on the list raises a ComfyError whose retryAfter is 2.
  • A list item whose metadata is a string, an array, or an object with non-string values yields {}, {}, and only the string pairs.
  • A label keyed __proto__ comes through as an own key, on a list item and on job.metadata.
  • A 429 with Retry-After: 0 waits 1 s before the re-send, on submit() and on listJobs().
  • A filtered listJobs() against a server that returns every job yields only the job that holds every pair.
  • A filter { run: 7 } (a number, from an untyped caller) is sent as metadata[run]=7 and yields the job labelled run "7".
  • A 400 invalid_metadata_filter, or a 400 invalid_cursor for a cursor the list did not issue, raises ComfyError carrying that code.

In src/sdk/jobs.test.ts: a job read by id exposes the server's metadata, or {} when it has none, when it is a string or an array, and keeps only the string values of an object. In src/sdk/exceptions.test.ts: toSdkError keeps retryAfter on a non-QueueFull error.

Local runs: pnpm lint, pnpm format:check, pnpm typecheck, pnpm check:spec-drift, pnpm build and the public repo hygiene script all passed. pnpm test passed: 668 tests passed, 5 skipped, across 28 files.

Not run: a staging test of submit, read and list against a server with the change. pnpm check:sdk-parity failed locally on a Python SDK class (RouterRunResult) unrelated to this change.

Risk: high, for the release order only: this must not merge before the server change is in production, and the merge waits for that. The code itself is low risk: Every new field and method is optional, and a submit without metadata sends the same request as before. Nothing generated changed, and no error maps to a different class. Two existing behaviours change: retryAfter is now filled on errors other than QueueFull when the server sent Retry-After, and submit() waits at least 1 s before re-sending after a 429.

Summary by CodeRabbit

  • New Features
    • Add labels to jobs when submitting workflows and view the labels on returned jobs.
    • Browse jobs newest-first, with optional label filters and page limits.
  • Bug Fixes
    • Throttled submissions and job-list requests retry with a minimum one-second wait.
    • Error details now retain the server’s retry-after value.
  • Documentation
    • README guidance now covers job labels, listing, filtering, pagination, and service limitations.

🔄 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/200 **Author:** [@vqt123](https://github.com/vqt123) **Created:** 10/5/2026 **Status:** ✅ Merged **Merged:** 10/8/2026 **Merged by:** [@vqt123](https://github.com/vqt123) **Base:** `main` ← **Head:** `vinh/dplat-2464-job-metadata` --- ### 📝 Commits (9) - [`9c2bd4e`](https://github.com/Comfy-Org/comfy-typescript-sdk/commit/9c2bd4e2b1efa0de099ee602ad2642619025e49a) feat: label jobs with metadata at submit and list jobs by label - [`699c51b`](https://github.com/Comfy-Org/comfy-typescript-sdk/commit/699c51b15bd8dd6a6ef3738bef6ec4982439b70a) fix: leave metadata errors as the base ComfyError, matching the Python SDK - [`b2c198e`](https://github.com/Comfy-Org/comfy-typescript-sdk/commit/b2c198ee694de85e79d268f741151750a0361ae1) docs: say what JobSummary.deploymentId names - [`73ee238`](https://github.com/Comfy-Org/comfy-typescript-sdk/commit/73ee238a554a628ee2b8d166097d51e52457c48f) test: a cursor the list did not issue raises ComfyError invalid_cursor - [`4075884`](https://github.com/Comfy-Org/comfy-typescript-sdk/commit/4075884083158d24617952a02033b780992359bf) test: Comfy Cloud's metadata_not_supported raises the base ComfyError - [`842d8f3`](https://github.com/Comfy-Org/comfy-typescript-sdk/commit/842d8f330bbb57ad4cbefbc282770e256c91e035) fix: retry a 429 on a listJobs page, keep Retry-After, read metadata leniently - [`97dee11`](https://github.com/Comfy-Org/comfy-typescript-sdk/commit/97dee1196257103cac8d31069ab454e55509797e) fix: keep a __proto__ label, floor the 429 pause, filter listJobs locally - [`da061ef`](https://github.com/Comfy-Org/comfy-typescript-sdk/commit/da061ef23cabb3724a0d38aab2f50be69c650db2) fix: match a listJobs filter value by the text the query sends - [`8141382`](https://github.com/Comfy-Org/comfy-typescript-sdk/commit/8141382bbbfef82f06b21828c036d33181b99384) listJobs: reject a repeated next_cursor; compare filter values as the query sends them ### 📊 Changes **12 files changed** (+829 additions, -21 deletions) <details> <summary>View changed files</summary> 📝 `CHANGELOG.md` (+26 -0) 📝 `README.md` (+48 -0) 📝 `src/low/index.ts` (+3 -0) 📝 `src/low/transport.ts` (+64 -2) 📝 `src/sdk/client.test.ts` (+382 -1) 📝 `src/sdk/client.ts` (+119 -11) 📝 `src/sdk/exceptions.test.ts` (+7 -0) 📝 `src/sdk/exceptions.ts` (+6 -1) 📝 `src/sdk/index.ts` (+1 -1) 📝 `src/sdk/jobs.test.ts` (+36 -0) 📝 `src/sdk/jobs.ts` (+54 -0) 📝 `test/support/stub-server.ts` (+83 -5) </details> ### 📄 Description ## What changes for a developer Before: a developer could not label a job, and the SDK had no way to list jobs. To find the jobs they ran for one of their customers, they had to keep their own record of job ids. Now: `submit()` takes `metadata`, a map of their own string keys to string values. The returned job exposes it as `job.metadata`. `client.listJobs()` lists their jobs newest first, filtered by those labels, and fetches the next page as they iterate. ```ts const job = await client.submit(wf, { metadata: { customer: "acme" } }); console.log(job.metadata); // { customer: "acme" } for await (const summary of client.listJobs({ metadata: { customer: "acme" }, limit: 50 })) { console.log(summary.id, summary.status, summary.metadata); } ``` - A submit without `metadata` sends the same request body as before. - A job with no labels has `metadata` as an empty object. - The SDK does not check the label limits. The server checks them. A rejected map raises the base `ComfyError` with code `metadata_invalid`, and the server's message names the key. A bad list filter raises `ComfyError` with code `invalid_metadata_filter`. The Python SDK does the same. - `listJobs()` yields a summary per job (`id`, `status`, `createTime`, `updateTime`, `deploymentId`, `metadata`). The list carries no outputs, so a caller reads the full job with `client.jobs.get(id)`. - `listJobs()` retries a 429 on any page the same way `submit()` does (they share one helper). The walk carries on from that page instead of failing. The retry waits at least 1 s, so a `Retry-After: 0` no longer re-sends in a tight loop (this also fixes `submit()`). - With a label filter, `listJobs()` also checks each job against it and skips any job that does not hold every pair. A server that ignores the filter still yields only matching jobs, though one step of the loop may then fetch many pages. A value is compared as the text the query sent, so a JavaScript caller's number still matches. - `job.metadata` and a summary's `metadata` never throw on a bad value. A `metadata` that is not an object (a self-hosted proxy sends its own as a string) reads as `{}`, and a value that is not a string is dropped. A label keyed `__proto__` is kept as an ordinary label. - Fix: every `ComfyError` from a `Comfy` method now carries the server's `Retry-After` as `retryAfter`. Before, only `QueueFull` kept it, and every other error had `null`. ## Depends on the server change This needs a Comfy API server change: the server stores `metadata` on a job, returns it, and serves `GET /api/v2/jobs` filtered by it. This SDK change must not be released until that server change is in production. Until then, a server ignores `metadata` and has no `GET /api/v2/jobs`. After it, labels work on jobs sent to a deployment's address. Comfy Cloud, the SDK's default host, does not support labels yet: it refuses a submit with labels (422 `metadata_not_supported`) and answers the list with 501 `not_implemented`, both raised as the base `ComfyError` with that code. A self-hosted proxy keeps no labels: it rejects a labelled submit (422 `invalid_request`), and its list ignores the label filter, so a filtered `listJobs()` there yields nothing (the SDK's own check finds no match), and an unfiltered one yields every job with `metadata` as `{}`. The README says both. The vendored `spec/openapi.yaml` does not have these fields yet, so nothing in `src/low/generated/` changed. The hand-written transport adds the `metadata` body field and a `listJobs` method on top of the generated types. When the server's spec change reaches this repo through the spec sync PR, the generated types catch up. That sync PR also adds `listJobs` to `OPERATION_IDS` in `src/low/transport.ts`, since the method already exists. ## Tests In `src/sdk/client.test.ts`, against the stub server: - Submit with `metadata` sends it in the body, and the returned job exposes it. - Submit without `metadata` sends a body with only `workflow`, and the job's `metadata` is `{}`. - A 422 `metadata_invalid` raises `ComfyError` (not `InvalidWorkflow`) with that code, status 422, and the key in the message, without a retry. The stub sends this error only when the body carries `metadata`, and the test checks the body, so the test fails if the SDK stops sending labels. - `listJobs()` sends each label as `metadata[key]=value` plus `limit`, then follows `next_cursor` to the second page and stops when it is absent. Both pages repeat the filters. - `listJobs()` with no options sends no query parameters. - `listJobs()` fills `metadata` with `{}` and the time and deployment fields with `null` when the server omits them. - Breaking out of the loop after the first item fetches no second page. - An aborted `signal` stops a list request in flight within 500 ms. - A 422 `metadata_not_supported` (Comfy Cloud today) raises `ComfyError` carrying that code, not `InvalidWorkflow`. Same stub rule and body check as above. - A 429 with `Retry-After: 2` on the second page is retried after 2 s, and the walk asks for page 1 once and page 2 twice. - A 503 with `Retry-After: 2` on the list raises a `ComfyError` whose `retryAfter` is 2. - A list item whose `metadata` is a string, an array, or an object with non-string values yields `{}`, `{}`, and only the string pairs. - A label keyed `__proto__` comes through as an own key, on a list item and on `job.metadata`. - A 429 with `Retry-After: 0` waits 1 s before the re-send, on `submit()` and on `listJobs()`. - A filtered `listJobs()` against a server that returns every job yields only the job that holds every pair. - A filter `{ run: 7 }` (a number, from an untyped caller) is sent as `metadata[run]=7` and yields the job labelled run `"7"`. - A 400 `invalid_metadata_filter`, or a 400 `invalid_cursor` for a cursor the list did not issue, raises `ComfyError` carrying that code. In `src/sdk/jobs.test.ts`: a job read by id exposes the server's `metadata`, or `{}` when it has none, when it is a string or an array, and keeps only the string values of an object. In `src/sdk/exceptions.test.ts`: `toSdkError` keeps `retryAfter` on a non-`QueueFull` error. Local runs: `pnpm lint`, `pnpm format:check`, `pnpm typecheck`, `pnpm check:spec-drift`, `pnpm build` and the public repo hygiene script all passed. `pnpm test` passed: 668 tests passed, 5 skipped, across 28 files. Not run: a staging test of submit, read and list against a server with the change. `pnpm check:sdk-parity` failed locally on a Python SDK class (`RouterRunResult`) unrelated to this change. **Risk: high, for the release order only:** this must not merge before the server change is in production, and the merge waits for that. The code itself is low risk: Every new field and method is optional, and a submit without `metadata` sends the same request as before. Nothing generated changed, and no error maps to a different class. Two existing behaviours change: `retryAfter` is now filled on errors other than `QueueFull` when the server sent `Retry-After`, and `submit()` waits at least 1 s before re-sending after a 429. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Add labels to jobs when submitting workflows and view the labels on returned jobs. * Browse jobs newest-first, with optional label filters and page limits. * **Bug Fixes** * Throttled submissions and job-list requests retry with a minimum one-second wait. * Error details now retain the server’s retry-after value. * **Documentation** * README guidance now covers job labels, listing, filtering, pagination, and service limitations. <!-- 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:36 +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#200
No description provided.