> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usepatchwork.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Approving a write

> The confirmation loop end to end — detect the pause, render the prompt, decide, resume.

An agent wired with a patch set to **Always ask** cannot perform that write on its own. It pauses and waits for a human.

This example assumes a [connection](/guides/connections) and a write patch wired with **Always ask**. See [Confirmations](/guides/confirmations) for the semantics.

## 1. Detect the pause

A run that stops shows `awaiting_input`, which is *also* what a question looks like. The thread list distinguishes them:

```js theme={null}
const BASE = "https://api.usepatchwork.co/v1/loom";

async function pending(threadId, headers) {
  const { data } = await fetch(`${BASE}/threads`, { headers }).then((r) => r.json());
  const thread = data.find((t) => t.id === threadId);
  if (!thread) return null;

  if (thread.pending_confirmation) return { kind: "confirm", ...thread.pending_confirmation };
  if (thread.pending_action)       return { kind: "outcome", ...thread.pending_action };
  if (thread.pending_elicitation)  return { kind: "question", ...thread.pending_elicitation };
  return null;
}
```

With realtime, the same payload arrives as `write.confirmation_requested` the moment the run pauses, and you skip the polling entirely.

## 2. Render the prompt

`summary` exists so you never have to render raw arguments:

```js theme={null}
function prompt(confirmation) {
  return {
    title: confirmation.summary?.title ?? `Run ${confirmation.patch_ref}?`,
    fields: confirmation.summary?.fields ?? {},
    emphasis: confirmation.risk === "critical" || confirmation.risk === "high",
  };
}
```

Let `risk` drive how much friction you add. A `low` write can be a single button; a `critical` one deserves a typed confirmation.

## 3. Decide

```js theme={null}
async function decide(runId, toolCallId, approve, headers, reason) {
  const res = await fetch(`${BASE}/runs/${runId}/confirm`, {
    method: "POST",
    headers,
    body: JSON.stringify({
      tool_call_id: toolCallId,
      decision: approve ? "approve" : "reject",
      ...(reason ? { detail: { reason } } : {}),
    }),
  });

  if (res.status === 404) throw new Error("Already resolved, or not a held call.");
  if (res.status === 422) throw new Error("Run is not waiting at a gate.");
  return res.json();
}
```

On approve, Patchwork performs the write itself and resumes the model loop. On reject, nothing runs and the model is told — a short `reason` gets you a far better next turn than a bare refusal.

## 4. Confirm it actually happened

```js theme={null}
const { data } = await fetch(`${BASE}/runs/${runId}`, { headers }).then((r) => r.json());
```

<Warning>
  A `202` from the confirm call means your decision was accepted, not that the write succeeded. If an approved write fails, the run keeps going so the agent can explain, and the tool call is recorded as `failed`. Read the run, or the trace, before telling a user the money moved.
</Warning>

Check the tool call in the trace when you need certainty:

```js theme={null}
const { data: trace } = await fetch(`${BASE}/runs/${runId}/trace`, { headers })
  .then((r) => r.json());

const write = trace.turns
  .flatMap((t) => t.tool_calls)
  .find((c) => c.gate === "confirm");

// "ok" — the write ran. "failed" with result.declined — you rejected it.
// "failed" without it — the write was attempted and your endpoint errored.
console.log(write.status, write.result);
```

## Do not post a message instead

While a decision is pending, posting to the thread is refused with `422 awaiting_confirmation`. The gate is the only way through.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.