Problem
Today `IssuePoller` picks up any issue assigned to the configured GitHub user, regardless of who did the assignment. That means a teammate (or any repo admin) can drop an issue on me and the dev pipeline will fire automatically against it. The intended use case is "self-assigned todo list drives the bot" — external assignments become implicit command-and-control over my local compute.
Proposed behavior
Only handle an issue if the most recent `assigned` event targeting the operator was performed by the operator themselves. Any other assigner (including bots) is filtered out — the issue stays visible on GitHub for me to see, but `ctrlrelay` doesn't auto-run on it.
Out of scope: issues where I was auto-assigned via CODEOWNERS, GitHub App, or webhook — those still shouldn't trigger. The filter treats those like any other "not me" assigner.
API notes for the implementer
GitHub's issue payload has `assignees[]` (current state) but no `assigner`. To find who performed the assignment, hit the issue events endpoint:
```
GET /repos/{owner}/{repo}/issues/{issue_number}/events
```
Filter for events where:
- `event == "assigned"`
- `assignee.login == config.username`
Take the most recent such event (events are chronological) and compare `actor.login == config.username`. If they match, accept. Otherwise, skip.
The poller already calls `gh issue list --assignee ` once per repo per poll interval. Adding the events check means one extra API call per newly-detected issue (not per poll — only per new issue, which is rare). Acceptable overhead.
Suggested implementation sketch
- Add `list_assignment_events(repo, issue_number)` to `core/github.py` wrapping `gh api /repos/.../issues/.../events --jq '[.[] | select(.event=="assigned")]'`.
- In `IssuePoller.poll()`, after an issue is found new-to-us but before it's added to `new_issues`, call the above and apply the self-assignment check. If it fails, log `poll.issue.foreign_assignment` with `repo`, `number`, `assigner_login` and do still mark it seen (otherwise every poll would re-check it and emit duplicate log lines / API calls).
- Config escape hatch: a per-repo `automation.accept_foreign_assignments: bool = false` flag in `RepoConfig` so specific repos can opt back into the old "any assignment counts" behavior if some team needs it.
Tests
- Fresh issue self-assigned → poller picks it up.
- Fresh issue assigned by a different user → poller logs `foreign_assignment`, does NOT add to `new_issues`, DOES mark seen.
- Issue first self-assigned, later re-assigned (someone else reassigns to me) → the most-recent `assigned` event is by someone else → filtered out.
- Issue assigned by `github-actions[bot]` → filtered out (same rule — we don't trust bot assignment).
- Per-repo `accept_foreign_assignments: true` → the check is bypassed.
Not addressed here
Security-wise this is defense-against-annoyance, not hardening — a teammate with write access to a configured repo can still push branches, trigger CI, etc. The filter stops the dev pipeline from spontaneously executing agent-backed work against my machine, which is the specific concern.
Problem
Today `IssuePoller` picks up any issue assigned to the configured GitHub user, regardless of who did the assignment. That means a teammate (or any repo admin) can drop an issue on me and the dev pipeline will fire automatically against it. The intended use case is "self-assigned todo list drives the bot" — external assignments become implicit command-and-control over my local compute.
Proposed behavior
Only handle an issue if the most recent `assigned` event targeting the operator was performed by the operator themselves. Any other assigner (including bots) is filtered out — the issue stays visible on GitHub for me to see, but `ctrlrelay` doesn't auto-run on it.
Out of scope: issues where I was auto-assigned via CODEOWNERS, GitHub App, or webhook — those still shouldn't trigger. The filter treats those like any other "not me" assigner.
API notes for the implementer
GitHub's issue payload has `assignees[]` (current state) but no `assigner`. To find who performed the assignment, hit the issue events endpoint:
```
GET /repos/{owner}/{repo}/issues/{issue_number}/events
```
Filter for events where:
Take the most recent such event (events are chronological) and compare `actor.login == config.username`. If they match, accept. Otherwise, skip.
The poller already calls `gh issue list --assignee ` once per repo per poll interval. Adding the events check means one extra API call per newly-detected issue (not per poll — only per new issue, which is rare). Acceptable overhead.
Suggested implementation sketch
Tests
Not addressed here
Security-wise this is defense-against-annoyance, not hardening — a teammate with write access to a configured repo can still push branches, trigger CI, etc. The filter stops the dev pipeline from spontaneously executing agent-backed work against my machine, which is the specific concern.