can1357/oh-my-pi · warning · HTTPException

retry requires 'delivery_id' or 'issue'

Error message

retry requires 'delivery_id' or 'issue'

What it means

The retry endpoint requires an identifier to act on and raises HTTP 400 'retry requires delivery_id or issue' when the request body contains neither. The endpoint accepts either a stored delivery_id or an issue reference (`owner/repo#N`) to locate the event; an empty or non-string value for both leaves it with no target, so the request is rejected outright.

Source

Thrown at python/robomp/src/server.py:655

                status_code=202,
            )

        # mode == "retry"
        if isinstance(delivery_id, str) and delivery_id:
            target = delivery_id
        elif isinstance(issue_ref, str) and issue_ref:
            try:
                repo_full, number = parse_issue_ref(issue_ref)
            except InvalidIssueRef as exc:
                raise HTTPException(400, str(exc)) from exc
            if not cfg.allows(repo_full):
                raise HTTPException(403, f"{repo_full} not in ROBOMP_REPO_ALLOWLIST")
            row = db.latest_event_for_issue(make_issue_key(repo_full, number))
            if row is None:
                raise HTTPException(404, f"no retryable stored event for {repo_full}#{number}")
            target = row.delivery_id
        else:
            raise HTTPException(400, "retry requires 'delivery_id' or 'issue'")

        event = db.get_event(target)
        if event is None:
            raise HTTPException(404, f"unknown delivery {target}")
        if not db.requeue_event(target, from_states=INACTIVE_EVENT_STATES):
            raise HTTPException(409, f"delivery {target} is {event.state}; only inactive events can be retried")
        pool.wake()
        log.info("manual retry", extra={"delivery": target})
        return JSONResponse(
            {"delivery": target, "state": "queued", "mode": "retry"},
            status_code=202,
        )

    @app.post("/api/cancel")
    async def api_cancel(
        request: Request,
        payload: dict[str, Any] = Body(...),
        x_robomp_token: str | None = Header(None, alias="X-Robomp-Replay-Token"),

View on GitHub (pinned to 9690622007)

Solutions

  1. Include a non-empty string 'delivery_id' in the JSON body: {"delivery_id": "<id>"}
  2. Alternatively include a well-formed issue reference: {"issue": "owner/repo#123"}
  3. Ensure the request has Content-Type: application/json and the body is actually sent
  4. Convert numeric/UUID delivery values to strings before sending

Example fix

// before
await fetch('/retry', { method: 'POST', body: JSON.stringify({}) });
// after
await fetch('/retry', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ delivery_id: String(deliveryId) }) });
Defensive patterns

Strategy: validation

Validate before calling

// Ensure exactly one valid target field before calling retry
const body = deliveryId
  ? { delivery_id: String(deliveryId) }
  : { issue: `${owner}/${repo}#${number}` };
if (!body.delivery_id && !body.issue) throw new Error('retry needs delivery_id or issue');

Type guard

function hasRetryTarget(b: unknown): b is { delivery_id?: string; issue?: string } {
  const o = b as Record<string, unknown>;
  return (typeof o?.delivery_id === 'string' && o.delivery_id.length > 0) ||
         (typeof o?.issue === 'string' && o.issue.length > 0);
}

Try / catch

try {
  await api.retry(body);
} catch (err) {
  if (err.status === 400 && /retry requires/.test(err.message)) {
    logger.error('retry call missing target', { body });
  } else throw err;
}

Prevention

When it happens

Trigger: POSTing to the retry endpoint with a body missing both 'delivery_id' and 'issue' — e.g. `{}`, `{'delivery_id': ''}` (empty string), `{'delivery_id': 123}` (not a string), or `{'issue': None}`. The guard is `isinstance(delivery_id, str) and delivery_id` / `isinstance(issue_ref, str) and issue_ref`.

Common situations: Forgetting the JSON body entirely or sending it with the wrong Content-Type so the body parses to empty; a client serializing an undefined/null variable; integer delivery IDs being sent where a string is required; renaming the parameter in a client without updating it server-side.

Understand the failure class

Background: Missing required parameter errors: what 'X is required' and 'the required X param is missing' mean, and how to fix them — this error's family across 27 libraries.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/be391a14a7aafa30. Report an issue: GitHub.