{"record":{"id":"16a793cef1f082b8","repo":"windmill-labs/windmill","slug":"inline-checkpoint-api-resp-status","errorCode":null,"errorMessage":"inline_checkpoint API ${resp.status}","messagePattern":"inline_checkpoint API (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"typescript-client/client.ts","lineNumber":2013,"sourceCode":"      const body = payload;\n      const chainTail = this._inlineChain.then(async () => {\n        const ctrl = new AbortController();\n        const t = setTimeout(() => ctrl.abort(), 10_000);\n        try {\n          const resp = await fetch(\n            `${OpenAPI.BASE}/w/${workspace}/jobs/wac/inline_checkpoint/${jobId}`,\n            {\n              method: \"POST\",\n              headers: {\n                \"Content-Type\": \"application/json\",\n                Authorization: `Bearer ${OpenAPI.TOKEN}`,\n              },\n              body,\n              signal: ctrl.signal,\n            },\n          );\n          if (!resp.ok) {\n            throw new Error(`inline_checkpoint API ${resp.status}`);\n          }\n          if (!errored) return undefined;\n          // The backend normalizes the failure before storing it, and hands\n          // back what it stored. Throwing from that, not from the marker posted\n          // above, is what makes this round and every replay read the same\n          // record even if the two sides ever disagree about how to build one.\n          //\n          // A backend predating the echo answers without a JSON body; the\n          // caller then falls back to the round trip of what it posted. A JSON\n          // body we cannot read is different: the normalized record may already\n          // be committed and we do not know what it says, so let this reject\n          // and take the suspend path, where the next round reads whatever the\n          // backend actually stored.\n          if (!(resp.headers.get(\"content-type\") ?? \"\").includes(\"json\")) {\n            return undefined;\n          }\n          return (await resp.json())?.failure;\n        } finally {","sourceCodeStart":1995,"sourceCodeEnd":2031,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/typescript-client/client.ts#L1995-L2031","documentation":"The client's inline_checkpoint integration posts checkpoint data to the Windmill API and throws this error when the HTTP response status is not OK (typescript-client/client.ts:2013). The status code is interpolated so you can see whether it was a 4xx (bad request, auth) or 5xx (server-side failure). A failed POST means the checkpoint record could not be stored, breaking resume/replay consistency.","triggerScenarios":"Calling a checkpoint wrapper when the inline_checkpoint endpoint returns non-2xx — 401/403 from expired or missing credentials, 404 because the route/job does not exist, 413 from an oversized payload, or 500 from a backend error.","commonSituations":"Token expired mid-run; calling checkpoints in a context where the endpoint is unavailable; posting checkpoint bodies above server limits; server/client version mismatch (older server lacking inline_checkpoint route).","solutions":["Read the interpolated status: 401/403 → refresh credentials; 404 → check server/client version match; 5xx → inspect backend logs.","Retry transient 502/503/504 responses with backoff.","Reduce the checkpoint payload size if you see 413.","Upgrade the Windmill server to a version matching the TypeScript client."],"exampleFix":"// before\nawait checkpoint(myHugeState); // 413 if state is huge\n// after\ntry {\n  await checkpoint({ summary: summarize(myHugeState) }); // smaller body\n} catch (e) {\n  if (/inline_checkpoint API 5\\d\\d/.test(String(e))) {\n    await sleep(1000);\n    await checkpoint({ summary: summarize(myHugeState) }); // retry transient\n  } else throw e;\n}","handlingStrategy":"retry","validationCode":"function isRetryableCheckpointError(e: unknown): boolean {\n  const m = /inline_checkpoint API (\\d{3})/.exec(String(e));\n  return !!m && Number(m[1]) >= 500;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await checkpoint(state);\n} catch (e) {\n  if (/inline_checkpoint API (502|503|504)/.test(String(e))) {\n    await sleep(1000);\n    return await checkpoint(state); // one retry for transient failures\n  }\n  throw e; // 401/403/404/413 need a fix, not a retry\n}","preventionTips":["Match client and server versions so the inline_checkpoint route exists.","Keep checkpoint payloads small to avoid 413.","Refresh long-lived tokens before long runs.","Retry 5xx with backoff; treat 4xx as a code/config bug."],"tags":["http","api","checkpoint"],"backgroundTag":"http-request-failed","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}