can1357/oh-my-pi · warning · HTTPException
{str(exc)} (ManualTriageConflict)
Error message
{str(exc)} (ManualTriageConflict) What it means
The manual triage endpoint raised ManualTriageConflict, which api_trigger translates into HTTP 409 with the exception's message. The server throws this when the triage operation cannot proceed because the issue or delivery is already being handled or is in a state that conflicts with a new triage run (a concurrency/state conflict, not a bad request). The suffix '(ManualTriageConflict)' in the logged message marks the exception class so operators can distinguish it from other triage failures.
Source
Thrown at python/robomp/src/server.py:628
if mode == "triage":
if not isinstance(issue_ref, str) or not issue_ref:
raise HTTPException(400, "triage requires 'issue' = 'owner/repo#NN'")
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")
try:
delivery = await enqueue_manual_triage(
db=db,
github=github,
repo_full=repo_full,
number=number,
)
except ManualTriageConflict as exc:
raise HTTPException(409, str(exc)) from exc
except ManualTriageError as exc:
raise HTTPException(400, str(exc)) from exc
except GitHubError as exc:
raise HTTPException(502, f"github error: {exc.status} {exc.message}") from exc
pool.wake()
log.info("manual triage", extra={"delivery": delivery, "issue": f"{repo_full}#{number}"})
return JSONResponse(
{"delivery": delivery, "state": "queued", "mode": "triage"},
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:View on GitHub (pinned to 9690622007)
Solutions
- Wait for the in-flight triage run on that issue/delivery to finish, then retry the trigger request
- Check the delivery/issue state (e.g. via the events/state API or logs) to confirm an active run before re-triggering
- Add client-side idempotency: dedupe by delivery_id/issue key so duplicate triggers are not sent
- If a previous run is wedged, cancel or reset that delivery's state, then trigger again
Example fix
// before: blind re-trigger loop for await (const d of deliveries) await api.trigger(d); // after: skip deliveries already in an active/conflicting state const active = await api.listActive(); const activeKeys = new Set(active.map(a => a.issueKey)); for (const d of deliveries) if (!activeKeys.has(d.issueKey)) await api.trigger(d.deliveryId);
Defensive patterns
Strategy: try-catch
Validate before calling
// Check no active/conflicting run exists before triggering
const evt = await api.getLatestEventForIssue('acme/widgets#42');
if (evt && ['queued', 'running'].includes(evt.state)) {
throw new Error(`triage already active for ${evt.deliveryId} (state=${evt.state})`);
} Type guard
function isConflictResponse(res: { status: number; body?: { detail?: string } }): boolean {
return res.status === 409 && typeof res.body?.detail === 'string';
} Try / catch
try {
const res = await api.trigger(payload);
} catch (err) {
if (err.status === 409 && /ManualTriageConflict/.test(err.message)) {
logger.warn('triage already in progress; backing off', { payload });
await Bun.sleep(backoffMs); // re-check state, then decide whether to re-trigger
} else throw err;
} Prevention
- Debounce/dedupe trigger requests by delivery_id or issue key in the client
- Check event state before re-triggering an issue
- Add jittered backoff to any automated re-trigger loop
- Never fire trigger requests from both a webhook redelivery and a manual button simultaneously
When it happens
Trigger: POSTing to the trigger API (with a valid trigger token and delivery payload) while the target issue/delivery is already locked by an in-flight triage run, or re-triggering triage for an event whose state conflicts with an active run. Raised by the inner triage call inside `except ManualTriageConflict as exc: raise HTTPException(409, str(exc))`.
Common situations: Double-clicking a 'triage' button so two requests race on the same issue; a webhook redelivery firing while the first triage is still running; automated retry scripts polling the trigger endpoint faster than triage completes; a stale UI session re-submitting an already-queued item.
Related errors
- delivery {target} is {event.state}; only inactive events can
- ManualTriageConflict(delivery, existing.state)
- ManualTriageConflict(delivery, state)
- {str(exc)} (ManualTriageError)
- delivery {delivery_id} is {event.state}; only running delive
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/634b84a8c65ed4f2.
Report an issue: GitHub.