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
- Wait for the delivery to reach an inactive state (finished/failed), then retry
- Inspect the event's current state (message body tells you; or query events/logs) before retrying
- If the run is genuinely hung, cancel it via the cancel endpoint (requires state 'running'), then retry once inactive
- 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
- Poll the event state and only issue retries on inactive states
- Use backoff — active runs can take minutes
- If a run is truly stuck, cancel it (cancel requires state 'running') before retrying
- Never fire retries from multiple clients concurrently for the same delivery
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
- {str(exc)} (ManualTriageConflict)
- delivery {delivery_id} is {event.state}; only running delive
- ManualTriageConflict(delivery, existing.state)
- ManualTriageConflict(delivery, state)
- native spelling thread stopped
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/bd91ee10067ea4bf.
Report an issue: GitHub.