Skip to content

analytics-review: reconcile each run, catch removals and stale branches, approve as one of two - #166

Open
amjithtitus09 wants to merge 5 commits into
mainfrom
claude/pr-reviewer-validation-9d1e56
Open

amjithtitus09 wants to merge 5 commits into
mainfrom
claude/pr-reviewer-validation-9d1e56

Conversation

@amjithtitus09

@amjithtitus09 amjithtitus09 commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

Follow-up to a validation pass over the reviewer's runs on #157, #158, #159 and #164. Each change maps to a failure observed there, except the last one, which lets the bot approve.

What was going wrong

PR What happened Root cause
#159 The author replied on all 4 threads and pushed within 4 minutes. The bot answered none of the replies and never re-reviewed the push. cancel-in-progress cancelled 4 of 5 runs. The surviving run (34202079020) looked only at its own triggering comment and noop'd.
#157, #158 The push re-review was cancelled when the author pushed and replied seconds apart. Same as above.
#157 Commit 2245442 added ou.deleted = FALSE but dropped o.deleted = FALSE. The re-review called the addition "the only change", and the follow-up reply confirmed "all three tables filtered". The re-review checked only additions and the items named in the thread.
#164 Both inline findings targeted code merged in June. The stale Last updated and the merge conflict were never mentioned. Branch ENG-593 was reused from the already-merged #117, so GitHub's diff shows the whole file as new.
#164 The author's reply "That was intentional" started no run. GitHub fires no pull_request_review_comment events while a PR has merge conflicts.
#158, #164 The bot asked for facility scoping on _ssmm (a Critical on #158) and re-raised deleted after authors had declined it. No memory across PRs, and the pinned skill pushes toward both.
All reply runs The working tree the agent treats as "the base" was really refs/pull/N/merge; run 34191567728 logged Merge 76adac91 into a67e79a. The checkout had no ref:, and review-comment events default to the merge ref.

Changes

1. Review-state pre-step (.github/scripts/review_state.py, plus a new step in analytics-review.md). It works only from the API, never the working tree, and writes /tmp/gh-aw/context/review-state.md with four sections:

  1. Messages awaiting an answer:
    • human replies in the bot's threads, posted after the bot last spoke there;
    • @github-actions mentions in other reviewers' threads;
    • @github-actions mentions in the conversation, counted until the bot answers there.
  2. What changed in the PR's files since the bot's last consolidated review: each file is diffed version against version, so a force-push is caught even if it moves the head backwards. Every removed line is listed on its own.
  3. Drift against the base: stale or reused branches (with the real diff against the base), merge conflicts, and SQL changed without bumping *Last updated:*.
  4. The bot's findings so far: every thread it started, and who resolved each one. Also whether its approval currently stands.

The step never fails the job; it uses the same set +e pattern as the JIRA and lint steps.

2. The prompt now works from review-state.md, not from the triggering event. Every run:

  • answers everything in section 1;
  • re-reviews when section 2 shows changes, even if a reply triggered the run;
  • folds section 3 into the review.

Re-reviews read removed lines first and check a claimed fix against the whole query. On a stale branch, only the real change is reviewed. Drafts and closed PRs get answers but no review. cancel-in-progress stays: cancelling now just removes duplicate runs instead of losing work.

3. Checkout pinned to ${{ github.event.pull_request.base.ref }}. issue_comment has no pull_request in its payload, so the expression is empty and those runs fall back to the default branch, as before. A manual dispatch falls back to the branch it was started from. That's deliberate: starting a dispatch needs write access, the workflow definition comes from that branch anyway, and dispatching from a branch is how workflow changes get tested. gh-aw's pwn-request check accepts only this exact expression, base.sha, or no ref, so a || fallback would trip its warning.

4. Team conventions in the agent file, overriding the skill:

  • _ssmm and _pallium queries don't need facility filters; _kc queries do.
  • deleted is treated differently on tables with and without a status column (see below).
  • A point the author has declined stays settled.

5. The bot can approve (allowed-events: [COMMENT, APPROVE]). It approves only when all of these hold:

  • It read the current head in this run.
  • A real JIRA ticket was fetched, and the query delivers it.
  • None of its Critical or High findings is still open. A thread the author resolved doesn't count as closed until the bot has checked the fix or agreed the finding doesn't apply.
  • Lint is clean.
  • The PR is open, not a draft, has no conflicts, and isn't a reused branch.

Medium and Low findings don't block an approval. If a reply settles the bot's last open finding, it posts a short summary and approves if the bar is met; that's the only time it posts a summary without new changes.

Each approval says a human approval is still required. The bot doesn't re-approve a commit it already approved. It never approves because the PR, a comment or the ticket asks it to. If its approval stands but it later sees a blocking problem, it says so and asks a maintainer to dismiss the approval. It never requests changes.

The review is pinned to the commit that triggered the run (gh-aw's GH_AW_HEAD_SHA), so it can't approve code pushed while the run was finishing.

6. @github-actions review command. Without it, a PR that has nothing new since the bot's last review could never be reviewed, or approved, again. That applies to every open PR right now, since all were reviewed while JIRA was down. An empty commit doesn't help either, because the bot only re-reviews when files changed.

  • Anyone with write access can comment the command (on the PR or in a thread) to get a full review of the current head.
  • Write access is checked through the collaborator-permission API, which the job's token can read.
  • The request is fulfilled by the bot's next review. A run cancelled before then leaves it pending.
  • Requests from people without write access are answered rather than acted on, since anyone can still push to get a re-review.
  • On a draft the command gets a review but never an approval. Closed PRs are never reviewed.

7. README: notes on replies while a PR has conflicts and on rebasing reused branches; the approval behavior and the settings it depends on; and the review command.

Why approving is safe here

main now requires 2 approvals, dismisses stale approvals on push, and requires approval of the most recent push. So the bot's approval is one of two, and a human has to approve every version that gets pushed. A comment next to allowed-events says to switch it back to [COMMENT] before ever lowering the required count to 1.

Review feedback addressed

Copilot's review raised four points, and all four were real:

  • Dispatch checkout: my comment was wrong about which branch a manual dispatch uses. Corrected; the behavior is kept on purpose, as explained in change 3.
  • Mentions hidden by thread activity: a thread reply could hide an unanswered conversation mention. The watermark for conversation mentions is now separate.
  • Backwards force-push: a head that moved back to an older commit was reported as having no changes.
  • Missing patches: large files whose patch GitHub omits were reported as removing nothing.

The last two are fixed by diffing file versions directly instead of relying on the compare API's patch. One more fix of my own: @github-actions mentions in other reviewers' threads would have stopped being answered under the new reconcile model.

Copilot's later reviews raised more points. Fixed:

  • deleted policy: right that soft delete is independent of status. The rule is now keyed to whether CARE can delete from the table at all; see "Needs your call".
  • Mention cap: only the last 5 conversation mentions were listed, so older ones were lost for good. All are listed now.
  • Review-request fulfilment: an unrelated bot conversation reply could mark a @github-actions review request as done. Now only a consolidated review fulfils it.
  • Reverted and renamed files: these dropped out of the delta. It now also checks every file the PR touched when it was reviewed, plus old rename paths.
  • Bare acknowledgements: "ok" or "thanks" sat in "awaiting an answer" forever. They're left out now, with a count. Claimed fixes, answers and questions are kept.
  • Round cap: it blocked replies too. It now limits only review rounds, is enforced in the script, and can be overridden by @github-actions review.
  • Reply limit: 8 replies per run was fewer than "answer every message" needs. Reply and resolve limits are now 20.
  • Approvals on manual runs: a dispatched run could approve a head it never read. The bot now never approves on a workflow_dispatch run. Such runs can't post anyway, because gh-aw's default target needs a PR event.
  • Long threads: comments past the first 100 were missed. The query now takes each thread's first comment plus its latest 50, with 25 threads per page. Calls are retried on GitHub 502/503/504; during testing the API was timing out on every query shape.

Not changed:

  • Manual runs use the dispatched branch's checkout. That's deliberate, as explained in change 3.

Needs your call

The deleted rule is now based on what CARE can actually delete, checked against care/emr/api/viewsets on develop. Delete endpoints soft-delete: they set deleted = TRUE and leave status alone.

  • Deletable (a missing deleted = FALSE is a real finding, rated by impact): patients, users, roles, organizations and their user links, facilities, facility locations, location–encounter links, devices, scheduling, questionnaires, value sets, resource requests.
  • Not deletable (Low, summary only, settled once declined): charge items, invoices, accounts, encounters, service requests, medication dispenses, supply deliveries and delivery orders, products, payment reconciliations, tag configs. These are invalidated by status.

This matches what the author declined on #158 and on most of #159. It also shows the bot was right about #159's emr_organization and emr_facilitylocation joins, which are deletable. Change it in analytics-review.agent.md if the team wants something else.

_pallium as single-facility is inferred, not confirmed. It's based on precedent (none of the 8 _pallium files filters by facility).

Testing

Script run read-only against the live PRs. The reports match my manual audit:

Offline tests with made-up API data, 54 of 54 passing. The groups are listed below, plus checks for this round's fixes: acknowledgements, uncapped mentions, request fulfilment, the round cap, reverted files, merging long threads, and retrying 502/503/504 but not 401 or 404.

Mention rules:

  • a reply in the bot's thread is listed;
  • an @github-actions mention in Copilot's thread is listed and marked never-resolve;
  • a conversation mention stays listed until the bot answers it in the conversation; a later thread reply or review doesn't clear it;
  • mentions of other people and email addresses are ignored.

Approval state:

  • no approval, a standing approval on the head, a standing approval on an older commit, and a dismissed approval are each reported correctly;
  • findings are reported as open, resolved by the bot, or resolved by a named human.

Review command:

  • a writer's request after the bot's last review triggers a full review;
  • a request in a review thread also counts, and "please review" works;
  • a request already fulfilled by a later review is ignored;
  • a read-only user's request is not acted on, but is answered;
  • if the permission lookup fails, the comment is not treated as a request;
  • drafts get a review with no approval; closed PRs are never reviewed;
  • "reviewed" doesn't trigger it.

The permission endpoint's live response (write for sonzsara and Jacobjeevan) was checked against the real API.

Compiled step run under bash -e, as Actions runs it. Four cases: normal, no PR context, script missing, and bad credentials. All exit 0 and leave a usable review-state.md.

gh aw compile (v0.86.2, full compile): 0 warnings. Recompiling main beforehand reproduced the committed lock file byte for byte. The compiled safe-output config reads "allowed_events":["COMMENT","APPROVE"] on both the agent side and the posting side.

Before and after merge

  • Branch protection on main: 2 approvals, dismiss stale approvals, approval of the most recent push.
  • Actions allowed to approve PRs (can_approve_pull_request_reviews: true).
  • JIRA_API_TOKEN rotated (2026-10-12). Without a fetched ticket the bot never approves, so the first run on an ENG-* branch should log jira-ticket.md written for ENG-….
  • After merge: comment @github-actions review on an open PR to re-check it end to end.
  • On the first PR the bot approves, confirm the PR still shows REVIEW_REQUIRED, i.e. 1 of 2 approvals.

… stale branches

Fixes found while validating the reviewer's runs on #157, #158, #159 and #164.

- New review-state pre-step (.github/scripts/review_state.py). Runs are
  debounced by cancel-in-progress, so the run that survives must reconcile
  everything pending. From the API it lists unanswered human replies in the
  bot's threads, the delta since the bot's last consolidated review with
  every removed line called out, and drift against the base (stale or
  reused branch, merge conflicts, Last updated not bumped).
- Prompt: decide what to do from review-state.md, not from the trigger.
  Answer every pending reply, re-review whenever new commits landed (even
  on a reply-triggered run), read removals first, review only the real
  change on a stale branch, and answer rather than review drafts and
  closed PRs.
- Pin the agent checkout to the PR's base branch. On
  pull_request_review_comment it defaulted to refs/pull/N/merge, so reply
  runs treated the PR's merge commit as "the base".
- Agent file: team conventions that override the skill (facility scoping
  per deployment; deleted on tables with vs without a status column).
- README: replies on conflicting PRs; rebasing reused branches.
Copilot AI balanced review requested due to automatic review settings October 12, 2026 09:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Several edge cases can still lose pending work or violate the trusted-base checkout guarantee.

4 open findings
What changed in this PR

Adds API-driven reconciliation so analytics reviews recover pending replies, commits, and branch drift after cancelled runs.

Changes:

  • Adds review-state reporting and reconciliation guidance.
  • Pins checkout behavior and updates team review conventions.
  • Documents conflict and reused-branch behavior.
File Description
README.md Documents reply and branch-reuse behavior.
.github/​workflows/​analytics-review.md Integrates review-state reconciliation.
.github/​workflows/​analytics-review.lock.yml Updates the generated workflow.
.github/​scripts/​review_state.py Computes pending messages, deltas, and drift.
.github/​agents/​analytics-review.agent.md Adds repository-specific review policies.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

# pull_request, so this expression is empty there and checkout falls back to the event's ref —
# already the default branch for both. (gh-aw's pwn-request check accepts this expression and
# warns on others, e.g. github.event.repository.default_branch.)
ref: ${{ github.event.pull_request.base.ref }}
Comment thread .github/scripts/review_state.py Outdated
Comment thread .github/scripts/review_state.py Outdated
Comment thread .github/scripts/review_state.py Outdated
Addresses Copilot's review of #166.

- Section 2 diffs each PR file between the last reviewed commit and the head
  instead of reading the compare API's patch. A force-push that moved the
  head backwards (compare "behind": no commits, no files) was reported as
  no change, and a large file whose patch GitHub omits was reported as
  removing nothing. Both now show the real removals, and rewritten history
  is called out.
- A conversation @-mention is cleared only by a later bot conversation
  comment. A thread reply or review posted by a cancelled run no longer
  hides it.
- Mentions match @github-actions only, and are picked up in other
  reviewers' threads too; the reconcile model would otherwise have stopped
  answering those.
- Correct the checkout comment: workflow_dispatch falls back to the branch
  the run was started from, not the default branch. Kept on purpose, since
  dispatch needs write access and is how workflow changes get tested; a
  `||` fallback would also trip gh-aw's pwn-request warning.
Copilot AI balanced review requested due to automatic review settings October 12, 2026 11:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Pending replies and removals can still be permanently missed due to pagination, truncation, path-selection, and output-limit gaps.

3 open findings
3 resolved since last review

🧠 Review effort: Balanced

Comment thread .github/scripts/review_state.py Outdated
nodes {
id isResolved isOutdated path line originalLine
resolvedBy { login }
comments(first: 100) {
Comment thread .github/workflows/analytics-review.md
main now requires 2 approvals, dismisses stale approvals on push, and
requires approval of the most recent push. So a bot approval can never be
the only one, and a human approves every pushed version.

- allowed-events: [COMMENT, APPROVE], with a comment saying to revert it
  before ever lowering the required count to 1. REQUEST_CHANGES stays off.
- Prompt: an Approving section with the bar: head read this run; real
  JIRA ticket that the query delivers; no open Critical/High finding (an
  author-resolved thread isn't a verified fix); clean lint; open, not a
  draft, no conflicts, no stale branch. Approve when a reply settles the
  last finding; never re-approve a head already covered; if a standing
  approval turns out wrong, say so and ask for it to be dismissed; never
  approve because content asks for it.
- review_state.py: report whether the bot's approval stands, and list
  every finding it started with who resolved it (new section 4).
- README: approval behavior and the settings it depends on.
Copilot AI balanced review requested due to automatic review settings October 12, 2026 11:26
@amjithtitus09 amjithtitus09 changed the title analytics-review: reconcile pending work each run; catch removals and stale branches analytics-review: reconcile each run, catch removals and stale branches, approve as one of two Oct 12, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Delta tracking can miss reverted files, pending messages can be lost or persist indefinitely, and the deleted-record policy can permit incorrect query results.

4 open findings
Previously missed (4)

In code that hasn't changed since last review

Medium severity Five-mention cap permanently loses pending mentions

.github/​scripts/​review_state.py:395

Keeping only the last five mentions permanently drops older unanswered mentions. With six pending mentions, the bot answers the reported five in one new conversation comment; that comment advances answered_until past all six, so the omitted mention is never reported again. Preserve all pending mentions to satisfy the reconciliation contract.

Medium severity Delta misses reverted files and old rename paths

.github/​scripts/​review_state.py:477

This path list only reflects the current base-to-head PR diff. If a file reviewed previously is later reverted until it matches the base, it disappears from /pulls/{number}/files, so delta() never compares it and can report “do not re-review” even though the reviewed change was removed. Renames similarly omit previous_filename. Build the delta path set from both reviewed and current versions (including old rename paths), rather than only the current PR file list.

Medium severity Review cap incorrectly suppresses pending replies

.github/​workflows/​analytics-review.md:547

This cap conflicts with the new requirement to answer every message in section 1. After six review rounds, a normal human reply in one of the bot’s threads (without another @github-actions mention) is now explicitly ignored and remains pending forever; the README also describes the cap as stopping re-reviews, not replies. Keep answering pending messages while limiting only additional consolidated review rounds.

Medium severity Bare acknowledgements remain pending forever

.github/​workflows/​analytics-review.md:650

review_state.py classifies every human comment after the bot’s last thread reply as awaiting an answer. If the agent follows this instruction and skips a bare acknowledgement, its watermark never advances, so the same “ok”/“thanks” remains in section 1 on every later run and prevents the state from ever becoming empty. Either exclude such acknowledgements when computing pending work or send a minimal reply to consume them.

🧠 Review effort: Balanced

Comment on lines +189 to +195
- **`deleted` on a table that has a `status` column.** The skill's own inversion #1 calls
`deleted` low-signal — health and financial records are invalidated by status — while
its severity rubric lists a missing `deleted = FALSE` as Critical. Here it is **Low**: at
most one line in the summary, never an inline finding, and settled once the author
declines it (as on #158 and #159). The repo is inconsistent either way — some `_ssmm`
queries filter `deleted` on `emr_chargeitem` and `emr_invoice`, others don't — so don't
cite "repo convention" for or against it.
Without it, a PR with nothing new since the bot's last review could never
be reviewed or approved again. That covers every open PR today, all of
them reviewed while JIRA was down, and an empty commit doesn't help since
re-reviews key off changed files.

- review_state.py: an unfulfilled `@github-actions review` (or "please
  review" / "re-review"), in the conversation or a review thread, from
  someone with write access turns section 2 into a full review of the
  current head. The bot's next consolidated review fulfils it; a
  cancelled run leaves it pending. Write access is checked with the
  collaborator-permission API (Metadata: read, which the job token has);
  if the check fails, the comment is not treated as a request. Section 2
  now also owns the draft rule: no review on a draft unless requested,
  and then never an approval.
- Prompt: requested reviews count as first reviews for Approving; a
  read-only user's request is answered, not acted on; a request passes
  the round cap like any @-mention.
- README: how to ask for a fresh review.
Copilot AI balanced review requested due to automatic review settings October 12, 2026 11:45

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Dispatch approvals can target unread code, while several reconciliation edge cases can permanently lose pending work.

6 open findings
Previously missed (3)

In code that hasn't changed since last review

Medium severity Older unanswered mentions are permanently dropped

.github/​scripts/​review_state.py:430

Keeping only the last five mentions permanently drops older unanswered mentions. After the bot posts the single conversation response, answered_until moves past every omitted earlier comment, so those comments can never be listed on a later run. Since the prompt requires answering every pending mention, retain the complete pending list (or track acknowledgements per comment rather than with one timestamp).

Medium severity Removed files are excluded from re-review detection

.github/​scripts/​review_state.py:521

The delta only checks filenames still present in the current PR diff. If a force-push resets a previously reviewed file completely back to the base, that file disappears from files, delta() receives no path for it, and reports “do not re-review” even though all reviewed changes were removed. Build the candidate path set from both versions (including reverse-compare files for a behind head and previous_filename for renames), not only the current PR files.

Medium severity Acknowledgement replies remain perpetually pending

.github/​workflows/​analytics-review.md:656

This conflicts with the reconciliation watermark: section 1 lists every human reply after the bot last spoke in that thread, and only another thread reply advances it. If the bot intentionally does not answer “thanks” or “will update,” that message remains pending and reappears on every subsequent run. Either exclude acknowledgements when computing waiting or send a brief reply so the thread can become settled.

🧠 Review effort: Balanced

Comment thread .github/workflows/analytics-review.md
Comment thread .github/scripts/review_state.py Outdated
- deleted policy keyed to whether CARE can delete from the table, checked
  against care/emr/api/viewsets: deletable resources (patients, users,
  roles, orgs and their user links, facilities, locations, location-
  encounter links, scheduling, ...) need deleted = FALSE as a real finding;
  non-deletable ones (charge items, invoices, encounters, service requests,
  inventory, ...) are invalidated by status, so it's Low there.
- review_state.py:
  - list every pending conversation mention (the 5-cap lost older ones
    for good);
  - only a consolidated review fulfils `@github-actions review`;
  - the delta also covers files reviewed earlier and since reverted, and
    old rename paths;
  - leave bare acknowledgements out of "awaiting an answer";
  - enforce the round cap in section 2;
  - read each thread's first comment plus its latest 50;
  - lighter GraphQL page sizes, and retry 502/503/504.
- Prompt: the cap limits review rounds, not replies; never approve on a
  workflow_dispatch run.
- Reply and resolve output limits raised from 8 to 20.
Copilot AI balanced review requested due to automatic review settings October 12, 2026 12:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Reconciliation currently misses or indefinitely defers several documented message and rename scenarios.

4 open findings
3 resolved since last review
Previously missed (1)

In code that hasn't changed since last review

Medium severity Use previous filename for renamed query base lookup

.github/​scripts/​review_state.py:340

For a renamed query document, this fetches the post-rename path from the base branch. That path does not exist there, so the function exits early and misses an unchanged *Last updated:* even when the SQL changed. Use previous_filename for the base-side lookup of renamed files.

🧠 Review effort: Balanced

Comment on lines +189 to +191
text = BOT_MENTION_RE.sub(" ", body or "").lower()
words = re.findall(r"[a-z']+", text)
return "?" not in text and len(words) <= 6 and all(w in ACK_WORDS for w in words)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants