can1357/oh-my-pi · error · HTTPException

{str(exc)} (ManualTriageError)

Error message

{str(exc)} (ManualTriageError)

What it means

The manual triage endpoint raised ManualTriageError, translated into HTTP 400 with the exception's message. This is the generic triage-failure case: the request reached the triage logic but that logic rejected it for reasons other than a conflict — invalid payload semantics, a triage rule refusing the issue, or a precondition failure. The '(ManualTriageError)' suffix in the log identifies the class.

Source

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

            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:
                raise HTTPException(400, str(exc)) from exc
            if not cfg.allows(repo_full):

View on GitHub (pinned to 9690622007)

Solutions

  1. Read the exception message in the 400 response body — it states the specific triage precondition that failed
  2. Compare your request payload against what a genuine webhook delivery sends and add any missing fields
  3. Check the repo's triage configuration for the failing rule mentioned in the message
  4. If it started after a robomp upgrade, check the changelog for triage input schema changes and update the caller

Example fix

// before: minimal hand-rolled payload
await fetch('/trigger', { method: 'POST', body: JSON.stringify({ issue: 12 }) });
// after: include the fields triage requires
await fetch('/trigger', { method: 'POST', body: JSON.stringify({ issue: 12, repo: 'owner/repo', delivery_id: evt.deliveryId, action: evt.action }) });
Defensive patterns

Strategy: validation

Validate before calling

// Validate the payload matches what real webhook deliveries carry before calling trigger
function canTrigger(p) {
  return typeof p.issue === 'number' &&
    typeof p.repo === 'string' && p.repo.includes('/') &&
    typeof p.delivery_id === 'string' && p.delivery_id.length > 0 &&
    typeof p.action === 'string';
}
if (!canTrigger(payload)) throw new Error('trigger payload incomplete');

Type guard

function isValidTriagePayload(p: unknown): p is { repo: string; issue: number; delivery_id: string; action: string } {
  const o = p as Record<string, unknown>;
  return typeof o?.repo === 'string' && typeof o?.issue === 'number' &&
    typeof o?.delivery_id === 'string' && typeof o?.action === 'string';
}

Try / catch

try {
  await api.trigger(payload);
} catch (err) {
  if (err.status === 400) {
    logger.error('triage rejected the request', { detail: err.message }); // message states the failed precondition
  } else throw err;
}

Prevention

When it happens

Trigger: POSTing to the trigger API with a payload whose triage input the triage implementation rejects (e.g. malformed triage data, an issue that triage rules disallow, missing triage context) — any path in the triage routine that raises ManualTriageError, caught at server.py:630 as `except ManualTriageError as exc: raise HTTPException(400, str(exc))`.

Common situations: Submitting a triage request for an issue type the triage rules do not handle; automation sending a hand-built payload that skips fields the real webhook always includes; schema drift after a robomp update changed what triage expects; operating on a repo whose triage config is missing or invalid.

Related errors


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