{"record":{"id":"826b06adc8cce78c","repo":"can1357/oh-my-pi","slug":"manualtriageconflict-delivery-existing-state","errorCode":null,"errorMessage":"ManualTriageConflict(delivery, existing.state)","messagePattern":"ManualTriageConflict\\(delivery, existing\\.state\\)","errorType":"exception","errorClass":"ManualTriageConflict","httpStatus":null,"severity":"error","filePath":"python/robomp/src/manual_triage.py","lineNumber":100,"sourceCode":"            \"full_name\": repo.full_name,\n            \"default_branch\": repo.default_branch,\n            \"clone_url\": repo.clone_url,\n            \"private\": repo.private,\n        },\n    }\n\n\nasync def enqueue_manual_triage(*, db: Database, github: GitHubBackend, repo_full: str, number: int) -> str:\n    \"\"\"Fetch the issue from GitHub and queue it for the worker pool.\n\n    Returns the delivery_id. A row may already exist from a previous manual\n    triage; inactive rows are replaced so the fresh payload (and reset attempt\n    counter) wins. Active rows are left intact.\n    \"\"\"\n    delivery = manual_delivery_id(repo_full, number)\n    existing = db.get_event(delivery)\n    if existing is not None and existing.state in (\"queued\", \"running\"):\n        raise ManualTriageConflict(delivery, existing.state)\n\n    payload = await build_issues_opened_payload(github, repo_full, number)\n    replaced = db.replace_event_if_state_in(\n        delivery_id=delivery,\n        event_type=\"issues\",\n        repo=repo_full,\n        issue_key=issue_key(repo_full, number),\n        payload=payload,\n        state=\"queued\",\n        allowed_existing_states=INACTIVE_EVENT_STATES,\n    )\n    if not replaced:\n        current = db.get_event(delivery)\n        state = current.state if current is not None else \"active\"\n        raise ManualTriageConflict(delivery, state)\n    return delivery\n\n","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/python/robomp/src/manual_triage.py#L82-L118","documentation":"ManualTriageConflict raised by enqueue_manual_triage when a previous triage event with the same delivery id is still active — i.e. its recorded state is \"queued\" or \"running\". The function refuses to enqueue a duplicate of an in-flight run.","triggerScenarios":"Calling enqueue_manual_triage (via _go or api_trigger) twice for the same repo#issue before the first run finishes.","commonSituations":"Double-clicking the triage trigger in the dashboard, re-running an API trigger while a prior manual triage is still executing.","solutions":["Wait for the existing run to reach a terminal state (done/failed/skipped) before re-triggering","Use await_terminal_state(delivery_id) to wait, or poll the event state","If a stale run is stuck, reset/force-close the existing event row, then re-enqueue"],"exampleFix":"// before: unguarded re-trigger\nawait enqueue_manual_triage(github, db, repo, num)\n// after: wait out any active run first\ntry:\n    await await_terminal_state(db, delivery_id)\nexcept ManualTriageTimeout:\n    pass\nawait enqueue_manual_triage(github, db, repo, num)","handlingStrategy":"try-catch","validationCode":"existing = db.get_event(manual_delivery_id(repo_full, number))\nif existing is not None and existing.state in (\"queued\", \"running\"):\n    raise RuntimeError(f\"triage already {existing.state} for {repo_full}#{number}\")","typeGuard":null,"tryCatchPattern":"try:\n    delivery = await enqueue_manual_triage(github, db, repo_full, number)\nexcept ManualTriageConflict as e:\n    log.info(\"triage already %s; reusing delivery %s\", e.args, e.args[0])\n    delivery = e.args[0]","preventionTips":["Disable/dedupe the trigger button while a run is active","Track active deliveries per repo#issue in the caller","Wait for terminal state before allowing re-trigger"],"tags":["conflict","concurrency","duplicate"],"backgroundTag":"duplicate-operation-conflict","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}