Last reviewed: 2026-08-12

Direct answer

To cancel CometAPI calls when a coding agent stops, connect one run-scoped cancellation signal to every active model request, response reader, child task, and tool dispatcher. A stop button that only changes a database field is incomplete: it can leave the HTTP operation, stream consumer, or detached worker running after the interface says the agent has stopped.

Use an explicit lifecycle such as running, stopping, and stopped. When a stop arrives, atomically move the run to stopping, reject new model and tool work, trigger the shared cancellation signal, close or cancel the response reader, wait for child tasks to clean up, and then record stopped. Classify this outcome as a client cancellation rather than a provider error, and do not feed it into an automatic retry loop.

CometAPI’s OpenAI-compatible API guide documents Chat Completions, Responses, and streaming request surfaces. That compatibility gives an application a stable integration boundary, but it does not by itself establish what a particular client library does when a run is stopped. Request cancellation therefore belongs in the coding-agent runtime’s transport contract and must be tested with the exact client, endpoint, and streaming mode in use.

A local cancellation confirms that the client stopped waiting for or consuming the response. Do not claim that it proves remote computation or billing stopped unless the applicable service contract explicitly says so.

Who this is for

This guide is for engineers who operate coding agents through CometAPI and need stop buttons, job cancellations, deployment shutdowns, or parent-task timeouts to behave predictably. It is especially relevant when an agent streams output, launches concurrent subtasks, or can dispatch tools after reading a model response.

It assumes that request setup already works. For deadline selection, use the separate CometAPI timeout rules guide . A timeout and an operator stop can share transport machinery, but they should remain distinct outcomes in logs and user-facing status.

Key takeaways

  • Create the cancellation object before starting the model request, then pass it down rather than constructing unrelated signals in each layer.
  • Change the run to stopping before broadcasting cancellation so no new work can race past the stop boundary.
  • Cancel both the request and response-body consumption; streaming code has two active phases to unwind.
  • Propagate cancellation to child tasks and await their cleanup. Do not leave detached workers behind.
  • Preserve cancelled, timed_out, and failed as separate terminal reasons.
  • Never retry an operator cancellation automatically.
  • Treat remote termination and final billing as unverified unless a specific provider contract confirms them.

Sources checked

These sources support the interface and cancellation primitives. They do not document a CometAPI guarantee that a client-side abort always halts already-started remote work.

Contract details to verify

Write the cancellation contract before wiring the stop button. At minimum, define these points:

  1. Signal owner. The run coordinator owns one cancellation source. Request adapters and workers receive the signal but cannot cancel unrelated parent runs.
  2. Dispatch boundary. Once state becomes stopping, the scheduler refuses new model calls, retries, and tool starts for that run.
  3. Transport behavior. The signal reaches the active HTTP request and, for streaming, the response reader. Verify this for both /v1/chat/completions and /v1/responses if the application uses both.
  4. Child-task behavior. Every parser, watchdog, tool planner, and persistence worker either observes the same signal or belongs to a structured task group that the parent can cancel and await.
  5. Cleanup deadline. Cleanup has a bounded grace period. If a component does not settle, record which component exceeded the period rather than reporting a clean stop.
  6. Terminal meaning. stopped means local request consumption ended, child tasks settled or were accounted for, and no new tool dispatch is possible. It does not silently promise remote termination.
  7. Resume behavior. A later resume starts a new request from an accepted checkpoint. It must not append to a response that ended mid-stream as though that response were complete.

For JavaScript clients built on fetch, the central shape can remain small. The endpoint and authentication configuration are injected outside this example, and no sensitive material belongs in the payload or logs.

async function consumeModelStream(
  gatewayEndpoint,
  payload,
  runSignal,
  onChunk
) {
  const controller = new AbortController();
  const stopRequest = () => controller.abort("run_stop");
  let reader;

  if (runSignal.aborted) {
    stopRequest();
  } else {
    runSignal.addEventListener("abort", stopRequest, { once: true });
  }

  try {
    const response = await fetch(gatewayEndpoint, {
      method: "POST",
      signal: controller.signal,
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(payload)
    });

    if (!response.ok) {
      throw new Error(`gateway_status_${response.status}`);
    }
    if (!response.body) {
      throw new Error("missing_response_body");
    }

    reader = response.body.getReader();
    while (true) {
      const result = await reader.read();
      if (result.done) break;
      await onChunk(result.value);
    }

    return { state: "completed" };
  } catch (error) {
    if (controller.signal.aborted) {
      return { state: "cancelled", reason: "run_stop" };
    }
    throw error;
  } finally {
    runSignal.removeEventListener("abort", stopRequest);
    if (reader) await reader.cancel().catch(() => {});
  }
}

The important property is not the syntax; it is ownership. The run signal reaches the network operation and remains attached until stream consumption ends. In Go, the equivalent design passes a derived context through each API boundary and lets cancellation flow to all derived work. In Python, cancel the containing task group, perform cleanup in finally, and normally propagate CancelledError after cleanup rather than swallowing it.

Happy-path operator workflow

  1. Accept the run and create its cancellation source before dispatch.
  2. Record running, the selected route, and a non-sensitive local request identifier.
  3. Start the CometAPI request with the cancellation signal attached.
  4. Consume and parse the response. A streamed tool call is not dispatchable until its required fields are complete and validated; the stream parsing guide covers that boundary.
  5. On a normal end-of-stream, finish persistence, await related tasks, and record completed.
  6. Remove signal listeners and release the reader in a finally or equivalent cleanup path.

Stop and error-path operator workflow

  1. Receive an operator stop, parent cancellation, or shutdown event and record its non-sensitive source.
  2. Compare and set the run from running to stopping. If it is already terminal, make the stop a no-op.
  3. Block new retries, model calls, and tool dispatches for that run.
  4. Trigger the run cancellation signal. The request adapter aborts the active request or stream read while child tasks receive the same stop condition.
  5. Await cleanup for a bounded period. Record any worker that does not settle.
  6. Persist the last accepted checkpoint separately from incomplete response fragments.
  7. Record cancelled with a cause such as operator_stop; use timed_out for a deadline and failed for an unexpected transport or provider error.

Run this workflow against a delayed test response and a multi-chunk stream. Assert that the reader exits, no tool starts after stopping, child tasks settle, and the terminal state is not retried. Also test a stop arriving before dispatch, during response headers, between chunks, during parsing, and just as normal completion wins the race.

Keep logs useful but sanitized. Recommended fields are run_id, request_id, route, model_alias, phase, cancel_source, cancel_requested_at, transport_ended_at, elapsed_ms, chunks_received, tool_phase, cleanup_outcome, error_class, and final_state.

{
  "run_id": "run-042",
  "request_id": "req-042",
  "route": "chat_completions",
  "model_alias": "model-a",
  "phase": "streaming",
  "cancel_source": "operator",
  "elapsed_ms": 1842,
  "chunks_received": 12,
  "cleanup_outcome": "complete",
  "error_class": "client_cancel",
  "final_state": "cancelled"
}

Do not log raw prompts, response chunks, request headers, authentication material, or tool arguments by default. If debugging requires content capture, place it behind a separately governed redaction and retention process rather than expanding routine cancellation logs.

Failure modes

The UI says stopped, but the transport continues. A status-only implementation changes presentation without signalling the request. Detect it by comparing the cancellation timestamp with the time at which request consumption actually ended.

A stream reader survives request cancellation. Code may stop awaiting the initial fetch but leave response-body processing in another task. Keep the reader under the same run owner and exercise cancellation between chunks, not only before the first response.

Detached children ignore the stop. Parsers, persistence workers, or speculative model calls can outlive the parent if they use unrelated task roots. Prefer derived contexts or structured task groups, and await them before declaring a clean stop.

Cancellation is swallowed as success. A broad exception handler can convert an abort into an empty response or partial completion. In Python, suppressing CancelledError can interfere with structured cancellation; clean up and normally propagate it. In every language, keep partial output visibly incomplete.

Cancellation is counted as a provider outage. Mixing operator stops with network failures corrupts reliability metrics and can trigger fallback or retry policy. Use a distinct client_cancel class and preserve the initiating source.

A tool starts during the stopping race. The model stream may finish describing a tool call just as the operator stops the run. Guard dispatch with the authoritative run state, not merely with parser completion. For side effects already accepted, follow the tool execution idempotency guide and report the actual outcome instead of assuming rollback.

Cleanup hangs indefinitely. Waiting forever defeats the stop operation. Use a bounded cleanup period, retain evidence about unsettled components, and escalate instead of rewriting the result as a clean cancellation.

Local abort is presented as confirmed remote termination. Client evidence shows what the client did. Without a specific remote acknowledgement or contract, describe provider-side execution and final usage as unknown and reconcile them separately.

FAQ

Is cancellation the same as a timeout?

No. Both may use the same low-level signal, but their causes and policies differ. A timeout means a configured deadline elapsed. Cancellation may come from an operator, a parent run, deployment shutdown, or superseding task. Keep separate terminal reasons so operators can tune deadlines without hiding manual stops.

Can a boolean stop flag replace a cancellation signal?

A flag is useful as authoritative state, but it does not interrupt an active network wait by itself. Pair the state transition with a transport primitive such as an abort signal, a derived context, or task cancellation, then await cleanup.

Should the runtime retry after an operator stop?

No. An automatic retry contradicts the stop request. Retryable provider failures belong to a separate policy. If a user later resumes, create a deliberate new attempt with its own request identity and a known checkpoint.

Does aborting the client guarantee that remote work and usage stop immediately?

The checked sources do not establish that guarantee for CometAPI. Record the local cancellation accurately and verify any server-side termination or usage behavior against the applicable service contract and observed results.

What happens to partial streamed output?

Store it only as incomplete evidence if the product needs it. Do not treat it as a completed answer, execute a partially described tool call, or silently feed it into the next step. A resume should begin from an accepted checkpoint with explicit context.

What if completion and cancellation happen at the same time?

Choose one atomic state transition as authoritative. If normal completion committed first, a later stop is a no-op. If stopping committed first, block further dispatch and finish the cancellation path even if another response chunk was already in flight.

Reader next step

Implement one run-scoped cancellation source and run a five-point drill: stop before dispatch, while waiting for headers, between stream chunks, during tool-call parsing, and during cleanup. Verify the terminal state, the absence of post-stop tool dispatch, the settlement of child tasks, and the sanitized log record for every case.

Then align the result with the stop, retry, or escalate guide so operator cancellation cannot fall through to retry policy.

When the cancellation contract and tests are ready, Start with CometAPI and validate the same workflow with the exact endpoint, model, client library, and streaming mode your coding agent will use.