{"record":{"id":"8003bf53cab1983e","repo":"can1357/oh-my-pi","slug":"str-exc-manualtriageerror","errorCode":null,"errorMessage":"{str(exc)} (ManualTriageError)","messagePattern":"(.+?) \\(ManualTriageError\\)","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"python/robomp/src/server.py","lineNumber":630,"sourceCode":"            if not isinstance(issue_ref, str) or not issue_ref:\n                raise HTTPException(400, \"triage requires 'issue' = 'owner/repo#NN'\")\n            try:\n                repo_full, number = parse_issue_ref(issue_ref)\n            except InvalidIssueRef as exc:\n                raise HTTPException(400, str(exc)) from exc\n            if not cfg.allows(repo_full):\n                raise HTTPException(403, f\"{repo_full} not in ROBOMP_REPO_ALLOWLIST\")\n            try:\n                delivery = await enqueue_manual_triage(\n                    db=db,\n                    github=github,\n                    repo_full=repo_full,\n                    number=number,\n                )\n            except ManualTriageConflict as exc:\n                raise HTTPException(409, str(exc)) from exc\n            except ManualTriageError as exc:\n                raise HTTPException(400, str(exc)) from exc\n            except GitHubError as exc:\n                raise HTTPException(502, f\"github error: {exc.status} {exc.message}\") from exc\n            pool.wake()\n            log.info(\"manual triage\", extra={\"delivery\": delivery, \"issue\": f\"{repo_full}#{number}\"})\n            return JSONResponse(\n                {\"delivery\": delivery, \"state\": \"queued\", \"mode\": \"triage\"},\n                status_code=202,\n            )\n\n        # mode == \"retry\"\n        if isinstance(delivery_id, str) and delivery_id:\n            target = delivery_id\n        elif isinstance(issue_ref, str) and issue_ref:\n            try:\n                repo_full, number = parse_issue_ref(issue_ref)\n            except InvalidIssueRef as exc:\n                raise HTTPException(400, str(exc)) from exc\n            if not cfg.allows(repo_full):","sourceCodeStart":612,"sourceCodeEnd":648,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/python/robomp/src/server.py#L612-L648","documentation":"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.","triggerScenarios":"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))`.","commonSituations":"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.","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"],"exampleFix":"// before: minimal hand-rolled payload\nawait fetch('/trigger', { method: 'POST', body: JSON.stringify({ issue: 12 }) });\n// after: include the fields triage requires\nawait fetch('/trigger', { method: 'POST', body: JSON.stringify({ issue: 12, repo: 'owner/repo', delivery_id: evt.deliveryId, action: evt.action }) });","handlingStrategy":"validation","validationCode":"// Validate the payload matches what real webhook deliveries carry before calling trigger\nfunction canTrigger(p) {\n  return typeof p.issue === 'number' &&\n    typeof p.repo === 'string' && p.repo.includes('/') &&\n    typeof p.delivery_id === 'string' && p.delivery_id.length > 0 &&\n    typeof p.action === 'string';\n}\nif (!canTrigger(payload)) throw new Error('trigger payload incomplete');","typeGuard":"function isValidTriagePayload(p: unknown): p is { repo: string; issue: number; delivery_id: string; action: string } {\n  const o = p as Record<string, unknown>;\n  return typeof o?.repo === 'string' && typeof o?.issue === 'number' &&\n    typeof o?.delivery_id === 'string' && typeof o?.action === 'string';\n}","tryCatchPattern":"try {\n  await api.trigger(payload);\n} catch (err) {\n  if (err.status === 400) {\n    logger.error('triage rejected the request', { detail: err.message }); // message states the failed precondition\n  } else throw err;\n}","preventionTips":["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"],"tags":["http-400","bad-request","triage","validation","webhook"],"backgroundTag":"request-validation-failed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}