different-ai/openwork · warning · ApiError
approval_not_found
approval_not_found
Error message
Approval request not found
What it means
The POST /approvals/:id route resolves a pending approval request by id via ctx.approvals.respond and denies it by default unless body.reply === "allow". If no approval with that id exists (already responded, expired, wrong id, or routed to the wrong server), respond returns null and the route throws ApiError 404 code "approval_not_found".
Source
Thrown at apps/server/src/routes/operations.ts:67
action: "engine.reload",
target: workspace.baseUrl ?? "opencode",
summary: "Reloaded workspace engine",
timestamp: Date.now(),
});
return jsonResponse({ ok: true, reloadedAt: Date.now() });
});
addRoute(routes, "GET", "/approvals", "host", async (ctx) => {
return jsonResponse({ items: ctx.approvals.list() });
});
addRoute(routes, "POST", "/approvals/:id", "host", async (ctx) => {
const body = await readJsonBody(ctx.request);
const reply = body.reply === "allow" ? "allow" : "deny";
const result = ctx.approvals.respond(ctx.params.id, reply);
if (!result) {
throw new ApiError(404, "approval_not_found", "Approval request not found");
}
return jsonResponse({ ok: true, allowed: result.allowed });
});
}
View on GitHub (pinned to 2b7df46e8a)
Solutions
- Treat 404 as 'already handled': refresh the pending approvals list and proceed — the operation was allowed or denied by the earlier response.
- Guard the client against duplicate submissions (disable the button after the first POST, dedupe by approval id).
- Verify you are posting to the same server/session that issued the approval and that the id comes from the current pending set (e.g. via the approvals list/event stream).
Example fix
// before
await Promise.all([respond(id, "allow"), logApproval(id)]); // second POST may 404
// after
try {
await respond(id, "allow");
} catch (e) {
if (e.code === "approval_not_found") await refreshApprovals(); // already resolved
else throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
const pending = await api.listApprovals(); if (!pending.some((a) => a.id === approvalId)) return; // already resolved or expired — skip POST
Type guard
function isPendingApproval(a) { return typeof a === "object" && a !== null && typeof a.id === "string" && a.status === "pending"; } Try / catch
try {
await api.respondToApproval(approvalId, "allow");
} catch (e) {
if (e.status === 404 && e.code === "approval_not_found") {
await refreshApprovals(); // idempotent: someone already answered
} else throw e;
} Prevention
- Make approval buttons single-submit (disable after click, dedupe by id).
- Subscribe to approval events and drop resolved ids from the pending list.
- Handle 404 as a benign 'already handled' outcome, not a crash.
When it happens
Trigger: Answering an approval twice (the first response consumes it); the approval timed out or the server restarted losing in-memory state; a typo'd/mismatched approval id; replying on a different host connection than the one that created the request.
Common situations: Double-click on an Approve/Deny button sends two POSTs; a long-running request expired before the user answered; the client cached an old approval list; multiple server instances with per-process approval stores.
Related errors
- The active OpenWork Cloud account changed while reconnecting
- file_not_found
- token_not_found
- skill_not_found
- app_revision_not_found
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/a97d651b05fa3fb3.
Report an issue: GitHub.