can1357/oh-my-pi · warning · HTTPException

delivery {target} is {event.state}; only inactive events can

Error message

delivery {target} is {event.state}; only inactive events can be retried

What it means

The delivery exists but is still active, so the retry endpoint returns HTTP 409 'delivery <target> is <state>; only inactive events can be retried'. db.requeue_event(target, from_states=INACTIVE_EVENT_STATES) only transitions events whose current state is inactive (e.g. failed/skipped); when the conditional requeue returns False the server surfaces the event's actual state so you know why the retry was refused. Retrying a running or queued delivery would duplicate work.

Source

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

        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"),
    ) -> JSONResponse:
        """Stop a running event. The omp subprocess is killed; the row lands in
        `failed` with `cancelled by operator` as the error.
        """
        bag = request.app.state.bag
        cfg: Settings = bag["settings"]

View on GitHub (pinned to 9690622007)

Solutions

  1. Wait for the delivery to reach an inactive state (finished/failed), then retry
  2. Inspect the event's current state (message body tells you; or query events/logs) before retrying
  3. If the run is genuinely hung, cancel it via the cancel endpoint (requires state 'running'), then retry once inactive
  4. Add polling with backoff: only re-issue the retry after the state endpoint reports an inactive state

Example fix

// before: fire-and-forget retry
await api.retry(deliveryId);
// after: retry only when inactive
const evt = await api.getEvent(deliveryId);
if (!['running', 'queued'].includes(evt.state)) await api.retry(deliveryId);
Defensive patterns

Strategy: retry

Validate before calling

// Only retry when the event is in an inactive state
const evt = await api.getEvent(deliveryId);
const INACTIVE = ['failed', 'skipped', 'done', 'cancelled']; // match server INACTIVE_EVENT_STATES
if (!evt || !INACTIVE.includes(evt.state)) {
  throw new Error(`delivery ${deliveryId} is ${evt?.state ?? 'unknown'}; not retryable yet`);
}

Type guard

function isInactiveEvent(e: { state: string }): boolean {
  return !['queued', 'running'].includes(e.state);
}

Try / catch

try {
  await api.retry({ delivery_id: id });
} catch (err) {
  if (err.status === 409 && /only inactive events can be retried/.test(err.message)) {
    logger.info('still active; will retry after it becomes inactive', { id, state: err.message });
    await pollUntilInactive(id, { timeoutMs: 15 * 60_000 });
    await api.retry({ delivery_id: id });
  } else throw err;
}

Prevention

When it happens

Trigger: POSTing to the retry endpoint with a delivery_id whose event.state is still 'running', 'queued', or otherwise outside INACTIVE_EVENT_STATES — requeue_event returns False and the server raises the 409 with the live state embedded in the message.

Common situations: Clicking 'retry' on a job that is actually still executing (dashboard state stale); a slow agent run that looks hung but is still active; automation retrying on a timer without checking state first; double-firing the retry while the first attempt is mid-flight.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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