{"record":{"id":"6f5ef431c830a7a9","repo":"zed-industries/zed","slug":"no-assignment-event-for-assignee-on-issue-issu","errorCode":null,"errorMessage":"No assignment event for {assignee} on issue #{issue_number}","messagePattern":"No assignment event for (.+?) on issue #(.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"script/github-guild-board.py","lineNumber":183,"sourceCode":"\ndef issue_comments(issue_number):\n    return github_rest_get_paginated(\n        f\"repos/{REPO_OWNER}/{REPO_NAME}/issues/{issue_number}/comments\"\n    )\n\n\ndef latest_assignment_time(issue_number, assignee):\n    events = github_rest_get_paginated(\n        f\"repos/{REPO_OWNER}/{REPO_NAME}/issues/{issue_number}/timeline\"\n    )\n    times = [\n        parse_dt(event[\"created_at\"])\n        for event in events\n        if event.get(\"event\") == \"assigned\"\n        and (event.get(\"assignee\") or {}).get(\"login\") == assignee\n    ]\n    if not times:\n        raise RuntimeError(\n            f\"No assignment event for {assignee} on issue #{issue_number}\"\n        )\n    return max(times)\n\n\ndef issue_closing_prs(issue_node_id, include_closed_prs=False):\n    # Caps at the first 20 closing PRs: callers only test presence or scan for a\n    # single guild-authored merge, so an issue exceeding this bound is not worth\n    # paginating.\n    data = github_graphql(\n        \"\"\"\n        query($issueId: ID!, $includeClosedPrs: Boolean!) {\n          node(id: $issueId) {\n            ... on Issue {\n              closedByPullRequestsReferences(first: 20, includeClosedPrs: $includeClosedPrs) {\n                nodes { merged author { login } }\n              }\n            }","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/zed-industries/zed/blob/bc538def4545534201bbfcac4e95ac34ea6501b6/script/github-guild-board.py#L165-L201","documentation":"latest_assignment_time paginates the issue's REST timeline (100 per page, all pages) and collects events where event == \"assigned\" and assignee.login equals the passed login. If none match it raises, because callers need that timestamp for staleness math. The comparison is exact, so the usual cause is that the login passed is not the login GitHub recorded on the event — or the event genuinely does not exist (migrated/old issues, unusual assignment paths).","triggerScenarios":"GET /repos/{owner}/{repo}/issues/{n}/timeline returns no matching event: the assignee argument is a bot/app slug or differs in case from the user login; the issue's assignee was set without an assigned timeline event (imported/migrated repos); the assignee account was deleted so event.assignee is None; the issue passed actually has no assignee events at all.","commonSituations":"Automation passes a member handle or display name that differs from the GitHub login; issues imported from another tracker without timeline history; renames of user accounts breaking old event logins; race where the issue is queried before the assignment event is indexed.","solutions":["Compare the passed assignee against the issue's assignees[].login from the API (exact, case-sensitive) before calling","Dump the timeline for that issue and check which login the assigned events actually recorded","Fall back to another timestamp (issue updated_at) when no event exists, if the staleness rules permit","For bot-mediated assignment, accept either the bot slug or the effective user login"],"exampleFix":"// before\nif not times:\n    raise RuntimeError(f\"No assignment event for {assignee} on issue #{issue_number}\")\nreturn max(times)\n\n// after\nif not times:\n    # Migrated issues and bot-assigned ones can lack a matching timeline event;\n    # updated_at is the closest safe proxy for when the issue last changed hands.\n    return parse_dt(issue[\"updated_at\"])\nreturn max(times)","handlingStrategy":"fallback","validationCode":"def issue_has_assignee_with_login(issue: dict, login: str) -> bool:\n    return any(a.get(\"login\") == login for a in issue.get(\"assignees\", []))","typeGuard":"def timeline_has_assigned_event(events: list, login: str) -> bool:\n    return any(\n        e.get(\"event\") == \"assigned\" and (e.get(\"assignee\") or {}).get(\"login\") == login\n        for e in events\n    )","tryCatchPattern":"try:\n    assigned_at = latest_assignment_time(issue_number, assignee)\nexcept RuntimeError:\n    # No timeline event (migrated issue, bot assignment, deleted account):\n    # fall back to the issue's last update time for staleness math.\n    assigned_at = parse_dt(issue[\"updated_at\"])","preventionTips":["Compare logins exactly against the API's assignees list before looking for events","Record assignment timestamps in your own store instead of reconstructing them from timelines","Handle missing events explicitly in staleness policies","Beware case differences and bot slugs when passing assignee names"],"tags":["github-api","rest","timeline","data-integrity","python"],"backgroundTag":null,"analyzedSha":"bc538def4545534201bbfcac4e95ac34ea6501b6","analyzedAt":"2026-08-16T07:30:46.435Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}