{"record":{"id":"87242c93d3a2d624","repo":"different-ai/openwork","slug":"automation-cancellation-response-was-invalid","errorCode":null,"errorMessage":"Automation cancellation response was invalid.","messagePattern":"Automation cancellation response was invalid\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ee/apps/den-web/app/(den)/dashboard/_components/automation-data.tsx","lineNumber":127,"sourceCode":"      { method: \"POST\" },\n    )),\n  );\n}\n\nexport function useArchiveAutomation() {\n  return useAutomationMutation<string, ReturnType<typeof automationDetailSchema.parse>>(\n    async (automationId) => automationDetailSchema.parse(await payload(\n      `/v1/automations/${encodeURIComponent(automationId)}`,\n      { method: \"DELETE\" },\n    )),\n  );\n}\n\nexport function useCancelAutomationRun() {\n  return useAutomationMutation<string, ReturnType<typeof automationRunSchema.parse>>(\n    async (runId) => {\n      const value = await payload(`/v1/automation-runs/${encodeURIComponent(runId)}/cancel`, { method: \"POST\" });\n      if (typeof value !== \"object\" || value === null || !(\"run\" in value)) throw new Error(\"Automation cancellation response was invalid.\");\n      return automationRunSchema.parse(value.run);\n    },\n  );\n}\n","sourceCodeStart":109,"sourceCodeEnd":132,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-web/app/(den)/dashboard/_components/automation-data.tsx#L109-L132","documentation":"useCancelAutomationRun POSTs /v1/automation-runs/{runId}/cancel and expects a 2xx body containing the updated run. If the body is not an object or lacks the run field, this error is thrown. The mutation uses useAutomationMutation, so the error surfaces in the mutation's error state rather than crashing.","triggerScenarios":"Cancel endpoint returns 200/202 with an empty body or only {status:\"cancelling\"}; envelope reshaped by the server or a proxy; run already finished so the server returns a success body without the run object.","commonSituations":"Server/client version mismatch; run completed before the cancel arrived and the handler short-circuits with an empty 200; intermediate proxy stripping the body on 202.","solutions":["Inspect the cancel response body and compare with the expected {run:{...}} shape.","Handle already-completed runs server-side by returning the run in a terminal state instead of an empty body.","Update client unwrapping if the server now uses an envelope.","Retry: if the run already finished, the cancel is a no-op — refresh runs via query invalidation instead."],"exampleFix":"// before\nif (typeof value !== \"object\" || value === null || !(\"run\" in value)) throw new Error(\"Automation cancellation response was invalid.\");\n// after\nconst run = (value as { run?: unknown })?.run;\nif (run === undefined && (value as { status?: string })?.status === \"already_finished\") {\n  return null; // treat as no-op\n}\nif (run === undefined) throw new Error(\"Automation cancellation response was invalid.\");","handlingStrategy":"type-guard","validationCode":"const value = await payload(`/v1/automation-runs/${encodeURIComponent(runId)}/cancel`, { method: \"POST\" });\nif (!hasRun(value)) { /* treat as inconclusive; refetch run state */ }","typeGuard":"function hasRun(v: unknown): v is { run: unknown } {\n  return typeof v === \"object\" && v !== null && \"run\" in v;\n}","tryCatchPattern":"const cancelRun = useCancelAutomationRun();\ncancelRun.mutate(runId, {\n  onError: (err) => {\n    if (err instanceof Error && err.message.includes(\"cancellation response was invalid\")) {\n      // inconclusive: poll run status instead of assuming failure\n      refetchRun(runId);\n    }\n  },\n});","preventionTips":["Treat cancel as idempotent and confirm via polling the run status afterward.","Add server contract tests for the cancel endpoint including the already-finished case.","Return the run object even when the run already completed.","Use onError (not throw) so the runs list can refresh to the authoritative state."],"tags":["api-contract","zod","automations"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}