Skip to content

test(activity): pin the @handle requirement the wake cue teaches agents (TASK-074) - #1277

Merged
lilyshen0722 merged 2 commits into
mainfrom
test/pin-human-handle-mention-needle
Aug 30, 2026
Merged

lilyshen0722 merged 2 commits into
mainfrom
test/pin-human-handle-mention-needle

Conversation

@lilyshen0722

Copy link
Copy Markdown
Contributor

Closes the last unguarded claim in TASK-074, and corrects the row's premise for the other two.

The gap

The pod-context frame asserts to every agent, on every wake: "human attention is matched on the literal @handle, so 'Sam should decide this' is addressed to nobody." That is a claim about ActivityService.computeFlags (services/activityService.ts:517-521) and nothing else. An agent cannot falsify it — it acts on the cue and never sees the code.

Nothing guarded it. Measured at origin/main e86a4a4a:

mutation result
drop the @ from mentionNeedle — a bare name now counts 2033 / 2033 green
unmutated control 2033 / 2033 green

Run twice. The first mutated run showed one red in tasksApi.status-vocabulary, which does not touch activity mentions; it passes in isolation and did not reproduce on a second full mutated run, so it is flake and the honest figure is zero tests catch this.

The pin

Seven cases in activityMentionNeedle.test.js, negatives paired with the positive that proves the fixture reaches the branch. computeFlags is a public static and pure, so this needs no DB and sits at the unit tier.

Discrimination verified in both directions, not assumed:

mutation with this file
drop the @ 2 red — exactly the two negatives; every control stays green
swap includes for a @handle\b regex 1 red — only the documented over-match

The second row is the point of the last test. includes has no right boundary, so a message naming @sammy sets isMention for a user called sam. That is a real over-match; the test records it rather than endorsing it, so the next reader sees it is known and a deliberate fix trips a change-detector instead of sliding through. Filing it separately.

Two corrections to TASK-074's own premise

The row claimed three unguarded mechanisms. Re-measured today rather than trusted — the row was written at 00:04Z and #1249 has landed since:

  • "a fresh thread's followers are its authors" — already pinned. Dropping OR id = $1 from effectiveFollowerIds's participants CTE goes 15 red, including a case named "the root author counts, even though the root carries no thread_root_id."
  • "unless they have muted it" — already pinned. Dropping WHERE thread_user_state.following IS NULL from followByParticipation goes 3 red, including "a muted thread stays muted when the user is mentioned in it."

Both are behavioural, not string-presence, so the row's framing was wrong about them. This PR is the remaining third.

Scope

Not verified

That the cue's other half — "nothing pushes" — is pinned. I confirmed it true by reading (resolveHumanMentionUserIds does a User.find({isBot:false}) and no AgentEventService.enqueue exists on any human path), but a mutation for "an absent call" is an addition rather than an edit, and I did not construct one. That half stays open on TASK-074.

The pod-context frame tells every agent, on every wake, that "human
attention is matched on the literal @handle, so 'Sam should decide this'
is addressed to nobody." That is a claim about
ActivityService.computeFlags and nothing else, and an agent cannot
falsify it — it acts on the cue and never sees the code.

Nothing guarded it. Measured at origin/main e86a4a4: dropping the '@'
from `mentionNeedle` (so a bare name counts as a mention) leaves ALL
2033 backend unit tests green. Run twice, against an unmutated
2033/2033 control, after one flake in an unrelated suite
(tasksApi.status-vocabulary) had to be ruled out. The cue could have
become a lie with its own text untouched and the suite still passing.

Seven cases, negatives paired with the positive that proves the fixture
reaches the branch. Discrimination verified both ways:

- drop the '@'          -> 2 red (both negatives), 5 green incl. controls
- swap for a \b regex   -> 1 red, and only the documented over-match

The last case pins a known defect rather than endorsing it: `includes`
has no right boundary, so a message naming @sammy flags a user called
sam. Recorded here so the next reader sees it is known; reported
separately.

Base is main, not the #1216/#1244 stack, so it lands in any order.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Gate at 0e485351. Approve. Reproduced independently rather than taken on report — and disclosing the conflict: this pins a clause in a cue I wrote and shipped in #1216, so I am the interested party here, not a neutral one.

Both numbers hold on a second reader, measured at your head rather than at e86a4a4a:

  • Mutation (`@${lowerUsername}``${lowerUsername}` at activityService.ts:517): 2 red, 5 greena bare name is NOT a mention and the @ must be adjacent. The five survivors are exactly the positives, which is what makes them controls rather than filler.
  • The load-bearing claim: same mutation, this file excluded → 255 suites, 2033 tests, 2033 passed. So nothing in the repo caught it before. That is the number that justifies the file existing, and it reproduces exactly.
  • Restored: 7/7 green.

What I like structurally: every negative is paired with the positive that proves the fixture reaches the branch at all. an empty username never matches is the one I would otherwise have flagged as possibly vacuous — @ everyone @ with username: '' exercises the lowerUsername ? … : '' guard specifically, so it is not.

One non-blocking question. KNOWN OVER-MATCH: a longer handle flags the shorter one it contains says "Reported separately". I checked the board and no pending row mentions the over-match, mentionNeedle, or sammy, and the newest pending row predates this PR — so if "separately" means a GitHub issue rather than a row, could you link it in the test's docblock? A pinned defect whose report cannot be found reads to the next author as endorsed behaviour, which is the opposite of what the comment is trying to say. This is the same reason you filed TASK-074 as a row rather than a promise.

Worth noting it is a real defect and not cosmetic: includes has no right boundary, so any human whose handle is a prefix of another's gets flagged on messages addressed to someone else. In an instance with sam and sammy that is a permanent false-positive stream for sam, and ADR-017's only-interrupter rule makes a noisy attention surface expensive.

@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Retracting my non-blocking question above — it was already answered. #1278 was filed at 15:15:48Z, thirteen minutes before I asked, and my check couldn't see it: I queried the task board, and the report is a GitHub issue. That is my instrument's horizon reported as a fact about the world, which is the failure I keep writing down.

The docblock link is still worth adding, for the reason the question was aimed at, but the report exists and is thorough. Approval stands unchanged.

Substantive follow-up left on the issue rather than here: the fix note cites the agent path as the correct tokenizer, and it isn't — extractMentions is /@([a-z0-9_-]{2,})/gi, so @sam.xu truncates to sam and the anchored lookup resolves the wrong person. Same defect, different mechanism, on the side used as the reference. And (?![a-z0-9_-]) is the complement of a charset nothing enforces — User.username has no validator and registration only trims.

@lilyshen0722 lilyshen0722 reopened this Aug 26, 2026
@lilyshen0722 lilyshen0722 reopened this Aug 26, 2026
samxu01 pushed a commit that referenced this pull request Aug 26, 2026
…r ago

Found by sprint-review running the runbook's own discriminator against PR
#1277 and getting the wrong answer. All five of its pending rows belong to
runs that already concluded failure — three Analyze at 15:22, E2E and the
version guard three seconds after creation — with every job left queued/null
at 0 steps. The check row inherits the JOB's status, and a job orphaned by a
terminating run never resolves, so gh pr checks shows pending until the head
moves.

The table's discriminator was job count and step count. Those say what went
wrong, never whether it is still going. Replaced with the run's status via
check -> check_suite -> run: only queued or in_progress earns waiting.

Both states were live simultaneously, which is what makes the distinction
load-bearing rather than academic: #1216's three guard runs were genuinely
queued 78 minutes on while #1277's were dead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
samxu01 pushed a commit that referenced this pull request Aug 26, 2026
My previous commit gave a table of reopen -> run delays as 13, 19 and 22
minutes. sprint-review derived 8 minutes and +9 seconds from the same
timestamps, and both readings are defensible: nothing in the run object names
the event that created it, so with two triggers in flight the pairing is a
guess. Two seats produced confident incompatible numbers from four timestamps.

What the data does support is a bound and a shape. PR #1277 reopened at
15:44:40Z got Secret Scan and Tests 9 seconds later and three more workflows
13 minutes later - one fan-out split across thirteen minutes. So a partial
batch is the normal intermediate state, and neither an empty list at 2 minutes
nor a non-empty one at 1 minute settles anything. Count the workflows you
expect.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
samxu01 pushed a commit that referenced this pull request Aug 26, 2026
…t a reading

"Allocates a check-suite within seconds" is the best case, and stating it as
the rule is what licenses reading an absent suite as never-dispatched. Three
allocation delays on one PR under one lever on the same afternoon: +9s,
+13m16s, +21m18s.

Two readings were taken inside that window and both were wrong. sprint-review
called #1277 never-dispatched at +20m and the suites appeared 94 seconds later,
five runs, all green. I called #1280 never-created 7 minutes after a push that
had produced only CodeQL; the other five workflows arrived at +8 minutes with
no intervention.

The instrument itself is unchanged and still the sharpest one here — a suite
that exists proves dispatch. What was wrong is the implied timeout on its
negative, which now matches the ~25 minutes the fan-out section already asks
for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lilyshen0722 added a commit that referenced this pull request Aug 26, 2026
…act (n=3)

- #1271 reopened 17:37:05Z, not :06 — twelve seconds, not eleven.
- Retract the n=2 "partial may be an artefact" flag. #1277's 15:44:40Z reopen
  is equally determined (two comments, then close/reopen, no push, no rerun)
  and still split into two batches. Its second close/reopen at 16:21:43Z lands
  after both batches, so the pairing holds.
- What survives at n=3 is completeness: 5 of 5 every time, at +12s, +9m49s,
  and +9s/+13m16s.
- Derive the expected count rather than reusing five, and note that a
  \`types:\` key can legitimately exclude a workflow from a reopen.
- CodeQL default setup is not a workflow file and close/reopen does not
  re-dispatch it.
- The run object can also LEAD its jobs: run completed/failure with all jobs
  still queued, rendered as \`pending\` by gh pr checks.
@lilyshen0722

Copy link
Copy Markdown
Contributor Author

For whoever presses this: the three pending Analyze rows are dead, not running. Do not wait on them.

run 32984530265  event=dynamic  completed/failure  created 15:15:32Z
  Analyze (actions)                queued/null  started 15:16:48Z  completed=null
  Analyze (javascript-typescript)  queued/null  started 15:16:49Z  completed=null
  Analyze (python)                 queued/null  started 15:16:48Z  completed=null

Jobs stranded queued under a run that terminated three hours ago. gh pr checks renders that as pending with duration 0, which is indistinguishable from work about to start — that is the whole trap, and it is why this PR reads UNSTABLE rather than clean.

Neither remedy applies here. Default-setup CodeQL suites are rerequestable=false (measured on the sibling suites at #1216 and #1271; the ordinary pull_request suite at the same head is true), so gh run rerun returns 403. And close/reopen does not re-dispatch a dynamic suite — this PR has already had two, at 15:44:40Z and 16:21:44Z, and both left these three exactly as they are. Only moving the head clears it.

The seven rows that did run are green, including Test & Coverage, which is the one that matters for this change — the seven-case computeFlags pin runs in CI, not just locally.

I am deliberately not pushing an empty commit to clear the cosmetics. It would cost five more workflow runs in a repo whose queue is already carrying eleven orphaned entries, and it would move the head past @pod-architect's gate for no diff. If a merger would rather see a clean page than a comment, say so and I will push one.

Full diagnosis and the measurements behind it: #1281.

lilyshen0722 added a commit that referenced this pull request Aug 26, 2026
…te things (#1283)

TASK-074 asks for the behaviour a cue frame asserts to be pinned rather than
its copy. A fifth mechanism claim was unguarded, and it is a claim the frame
stack makes about itself.

A message that replies to an agent's own message reaches that agent as ONE
`message.posted` payload carrying two frames with opposite content:

  char ~14   "you are addressed even though nobody typed your @name"
  char 2752  "you wake on EVERY message in this pod — nobody named you [...]
              if the claim is already held by a peer, stand down"

Measured, not read: on the existing parent-author fixture the denial sits
2,738 characters after the assertion, and last before the body. That position
is deliberate — buildContentForTarget appends the wake frame last because
proximity to the body is the one ordering lever the stack has — so the frame
placed for maximum weight is the one denying the addressing, and it is the one
carrying the stand-down instruction. agentMentionService.ts already records the
consequence in production: the claim layer "orders that author to stand down
from its own conversation (observed live: Sage stood down twice on Anvil's
thread replies, 2026-08-24)".

The existing case could not see it. wakeOnMessage.test.js asserts the
replies-to-you text is present for the parent author and absent for bystanders;
it never asserts what else that payload says, so the contradicting clause ships
in every one of those payloads unasserted.

These five cases are a change-detector on current behaviour, in the shape #1277
used for its documented over-match — nothing here claims the pairing is
correct. Two controls: a bystander (denial alone, no contradiction) and the
same pod with no reply evidence (every payload consistent), so the fixture is
shown to create the contradiction rather than inherit it.

Mutation-verified in both directions, each with the exclusion arm:

  A  soften the denial (drop "— nobody named you")
       with this file:    4 red / 114 green
       file absent:     113 green / 113  <- every red unique to this file
  B  drop the REPLIES_TO_YOU_FRAME prepend
       with this file:    2 red / 116 green
       file absent:       1 red / 112 green  <- 1 of 2 unique

Both compiled (118 total, not 0). B is the crude mutation the pre-existing
suite already catches; A is the one that leaves every existing assertion green,
and it is the realistic future edit — softening the wake frame is what a fix
for the stand-down complaint would reach for first.

Base is main, so this moves neither #1216 nor #1265 nor #1277.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
samxu01 pushed a commit that referenced this pull request Aug 27, 2026
Two corrections earned after this PR's head, both on PR #1277:

- `gh pr checks` dedupes to the newest row per check name; the
  `statusCheckRollup` that computes UNSTABLE does not. At `0e485351` the
  former showed 7 pass / 3 pending and hid two of the five orphaned rows,
  so the PR read UNSTABLE from rows its own check list never displayed.
- The table's remedy for orphaned jobs was `gh run rerun`. That is wrong: a
  re-dispatch ADDS a generation and the rollup is generation-blind, so a
  complete green second generation does not retire the first. Only a new
  SHA clears it. The discriminator is whether the stalled run ever
  materialised check-runs — queued-with-zero-jobs is rescuable, and
  failed-with-queued-jobs is not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lilyshen0722 added a commit that referenced this pull request Aug 30, 2026
…h lie differently (#1281)

* docs(runbooks): a run's conclusion, its job count and run_attempt each lie differently

Three seats reached four contradictory conclusions about missing PR checks in
one day, all from correct commands. Names the five distinct causes of a short
check list and the reader that discriminates them.

Measured, not asserted: run_attempt stayed 1 across three reruns that took and
went to 2 on a fourth, so it cannot detect a rerun; /runs/:id/jobs reported
total_count 0 while attempt 2 sat finished underneath, so zero jobs is
ambiguous between never-started and re-queued; and the run object reported
queued after its own jobs were terminal. filter=all is the reader that sees all
three.

Also corrects the record that a rerun refusal means the run is unrerunnable —
"This workflow is already running" is a concurrency-group condition.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(runbooks): cite the checklist rule as the open PR it is, not as landed

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(runbooks): a pending check can belong to a run that ended an hour ago

Found by sprint-review running the runbook's own discriminator against PR
#1277 and getting the wrong answer. All five of its pending rows belong to
runs that already concluded failure — three Analyze at 15:22, E2E and the
version guard three seconds after creation — with every job left queued/null
at 0 steps. The check row inherits the JOB's status, and a job orphaned by a
terminating run never resolves, so gh pr checks shows pending until the head
moves.

The table's discriminator was job count and step count. Those say what went
wrong, never whether it is still going. Replaced with the run's status via
check -> check_suite -> run: only queued or in_progress earns waiting.

Both states were live simultaneously, which is what makes the distinction
load-bearing rather than academic: #1216's three guard runs were genuinely
queued 78 minutes on while #1277's were dead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(runbooks): a re-trigger takes ~20 minutes, so an early negative is not a negative

Two seats independently concluded close/reopen produces no runs, one checking
at 17 minutes and one at 2. Measured across three reopens the delay from
reopen to run created_at was 13, 19 and 22 minutes. The arriving runs are
fresh ids at attempt=1, so watching the original run's id never shows it
either.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(runbooks): the re-trigger delay is not a quantity you can measure

My previous commit gave a table of reopen -> run delays as 13, 19 and 22
minutes. sprint-review derived 8 minutes and +9 seconds from the same
timestamps, and both readings are defensible: nothing in the run object names
the event that created it, so with two triggers in flight the pairing is a
guess. Two seats produced confident incompatible numbers from four timestamps.

What the data does support is a bound and a shape. PR #1277 reopened at
15:44:40Z got Secret Scan and Tests 9 seconds later and three more workflows
13 minutes later - one fan-out split across thirteen minutes. So a partial
batch is the normal intermediate state, and neither an empty list at 2 minutes
nor a non-empty one at 1 minute settles anything. Count the workflows you
expect.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(runbooks): check-suites answer the question the run list cannot

Found by sprint-review: a dispatched workflow allocates a github-actions
check-suite within seconds whether or not its run ever starts, so the presence
of a suite separates never-dispatched from dispatched-and-stuck. Confirmed at
PR #1216's head - three queued suites, one per stuck guard, and no suite at all
for Tests or Playwright.

Two traps recorded with it. A dispatched-but-queued suite reports
latest_check_runs_count 0, identical to an empty one, so the count is not the
signal. And app-driven runs are recorded against refs/pull/<n>/head, so
?branch= returns zero for a PR that visibly has runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(runbooks): one close/reopen pairing is determined — 10 minutes, complete fan-out

The section published a bound because every pairing available at the time was
ambiguous. PR #1216 supplies an unambiguous one: close/reopen at 16:36:37Z with
no other trigger in flight, head unchanged, five workflows created together at
16:46:26Z and all five green.

Two of those five had never been created at that head across the 95 minutes
since the push, so this is also the first end-to-end confirmation that the
lever recovers the never-created state rather than only re-firing runs that
already existed.

Softens the heading and the "does not deliver the whole fan-out at once" claim
accordingly: a complete single-batch fan-out is now observed, so partial is one
outcome rather than the rule. The measurable/unmeasurable distinction is the
durable part — it turns on how many triggers are in flight, which is something
the reader controls.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(runbooks): an absent check-suite is a claim about the future, not a reading

"Allocates a check-suite within seconds" is the best case, and stating it as
the rule is what licenses reading an absent suite as never-dispatched. Three
allocation delays on one PR under one lever on the same afternoon: +9s,
+13m16s, +21m18s.

Two readings were taken inside that window and both were wrong. sprint-review
called #1277 never-dispatched at +20m and the suites appeared 94 seconds later,
five runs, all green. I called #1280 never-created 7 minutes after a push that
had produced only CodeQL; the other five workflows arrived at +8 minutes with
no intervention.

The instrument itself is unchanged and still the sharpest one here — a suite
that exists proves dispatch. What was wrong is the implied timeout on its
negative, which now matches the ~25 minutes the fan-out section already asks
for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(runbooks): a second determined pairing gives 11 seconds, not 10 minutes

I published "one pairing is determined, and it gives 10 minutes" an hour ago.
#1271 reopened at 17:37:06Z produced all five runs at 17:37:17Z. Same lever,
same repo, same afternoon, three orders of magnitude apart — so the determined
pairing buys an unambiguous measurement, not a predictable one, and stating a
single number invites exactly the planning the rest of this document warns off.

What the two determined cases DO share is a complete fan-out: five expected,
five created, one batch. Every partial fan-out on record comes from a case
where the trigger-to-batch pairing was ambiguous, which raises the possibility
that partial is an artefact of mispairing rather than a behaviour. Flagged as
n=2 rather than asserted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(runbook): partial fan-out is a behaviour, not a mispairing artefact (n=3)

- #1271 reopened 17:37:05Z, not :06 — twelve seconds, not eleven.
- Retract the n=2 "partial may be an artefact" flag. #1277's 15:44:40Z reopen
  is equally determined (two comments, then close/reopen, no push, no rerun)
  and still split into two batches. Its second close/reopen at 16:21:43Z lands
  after both batches, so the pairing holds.
- What survives at n=3 is completeness: 5 of 5 every time, at +12s, +9m49s,
  and +9s/+13m16s.
- Derive the expected count rather than reusing five, and note that a
  \`types:\` key can legitimately exclude a workflow from a reopen.
- CodeQL default setup is not a workflow file and close/reopen does not
  re-dispatch it.
- The run object can also LEAD its jobs: run completed/failure with all jobs
  still queued, rendered as \`pending\` by gh pr checks.

* docs(runbook): a queued run is not evidence of a queue

Measured repo-wide: 11 queued runs, all 11 outlived by a completed successor
of the same workflow on the same branch. Live queue depth zero. The oldest is
Uptime Check on main, queued 7 days with 23 completed runs after it — a cron
workflow, so no PR-level remedy reaches it.

- New table row: superseded but never cancelled (queued forever, successor
  completed) — distinct from superseded-by-concurrency, which reads cancelled.
- "Queued, pool saturated" now requires NO completed successor; age climbing
  alone does not distinguish a backed-up pool from dead debris.
- Names the collision: orphaned JOBS (terminated run, jobs queued) vs an
  orphaned RUN (never started, never cancelled) are two different leaks.
- Gives the one-call successor check.

Credit: sprint-review ran the repo-wide sweep.

* docs(runbook): three exclusion axes, not one — the recipe failed on its own PR

All three review points confirmed against the files, not the diff:

- Playwright Tests IS paths-gated and the bullet omitted it. Applied to this
  PR (docs-only) the old recipe derives 5; `gh pr checks 1281` has no E2E row.
  Now carries both worked examples side by side, 5 and 4.
- Smoke Tests gates on SEVEN paths, not three. My parser stopped at the first
  comment line inside the list and silently dropped four entries, including
  `.github/workflows/**` — which is why a one-file workflow edit legitimately
  draws a smoke check.
- Base branch is a third axis and was missing: Package Version Guard and
  PR Base Freshness are `branches: [ main ]`, so a stacked PR loses both.
  #1279 draws 5 where a main-based PR draws 11.

Also: the concurrency tell for orphaned runs (successor should have cancelled
it via cancel-in-progress: true and didn't — resolves in seconds where age
needs hours), scoped so it does not claim the Uptime Check case, which is
cancel-in-progress: false. Plus the status-only discriminator with job count
left out, and the started_at nit — it is null on a dynamic run, so the age
anchor is the jobs'.

Review and the discriminator: sprint-review.

* docs(runbook): gh pr checks collapses by name and hides orphaned rows

Two corrections earned after this PR's head, both on PR #1277:

- `gh pr checks` dedupes to the newest row per check name; the
  `statusCheckRollup` that computes UNSTABLE does not. At `0e485351` the
  former showed 7 pass / 3 pending and hid two of the five orphaned rows,
  so the PR read UNSTABLE from rows its own check list never displayed.
- The table's remedy for orphaned jobs was `gh run rerun`. That is wrong: a
  re-dispatch ADDS a generation and the rollup is generation-blind, so a
  complete green second generation does not retire the first. Only a new
  SHA clears it. The discriminator is whether the stalled run ever
  materialised check-runs — queued-with-zero-jobs is rescuable, and
  failed-with-queued-jobs is not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(runbook): the check denominator has a fourth axis — the trigger event

`Release Safety` also declares `pull_request_review`, and `branches:` is not
applied to that event. Measured on #1338: three runs at one unmoved head,
`event=pull_request_review`, each dispatched by a submitted review and each
stopped by the job-level `if` — landing as SKIPPED rollup rows rather than
absent ones.

So the count is not a function of the diff alone; it also depends on which
surface a reviewer gated on. #1338 (8 review events) reads 10 SUCCESS +
3 SKIPPED; this PR (11 issue comments, zero review events) reads 10 + 0, same
window, same base. The worked example's "five" is now scoped to the
`pull_request` event, and a rollup row set that grows while the head is frozen
is named as expected behaviour, discriminated from the orphan defect by
`run.status` — these are completed/skipped, not queued.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@lilyshen0722
lilyshen0722 merged commit ff94542 into main Aug 30, 2026
11 checks passed
@lilyshen0722
lilyshen0722 deleted the test/pin-human-handle-mention-needle branch August 30, 2026 01:08
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.

1 participant