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
- Read the exception message in the 400 response body — it states the specific triage precondition that failed
- Compare your request payload against what a genuine webhook delivery sends and add any missing fields
- Check the repo's triage configuration for the failing rule mentioned in the message
- 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
- Mirror the exact payload shape of genuine webhook deliveries when calling the trigger API manually
- Re-read the 400 message after every robomp upgrade — triage input expectations can change
- Keep triage configuration for each allowlisted repo valid and present
- Test manual triggers against a staging repo before using them in production
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
- retry requires 'delivery_id' or 'issue'
- cancel requires 'delivery_id'
- invalid workspace_key {workspace_key!r}
- workspace_key does not match repo
- invalid json: {exc}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/8003bf53cab1983e.
Report an issue: GitHub.