From 5fab0683882f0cfd969a6cc073454b30d9ed3d8f Mon Sep 17 00:00:00 2001 From: SMTP Relay & Email - Free Send <60409540+msgwing@users.noreply.github.com> Date: Mon, 17 Aug 2026 23:03:23 +0200 Subject: [PATCH] feat(ci): an approval inbox that only carries finished work Two faults, one fix. The discovery bots assigned every raw candidate to the owner, so the inbox filled with things that were not ready. Reviewing that first queue found eight leads and zero actionable ones: five threads closed and resolved, two dead since 2022, and one the owner had already answered six days earlier. An inbox of unfinished work is an inbox that stops being read. And none of it notified anyway. GitHub suppresses notifications for the actor who performed the action, so assigning the owner with the owner's own token emailed nobody. Verified against the event log: actor msgwing, assignee msgwing. So the bots no longer assign. They queue, labelled, and that is all. Labelling an issue `do-akceptacji` once it is genuinely finished hands it over: ready-for-approval.yml assigns the owner - as github-actions[bot], a different actor, so the mail actually goes out - and posts the instruction in Polish, because the owner is the only person reading it. The payload inside each issue stays in English. It is going to be pasted into an English thread or a directory entry, and translating it would mean translating it back. Rejecting is a first-class path: remove the label and say what is wrong, rather than fixing it by hand. The correction then lands where the fault was, instead of in one issue that gets fixed and forgotten. --- .github/workflows/listings-radar.yml | 10 +-- .github/workflows/outreach-watch.yml | 13 ++-- .github/workflows/ready-for-approval.yml | 81 ++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/ready-for-approval.yml diff --git a/.github/workflows/listings-radar.yml b/.github/workflows/listings-radar.yml index 90c2684..ceee136 100644 --- a/.github/workflows/listings-radar.yml +++ b/.github/workflows/listings-radar.yml @@ -302,16 +302,18 @@ jobs: 'queued again.', ].join('\n'); - // Assigned so GitHub emails a direct link. Same reasoning as in - // outreach-watch.yml: a queued draft that nobody is told about - // is a draft nobody submits. + // Deliberately not assigned. A raw candidate is not finished + // work, and the owner's inbox is for finished work only - + // reviewing the first assigned queue found eight leads and zero + // actionable ones. It is labelled `do-akceptacji` after + // qualification, and ready-for-approval.yml does the assigning + // then, which is also the only way the notification fires. const created = await github.rest.issues.create({ owner: context.repo.owner, repo: context.repo.repo, title: `Listing: ${c.full_name}`.slice(0, 200), body, labels: [LABEL], - assignees: [context.repo.owner], }); core.notice(`Queued #${created.data.number} — ${c.url}`); } diff --git a/.github/workflows/outreach-watch.yml b/.github/workflows/outreach-watch.yml index 0e2e76f..3680580 100644 --- a/.github/workflows/outreach-watch.yml +++ b/.github/workflows/outreach-watch.yml @@ -362,19 +362,18 @@ jobs: 'again.', ].join('\n'); - // Assigned, not just labelled. A draft that nobody is told - // about is a draft nobody posts, and the queue's whole value is - // speed - an answer four days late lands on a thread nobody is - // reading. GitHub emails the assignee with a direct link, which - // is the whole notification mechanism needed here: open the - // link, read the thread, post or close. + // Deliberately not assigned. A raw candidate is not finished + // work, and the owner's inbox is for finished work only - + // reviewing the first assigned queue found eight leads and zero + // actionable ones. It is labelled `do-akceptacji` after + // qualification, and ready-for-approval.yml does the assigning + // then, which is also the only way the notification fires. const created = await github.rest.issues.create({ owner: context.repo.owner, repo: context.repo.repo, title: `Outreach: ${c.title}`.slice(0, 200), body, labels: [LABEL], - assignees: [context.repo.owner], }); core.notice(`Queued #${created.data.number} — ${c.url}`); } diff --git a/.github/workflows/ready-for-approval.yml b/.github/workflows/ready-for-approval.yml new file mode 100644 index 0000000..4a19c17 --- /dev/null +++ b/.github/workflows/ready-for-approval.yml @@ -0,0 +1,81 @@ +name: Do akceptacji + +# Turns a finished piece of work into an email in the owner's inbox, in +# Polish, with the one action to take. +# +# Two problems this exists to solve, both found the hard way on 2026-08-17: +# +# 1. The discovery bots used to assign every raw candidate to the owner, +# so the inbox filled with things that were not ready. Reviewing that +# queue found eight leads and zero actionable ones - five threads +# closed and resolved, two dead since 2022, one already answered by +# the owner. Nothing should reach the inbox until it is finished. +# +# 2. GitHub suppresses notifications for the actor who performed the +# action. Assigning the owner using the owner's own token emails +# nobody, which is exactly what happened to those ten issues. This +# workflow runs as github-actions[bot], a different actor, so the +# assignment actually notifies. +# +# So: label an issue `do-akceptacji` once it is genuinely finished, and this +# assigns the owner and posts the Polish instruction. The label is the +# handover; the email is its side effect. + +on: + issues: + types: [labeled] + +permissions: + issues: write + +jobs: + assign: + if: github.event.label.name == 'do-akceptacji' + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v8 + with: + script: | + const owner = context.repo.owner; + const issue_number = context.issue.number; + + // Assigning is what sends the mail. Skip it if the owner is + // already assigned, so re-labelling does not silently do nothing + // and look like a failure. + const current = (context.payload.issue.assignees || []).map(a => a.login); + if (!current.includes(owner)) { + await github.rest.issues.addAssignees({ + owner, repo: context.repo.repo, issue_number, + assignees: [owner], + }); + core.notice(`Przypisano ${owner} — mail powinien wyjść.`); + } else { + core.warning( + `${owner} był już przypisany, więc GitHub nie wyśle powiadomienia ` + + `o przypisaniu. Komentarz poniżej i tak trafi na maila.`); + } + + // The comment carries the instruction. Polish, because the owner + // reads these; the draft or entry inside the issue stays in the + // language it will be posted in, which is English. + const body = [ + '## Gotowe do akceptacji', + '', + 'Ta sprawa jest skompletowana. Nic tu nie trzeba dopisywać ani poprawiać.', + '', + '**Co zrobić:** przeczytaj treść powyżej i wykonaj **jedną** czynność, którą opisuje —', + 'wklej gotową odpowiedź, zgłoś wpis albo kliknij przycisk. Potem zamknij to zgłoszenie.', + '', + '**Jeśli coś jest nie tak** — nie poprawiaj tego ręcznie. Zdejmij etykietę', + '`do-akceptacji` i napisz w komentarzu, co nie gra. Poprawka trafi tam, gdzie', + 'powstał błąd, żeby nie wrócił.', + '', + '---', + '', + '_Treść przeznaczona do publikacji jest po angielsku celowo — w takim języku zostanie', + 'wklejona. Ta instrukcja jest po polsku, bo czytasz ją tylko Ty._', + ].join('\n'); + + await github.rest.issues.createComment({ + owner, repo: context.repo.repo, issue_number, body, + });