{"record":{"id":"a62df6dc8dafecff","repo":"can1357/oh-my-pi","slug":"cancel-requires-delivery-id","errorCode":null,"errorMessage":"cancel requires 'delivery_id'","messagePattern":"cancel requires 'delivery_id'","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"python/robomp/src/server.py","lineNumber":684,"sourceCode":"            status_code=202,\n        )\n\n    @app.post(\"/api/cancel\")\n    async def api_cancel(\n        request: Request,\n        payload: dict[str, Any] = Body(...),\n        x_robomp_token: str | None = Header(None, alias=\"X-Robomp-Replay-Token\"),\n    ) -> JSONResponse:\n        \"\"\"Stop a running event. The omp subprocess is killed; the row lands in\n        `failed` with `cancelled by operator` as the error.\n        \"\"\"\n        bag = request.app.state.bag\n        cfg: Settings = bag[\"settings\"]\n        _require_trigger_token(cfg, x_robomp_token)\n\n        delivery_id = payload.get(\"delivery_id\")\n        if not isinstance(delivery_id, str) or not delivery_id:\n            raise HTTPException(400, \"cancel requires 'delivery_id'\")\n\n        db: Database = bag[\"db\"]\n        event = db.get_event(delivery_id)\n        if event is None:\n            raise HTTPException(404, f\"unknown delivery {delivery_id}\")\n        if event.state != \"running\":\n            raise HTTPException(\n                409, f\"delivery {delivery_id} is {event.state}; only running deliveries can be cancelled\"\n            )\n\n        pool: WorkerPool = bag[\"pool\"]\n        fired = await pool.cancel_event(delivery_id)\n        log.info(\n            \"manual cancel\",\n            extra={\"delivery\": delivery_id, \"fired\": fired, \"state\": event.state},\n        )\n        return JSONResponse(\n            {\"delivery\": delivery_id, \"fired\": fired, \"previous_state\": event.state},","sourceCodeStart":666,"sourceCodeEnd":702,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/python/robomp/src/server.py#L666-L702","documentation":"The cancel endpoint requires a delivery id and raises HTTP 400 'cancel requires delivery_id' when the request body lacks one. Cancelling targets a specific running event; without a non-string delivery_id (or an empty one) there is nothing to cancel, and unlike retry there is no issue-reference alternative, so the request is rejected immediately after token authentication (`_require_trigger_token`).","triggerScenarios":"POSTing to the cancel endpoint with a body missing 'delivery_id', an empty string value, a non-string value (number/null), or a body that failed to parse into `payload` — checked at server.py:684: `if not isinstance(delivery_id, str) or not delivery_id`.","commonSituations":"Client sending the id under a different key name ('id', 'delivery'); forgetting the JSON body; frameworks dropping the body on DELETE-style calls; passing a numeric database id where the string delivery id is expected.","solutions":["Send a JSON body with a non-empty string: {\"delivery_id\": \"<id>\"}","Ensure Content-Type: application/json and that the body is actually transmitted","Convert the delivery value to a string before sending","Include the trigger token header (x-robomp-token) so auth passes before validation — though this error specifically means the id field itself is missing"],"exampleFix":"// before\nawait fetch('/cancel', { method: 'POST', body: JSON.stringify({ id: 42 }) });\n// after\nawait fetch('/cancel', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-robomp-token': token }, body: JSON.stringify({ delivery_id: String(evt.deliveryId) }) });","handlingStrategy":"validation","validationCode":"// Ensure a non-empty string delivery_id is present before calling cancel\nif (typeof deliveryId !== 'string' || deliveryId.length === 0) {\n  throw new Error('cancel needs a non-empty string delivery_id');\n}","typeGuard":"function isDeliveryId(v: unknown): v is string {\n  return typeof v === 'string' && v.trim().length > 0;\n}","tryCatchPattern":"try {\n  await api.cancel({ delivery_id: deliveryId });\n} catch (err) {\n  if (err.status === 400 && /cancel requires/.test(err.message)) {\n    logger.error('cancel call missing delivery_id', { deliveryId });\n  } else throw err;\n}","preventionTips":["Use the exact key 'delivery_id' (not 'id' or 'delivery') in the cancel body","Send string values — numeric ids are rejected","Always attach the body and Content-Type: application/json to cancel requests","Centralize cancel in one client helper that validates the id and token header before sending"],"tags":["http-400","bad-request","missing-parameter","cancel","validation"],"backgroundTag":"missing-required-parameter","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}