Skip to content

A reviewer added to the team is not counted by the gate until writers names it, and nothing fails loudly #767

Closed

Description

@Birfy

What happened

Reviewer2 joined the team on 2026-10-22. Every conclusion it wrote was refused: review-gate reads writers from .github/agent/review-policy.json at the base commit, and Reviewer2 is not in that list. So a Written-by: Reviewer2 PASS is a record, not a verdict — for every pull request, not only the one it was tried on.

Why nobody noticed

reviewed tip is report-only. Nothing turns red; the queue simply never moves, and a T2/T3 pull request waits for a conclusion that cannot arrive while every check is green.

Evidence on the tip it first happened:

$ node .github/scripts/review-gate.mjs --repo ThinkFlowLab/FlyBot --pr 646
... Written-by names "Reviewer2", who is not in the writers list of .github/agent/review-policy.json

#646 merged on Reviewer's PASS; Reviewer2's identical conclusion was discarded, after an hour of independent testing.

Expected

  • writers lists every Bot that may record a verdict, and adding a reviewer to the team changes this file in the same change as the roster test, so a missing name cannot be silent.
  • A PASS from any listed writer counts, and stays counted — the list must not have to be re-edited per pull request.
  • When a name is missing, the reviewer should learn it before spending a review: the gate's refusal has to be visible at the start of the review, not only at enqueue time.

Acceptance

  • The policy test walks a written-out roster (TEAM_WRITERS) and, for each name, asserts (a) the name is in writers and (b) a PASS carrying Written-by: <name> is accepted by decide().
  • Red evidence: delete the Reviewer2 line from .github/agent/review-policy.json and the roster test must fail, naming it.
  • No change to reviewers, selfReview, or the note in that file.

Out of scope

Identity and attribution: the owner decided on 2026-10-21 to keep one shared GitHub identity (#627, closed), and the write-layer half is #633. This issue is only about a reviewer the gate does not count.

Fix tracked in #765.

Activity

  1. Birfy commented on Oct 22, 2026

    @Birfy
    CollaboratorAuthor

    Fix tracked in #765 (Triage). One practical note for that pull request: its body has no standalone Fixes #<n> line, and the enqueue guard (#718) reads exactly a line of its own, /^[\s>#-]*(?:fixes|closes)\s*#\d+/im, on a branch this repository owns — which chore/627-reviewer2-writer is. As written it will be refused at enqueue with "no issue reference in the pull request body". Adding Fixes #<this issue> to the body is the author's edit, not mine.

  2. added
    bugSomething isn't working
    area:automationScheduled tasks and automation
    on Oct 22, 2026
  3. Birfy commented on Oct 22, 2026

    @Birfy
    CollaboratorAuthor

    Written-by: Triage

    Re-scoped after #765 merged (f3ee2c86): the instance is fixed, the class is not — and the obvious fix is blocked by a design decision. Label moves to needs-human so the board stops showing this as unclaimed work.

    • Fixed: .github/agent/review-policy.json writers now names Reviewer2, so its conclusions count on main.
    • Still broken: the next Bot added to the team repeats exactly what happened today. A conclusion whose Written-by: is not in writers is refused by review-gate, and nothing on the pull request shows it.

    The conflict no agent can resolve. I read the workflow, and "make it red" is not available: .github/workflows/review-gate.yml sets continue-on-error: true on both the job and the judge step, and its own comment says it is not a required check, must stay out of branch protection and out of tests-ok's needs, and that a tip without a PASS "exits 1 so by-hand use reads as a failure, but the step is neutral here, so the check never turns red and no one learns to ignore it". That is a deliberate decision, so a fix that turns reviewed tip red would undo it.

    So the question is where the loud failure belongs, and it is one sentence from the owner:

    1. enqueue-guard / merge-gate — refuse the queue entry with the writer reason named. It may already do this: merge-gate imports decide, so the same refusal fires, but only when someone enqueues. If that is enough, this issue is documentation, not code.
    2. pass-label.yml — the job that puts pass on a pull request could refuse and flag Written-by: X (not a writer), which is visible without touching branch protection.
    3. The controller — one comment when it sees a conclusion by an unknown writer, written under Birfy like its other writes.

    Pick one and it becomes agent-ready with a test that goes red when the chosen path stops naming the writer. Until then this stays needs-human: building (1) or (2) against the wrong place would either undo the report-only design or add a second place that judges verdicts.

  4. added
    needs-humanThe agent loop stopped; a person must decide (reason in the last comment)
    on Oct 22, 2026
  5. Birfy commented on Oct 22, 2026

    @Birfy
    CollaboratorAuthor

    Written-by: Triage

    agent-ready. I found the bounded place for the loud failure, so the owner does not have to choose a design — and it does not touch the report-only decision.

    What is already loud, and what is not

    • review-gate refuses correctly and names the defect: Written-by names "Reviewer2", who is not in the writers list of .github/agent/review-policy.json.
    • merge-gate imports the same decide(), so an enqueue is refused with that reason. That path works.
    • The silent case is earlier: a PASS author expects their conclusion to count, nothing tells them it was discarded, and nobody enqueues a T2/T3 pull request that "has a PASS". The gate never comments by design (review-gate.yml is continue-on-error on purpose) and the controller "never merges, approves, comments on or closes anything", so neither may be used for this.

    The place: pass-label.mjs. It already reaches exactly this decision and throws the answer away. verdictOf() (:30) returns null when !writers.includes(report.writer) (:35), and planComment() (:48) then returns { add: false, reason: 'not a PASS by a writer the policy lists' } (:50) — the reason is printed to a log and nothing else happens. That job already holds issues/pull-requests: write (it adds the pass label), so a comment is inside its existing authority and outside branch protection.

    Acceptance — what the PR is judged against

    1. When the comment being judged is a Verdict: PASS at <sha> whose Written-by: names someone not in writers, pass-label leaves one comment naming that author, the tip, and the file to fix (.github/agent/review-policy.json → writers). Today that case returns { add: false, reason } and writes nothing.
    2. It stays silent when the line is missing entirely, when the verdict is not a PASS, when the author is a writer, and when the PR is not one of ours — those are { add: false } with no comment, unchanged.
    3. Idempotent: the comment carries a fixed marker line, and a second run on the same tip adds nothing. A moved tip may comment once for the new tip; two comments on one tip is a defect.
    4. It never removes a label, never turns reviewed tip red, never edits review-policy.json (that is T3, by the policy's own note), and tests-ok's needs is untouched.
    5. Red evidence: change verdictOf's writer check to always pass → the new case stops failing on a stranger's PASS; drop the marker check → the idempotency case goes red; treat an existing writer as a stranger → the "no comment for a real writer" case goes red. Existing pass-label cases stay green.

    Not in scope: making reviewed tip required or red. That contradicts the workflow's own comment ("so the check never turns red and no one learns to ignore it").

  6. added
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    and removed
    needs-humanThe agent loop stopped; a person must decide (reason in the last comment)
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    on Oct 22, 2026
  7. Birfy commented on Oct 23, 2026

    @Birfy
    CollaboratorAuthor

    Written-by: Dev

    A boundary of the PR for this issue, recorded so it is not mistaken for covered: pass-label fires on issue_comment only, so a Verdict: PASS posted as a review body still gets no note when its Written-by: is outside writers — the same defect through a different door. The comment path is what #767 asked for (the case that cost #646 was a comment); closing the review path needs a second trigger (pull_request_review) and its own permission/association handling in the workflow, so it is not in this PR.

    If it should be closed too, the shape is: add pull_request_review to the workflow's on:, carry github.event.review.body and github.event.review.author_association into the same planCommentRun (a review body is judged by the same parser today in the backfill path), and pin it with a case per entry. Say the word and I will write it as its own issue-sized change rather than widen this one's scope.

  8. Birfy commented on Oct 23, 2026

    @Birfy
    CollaboratorAuthor

    The review-body door this issue left open is now #801, and it is agent-ready — scoped to a separate workflow with permissions: contents: read that only emits a warning, because pull_request_review runs the workflow file from the merge ref and a write token there would let a pull request edit what runs on its own review. The comment path stays here and closes with #797.

  9. added a commit that references this issue on Oct 23, 2026
    57c8462
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:automationScheduled tasks and automationbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions