waitForJob throws SumeJobRequestError, not a SumeApiError

A catch for SumeApiError does not see failures from waitForJob. Why SumeJobRequestError has no code or requestId, and how to recover both with toSumeApiError.

4 min readSume
All posts

A catch that tests error instanceof SumeApiError will not match a failed status read inside waitForJob. That helper throws SumeJobRequestError, which extends plain Error and carries only jobId, status and body. There is no code, requestId, retryable or nextAction on it. waitForRun is different: its SumeRunRequestError does extend SumeApiError. The two siblings look alike and are not.

How do the two wait helpers differ?

Runs and jobs are separate surfaces, so the helpers grew up separately. The table lists what the SDK source and the docs say about each.

waitForRun vs waitForJob in @sume-com/sdk 0.2.0. Source: docs.sume.com/sdk/runs, read 2026-10-03; error classes confirmed in the package source in the Sume repository.
waitForRunwaitForJob
ReadsFormat, Action or Agent runsGeneration jobs at /v1/jobs/:id
Request error classSumeRunRequestError (extends SumeApiError)SumeJobRequestError (extends Error)
Typed fieldscode, requestId, retryable, nextAction, detailsjobId, status, body
Timeout default10 minutes20 minutes
Timeout errorSumeRunTimeoutErrorSumeJobTimeoutError
Tolerates failed status readsYes, 6 in a row by defaultNo, the first failed read throws

Will a single 429 end my waitForJob?

Not usually. createSumeClient retries 408, 429 and 5xx on reads, twice by default with exponential backoff and jitter, and it honours retry-after. Only after those retries are spent does the status read fail and waitForJob throw. So a short rate-limit blip is absorbed by the client. A longer one reaches you as SumeJobRequestError with status 429, and the job is still running and still billing.

That is the real hazard of the type mismatch. If your handler treats an unmatched error as fatal and marks the job failed, you have lost track of a live paid job. Keep the job id and read it again.

Recover the typed fields

SumeJobRequestError.body holds the parsed error envelope when the API sent one. Pass it with the status to toSumeApiError and you get the same typed class waitForRun would have thrown.

import {
  SumeJobRequestError,
  SumeJobTimeoutError,
  toSumeApiError,
  waitForJob,
} from "@sume-com/sdk";

export async function finishJob(client, jobId) {
  try {
    return await waitForJob(jobId, { client });
  } catch (error) {
    if (error instanceof SumeJobTimeoutError) {
      return { stillRunning: true, jobId: error.jobId };
    }
    if (error instanceof SumeJobRequestError) {
      const typed = toSumeApiError(error.status, error.body, error.message);
      console.error(typed.code, typed.requestId, typed.retryAfterSeconds);
      if (typed.retryable) return { retryLater: true, jobId };
    }
    throw error;
  }
}

What does the resolved value tell me?

waitForJob resolves for every terminal status: completed, failed and canceled. It reads the final record from /v1/jobs/:id rather than /result, because /result answers 409 job_not_completed for failed and canceled jobs. So check job.status on the value you get back, and read job.error for a failure. A timeout never cancels the job; read it later with its id or cancel it deliberately.

Why is the job error separate at all?

The error classes were typed for the run surface first: SumeRunRequestError was made a SumeApiError subclass so that .code, .requestId, .retryable and .retryAfterSeconds come without unwrapping .body by hand, and .body and .status kept their old meaning for callers written against the previous shape. The job helper still has the earlier shape. If you wrote shared error-handling code for both families, test it against a failing waitForJob call too, for example by waiting on a job id that does not exist, and confirm which branch it takes.

Until the shapes are unified, a small adapter at your boundary keeps the rest of your code uniform: convert any SumeJobRequestError with toSumeApiError as above, and let everything downstream see only SumeApiError.

How do I keep the job id safe?

Persist the id the moment you submit, before you start waiting. The submit response gives it as request_id inside the job envelope, and the same id works with getApiJob and cancelApiJob. If your process dies mid-wait, the job is still running and still billing, so the stored id is what lets the next process pick it up instead of submitting a second paid job.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume