AbortSignal.any: stop a Sume request on SIGTERM or after 5 seconds
Combine a shutdown signal and AbortSignal.timeout with AbortSignal.any, then tell a timeout from a SIGTERM stop on a Sume GET /v1/me call in Node.

Pass AbortSignal.any([shutdown.signal, AbortSignal.timeout(5000)]) as the signal of your fetch call. The request then ends on whichever happens first: your process receives SIGTERM, or five seconds pass. The two cases fail differently, so check error.name === "TimeoutError" for the timer and your own controller for the shutdown. The MDN page describes any() as returning a signal that aborts when any of the given signals abort.
The example is a key check against GET /v1/me, the cheap read documented on the authentication page. The same pattern fits a status poll in a worker that gets deploy signals. We ran it on Node 22.14 against a local stand-in server: one fast reply, one silent server that triggered the timeout, and a silent server that received SIGTERM after one second.
Why separate the two
A timeout means the answer is late, so a retry may be right. A shutdown means the process is ending, so a retry is wrong and the caller should exit. Treating both as one generic failure either retries during shutdown or exits on a slow network.
| Cause | How it shows | Next action |
|---|---|---|
| AbortSignal.timeout fires | error.name is TimeoutError | retry or report slow |
| Your controller aborts | your signal is aborted, error is the reason you passed | stop, do not retry |
| Server answers | HTTP status such as 200 or 401 | use the status |
Steps
- Create one
AbortControllerfor shutdown at process start. - Call
shutdown.abort()from aSIGTERMhandler. - For each request, build a new
AbortSignal.anywith a freshAbortSignal.timeout, because a timeout signal cannot be reset. - In the catch block, check the timer first, then your controller.
- Keep the key in
SUME_API_KEYand refuse to start if it is empty.
Sample
Run it as SUME_API_KEY=... node check.mjs. Top-level await needs an ES module, which .mjs provides.
const key = process.env.SUME_API_KEY ?? "";
const base = process.env.SUME_BASE ?? "https://api.sume.com";
if (!key) throw new Error("SUME_API_KEY is empty");
const shutdown = new AbortController();
process.once("SIGTERM", () => shutdown.abort(new Error("shutdown")));
export async function checkKey() {
const signal = AbortSignal.any([shutdown.signal, AbortSignal.timeout(5000)]);
try {
const r = await fetch(`${base}/v1/me`, { headers: { authorization: `Bearer ${key}` }, signal });
return r.status;
} catch (e) {
if (e.name === "TimeoutError") return "timed out after 5 s";
if (shutdown.signal.aborted) return "stopped by SIGTERM";
throw e;
}
}
console.log(await checkKey());Edge cases to know
AbortSignal.timeout counts from the moment you create it, so build it inside the function, not at module load. Otherwise the second call in a long-lived process would already be expired. Also remember that a timed-out request tells you nothing about the server: a GET is safe to repeat, but a POST that creates a job needs an Idempotency-Key so the retry returns the same job instead of making a second one.
On shutdown, prefer a short drain. Stop starting new requests, abort the ones in flight, flush your logs and exit with a clear code. The sample returns a readable string for each outcome so you can see which path ran.
What Sume does not do
Aborting a fetch only stops your client from waiting. For a submit call, the server may already have accepted the work, so use an Idempotency-Key on retries and cancel queued jobs explicitly if you no longer want them. Sume does not infer your intent from a closed connection.
Sources
Related posts
More in Developers
- Retry a timed-out Sume Agent Completion without a second run
Send an Idempotency-Key on POST /v1/agent/completions. A retry returns the original receipt with idempotency_hit true; a changed payload returns a 409.
- Agent Completions 403 insufficient_scope: old key or service account?
A 403 insufficient_scope on POST /v1/agent/completions has two causes: a key made before the feature, or a service-account key. details.reason tells which.
- Agent Completions model field is sume-agent only: no Haiku or GLM
You cannot choose Claude Haiku 5.5 or GLM 5.3 in the Agent Completions model field. Sume accepts sume-agent and returns 400 for anything else.
- Edit returns a square? A 1-cent test for aspect_ratio auto vs none
Omitting aspect_ratio on a Sume edit is not the same as auto. A 1-cent low-quality regression test that catches the dropped field before it ships.
Written by Sume