Skip to content

One review panel per branch, and 200 turns for the fixer - #649

Merged
harrykim8672 merged 2 commits into
wafflebase:mainfrom
hwisoo-kim:harness/panel-concurrency
Aug 3, 2026
Merged

One review panel per branch, and 200 turns for the fixer#649
harrykim8672 merged 2 commits into
wafflebase:mainfrom
hwisoo-kim:harness/panel-concurrency

Conversation

@hwisoo-kim

@hwisoo-kim hwisoo-kim commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Two fixes from investigating why #648 came out agent:blocked. Neither the
label nor the page described what actually happened.

What broke on #648

agent-review-panel.yml had no concurrency guard, so two CI completions
close together started two full pipelines against agent/586-cli-system-exit-code:

run panel fix turns outcome
A 30803409664 09:55→10:05 10:05→10:35:52 81 Reached maximum number of turns (80)
B 30803718950 10:00→10:11 10:11→10:32:48 76 ✅ pushed 102e0fa73

Two fixer agents, 21 overlapping minutes, the same nine findings. B converged
and pushed. A crossed its ceiling and its job failed, which fired stalled:

🛑 … the fixer agent failed, so the requested changes were not applied.

That sentence was false. 102e0fa73 was on the branch and CI-green three minutes
earlier. The paged latch then correctly froze the PR — so the commit that did
land was never reviewed. $22.30 spent, roughly a third of it on a duplicate that
could only lose.

The same absent guard had already broken #605 with a different symptom: two
panels completing in the same second with identical external_id and
contradictory verdicts — correctness 1 major vs 2 major, docs success vs failure,
twelve check runs for six lenses, last writer wins.

The changes

1. concurrency on the panel, cancel-in-progress: true.

Cancel rather than queue: a superseded panel is reviewing a sha that is no longer
the head, so its verdict is stale before it is written, and queueing pays for it in
full and then discards it. Keyed on the head branch — the PR number isn't
resolved until the gate job. No run_id in the key; that's the mistake that makes
a group match only itself and guard nothing.

2. Fixer --max-turns 80 → 200.

Above agent-implement's 150 on purpose. The implementer writes a change it planned
itself; the fixer must first read code it did not write, at locations the panel
chose, then fix every finding in one pass because the prompt tells it to converge in
one round. At 80 it died at exactly 81 while the duplicate landed the same work in
76 — a coin flip, not a margin. The ceiling is a runaway backstop, not a budget:
MAX_REVIEW_ROUNDS and the 45-minute job timeout both bind well before 200 turns of
useful work does.

3. The page no longer guesses. On a fixer failure it distinguishes three states:
the branch advanced (applied, unreviewed, naming the sha), it did not (nothing
pushed), or the head couldn't be read (says so).

The trap this change introduced

Caught before pushing, and it's the part most worth reviewing.

stalled ran under always(). With cancel-in-progress now cancelling
superseded runs, a panel cancelled before it starts reports skipped — which is
one of stalled's own trigger conditions. It would have paged and latched the PR to
agent:blocked at the exact moment a fresher round was starting, and the latch
would then have stopped that round from running.

Now !cancelled(), which still fires on every genuine failure — a failed dependency
is not a cancellation. A spurious page is this job's worst outcome because the page
is sticky: every other part of the pipeline reads it as "a human owns this now".

I also checked the remaining always() uses in the file. All step-level, and the one
that writes state (Post per-lens check runs) is self-healing — a superseded run's
lens checks get overwritten, since check runs resolve latest-per-name.

Verification

pnpm verify:self green (11/11) · workflow parses.

  • The three-state reason logic was extracted from the YAML and executed — inline
    github-script JS can hold neither a test nor a linter, so it was run against
    CLI: classify failures so system errors exit 2 #648's real shape (head moved → "applied and NOT reviewed"), a genuine no-push, and
    an unreadable head. The unknown case says it's unknown rather than falling back to
    "pushed nothing", which would be the same false certainty in a new place.

What I'd push back on as a reviewer

200 is a judgement, not a measurement. The only data points are 76 (converged)
and 81 (died); nothing says where the real ceiling should be. It's deliberately far
above both so the ceiling stops being the binding constraint — the round cap and job
timeout are the intended bounds — but if fixer runs start costing noticeably more per
round, this is the number to revisit.

cancel-in-progress can kill a fixer mid-work. That's intended (its base is
stale), and safe because the fixer only ever appends a commit. But a cancelled fixer
that had already pushed leaves a commit whose round never completed — the push
re-triggers CI and a fresh panel, so it converges, just not for free.

Also in here

One stale line in harness-engineering.md claiming a paged PR still re-reviews on
every CI-green push. The gate's paged latch ended that, and the sentence sat directly
above what I was editing.

Deliberately NOT in this PR

The third finding from the investigation: don't latch on a page whose premise the
branch contradicts.
If the head advanced and CI is green after a page, that's worth
one more panel round. It needs a decision about who may clear a latch — a trust
question, not a bug fix.

#648 itself still needs unblocking by hand so one round can review 102e0fa73.
Nothing here does that.

Linked Issues

None — from the #648 post-mortem.

Author checklist

  • I searched existing issues and PRs and confirmed this is not a duplicate.
  • I read every line of this diff and can explain why each change is necessary.
  • Changes follow the conventions in the touched packages; any deviations are explained above.
  • If AI tools assisted with this PR, I noted where in Notes for Reviewers below.

Verification checklist

  • verify:self — CI comment shows ✅
  • verify:integration — CI comment shows ✅ (or explicit skip reason below)

Risk Assessment

  • User-facing risk: none. No product code.
  • Data/security risk: no permission or trust-model change. The concurrency guard
    only reduces what runs; the turn ceiling and the page wording change no scope.
  • Rollback: revert. Both changes are single-value; removing the concurrency
    block restores the previous (racy) behaviour exactly.

Notes for Reviewers

Summary by CodeRabbit

  • Bug Fixes

    • Prevented overlapping automated review runs for the same branch by cancelling superseded runs.
    • Improved stalled and failure notifications to ignore cancelled runs and accurately reflect branch status.
    • Reduced stale outcomes and duplicate work during automated review and fix processes.
  • Improvements

    • Increased the automated fixer’s maximum work limit, enabling it to handle more complex changes.
  • Documentation

    • Documented concurrency issues, completed safeguards, verification results, and remaining manual follow-up.

wafflebase#648 came out agent:blocked with a page saying "the fixer agent failed,
so the requested changes were not applied". Both halves were wrong.

agent-review-panel.yml had NO concurrency guard, so two CI completions
close together started two full pipelines against the same branch. Two
FIXERS then ran for 21 overlapping minutes on the same nine findings. The
later one converged in 76 turns and pushed 102e0fa; the earlier one
crossed its 80-turn ceiling and its job failed, which fired `stalled`.
The changes HAD been applied -- by the other run, three minutes earlier,
CI-green. The paged latch then froze the PR, so the commit that did land
was never reviewed. $22.30 spent, about a third of it on a duplicate that
could only lose.

The same absent guard had already broken wafflebase#605 differently: two panels
completing in the SAME SECOND with identical external_id and
contradictory verdicts -- correctness 1 major vs 2 major, docs success vs
failure, twelve check runs for six lenses, last writer wins.

CANCEL, not queue: a superseded panel is reviewing a sha that is no
longer the head, so its verdict is stale before it is written, and
queueing pays for it in full and then discards it. Keyed on the head
BRANCH, which is what the workflow_run payload carries -- the PR number
is not resolved until `gate`. No run_id in the key; that is the mistake
that makes a group match only itself and guard nothing.

THE TRAP THIS CHANGE INTRODUCED, found before pushing: `stalled` ran
under always(), and a panel cancelled before it starts reports `skipped`
-- one of its own trigger conditions. It would have paged and latched the
PR to agent:blocked at the exact moment a fresher round was starting, and
the latch would then have stopped that round. Now `!cancelled()`, which
still fires on every genuine failure since a failed dependency is not a
cancellation. A spurious page is this job's worst outcome because the
page is STICKY -- every other part of the pipeline reads it as "a human
owns this now".

Fixer --max-turns 80 -> 200, above agent-implement's 150 on purpose: the
implementer writes a change it planned itself, while the fixer must first
READ code it did not write at locations the panel chose, then fix every
finding in one pass because the prompt tells it to converge in one round.
At 80 it died at exactly 81 while the duplicate landed the same work in
76 -- that is a coin flip, not a margin. The ceiling is a runaway
backstop, not a budget: MAX_REVIEW_ROUNDS and the 45-minute job timeout
both bind well before 200 turns of useful work does.

A page must not guess, so the fixer-failure reason now distinguishes
THREE states: the branch advanced (applied, unreviewed, with the sha), it
did not (nothing pushed), or the head could not be read (say that). The
flat old sentence sent this investigation after an unresponsive fixer
instead of an unreviewed commit.

Also corrects a harness-engineering.md line claiming a paged PR still
re-reviews on every CI-green push -- the gate's paged latch ended that.

verify:self green 11/11, workflow parses. The three-state reason logic was
extracted from the YAML and executed against wafflebase#648's real shape, a genuine
no-push, and an unreadable head, since inline github-script JS can hold
neither a test nor a linter. The remaining always() uses in this file are
step-level, and the one that writes state (Post per-lens check runs) is
self-healing because check runs resolve latest-per-name.

NOT here: don't latch on a page whose premise the branch contradicts.
That needs a decision about who may clear a latch. wafflebase#648 itself still
needs unblocking by hand so one round can review 102e0fa.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@hwisoo-kim
hwisoo-kim requested a review from a team as a code owner August 3, 2026 16:23
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@hwisoo-kim, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 37 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8eaa739a-50e5-43f2-95e6-92dcdec927f5

📥 Commits

Reviewing files that changed from the base of the PR and between abb6eaf and 5be41e5.

📒 Files selected for processing (3)
  • .github/workflows/agent-review-panel.yml
  • docs/design/harness-engineering.md
  • docs/tasks/active/20260804-panel-concurrency-todo.md
📝 Walkthrough

Walkthrough

The review panel workflow now cancels superseded runs for each PR head branch, increases the fixer turn limit, skips paging for cancelled runs, and classifies fixer failures by current branch state. Design and task documentation record these changes and deferred follow-up work.

Changes

Review panel reliability

Layer / File(s) Summary
Branch run serialization and cancellation
.github/workflows/agent-review-panel.yml, docs/design/harness-engineering.md
Panel runs use branch-keyed concurrency with cancellation. Cancelled runs no longer trigger stalled-state paging. Documentation clarifies the separate gate-latch behavior.
Fixer capacity and branch-state reporting
.github/workflows/agent-review-panel.yml, docs/tasks/active/20260804-panel-concurrency-todo.md
The fixer limit increases from 80 to 200 turns. Failure paging distinguishes unreadable, advanced, and unchanged branch heads. The task document records verification and deferred follow-up work.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PRHead
  participant PanelWorkflow
  participant FixerClaude
  participant FailurePager
  PRHead->>PanelWorkflow: trigger review panel
  PanelWorkflow->>PanelWorkflow: cancel superseded branch run
  PanelWorkflow->>FixerClaude: run fixer with 200-turn limit
  FixerClaude-->>PanelWorkflow: fixer result
  PanelWorkflow->>FailurePager: compare reviewed SHA with current PR head
  FailurePager-->>PRHead: report branch-state-specific outcome
Loading

Possibly related PRs

Suggested reviewers: hackerwins, dlgpdmsly2, harrykim8672

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the two primary changes: branch-based review-panel concurrency and the increased fixer limit.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/agent-review-panel.yml:
- Around line 1434-1453: Wrap the current-head PR lookup and head SHA extraction
used before advanced classification in try/catch, setting advanced to null when
pulls.list or related map access fails so the “could not read the branch head”
message is emitted. Reuse the PR number already emitted by gate for both the
returned PR and head lookup, avoiding a second PR resolution.
- Around line 1454-1459: Update the stalled-reporting logic that builds
fixReason so it does not claim the fixer pushed changes solely because the
branch advanced. Use the fixer-step result and recorded pre-fix SHA when
available to establish provenance; otherwise describe the branch movement as
unknown and avoid attributing it to the fixer. Preserve distinct messaging for
confirmed fixer failure with no push and for unreadable branch state.
- Around line 1381-1394: Move the stuck-check cleanup out of the `stalled` job
into a separate cancellation-safe cleanup job that runs for superseded or
cancelled panel runs and closes any `agent-review-*` checks left in progress for
the old SHA. Keep the existing `stalled` paging condition under `!cancelled()`
and preserve its genuine-failure behavior; update dependencies and finalization
references as needed so cleanup still executes when `stalled` is skipped.
- Around line 53-55: Update the workflow-level concurrency group to include
github.event.workflow_run.head_repository.full_name alongside head_branch,
ensuring runs from different source repositories cannot share a cancellation key
while preserving cancel-in-progress behavior.

In `@docs/tasks/active/20260804-panel-concurrency-todo.md`:
- Line 82: Update the line beginning with “#648” in the task document so the
issue reference is written as normal text rather than a Markdown heading
trigger, while preserving the existing wording and reference.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a35e2fc9-7b86-485c-a8d8-43774741b978

📥 Commits

Reviewing files that changed from the base of the PR and between 2d41c1e and abb6eaf.

📒 Files selected for processing (3)
  • .github/workflows/agent-review-panel.yml
  • docs/design/harness-engineering.md
  • docs/tasks/active/20260804-panel-concurrency-todo.md

Comment thread .github/workflows/agent-review-panel.yml
Comment thread .github/workflows/agent-review-panel.yml
Comment on lines +1434 to +1453
// Did the branch move? `workflow_run.head_sha` is the sha this panel
// reviewed; `pr.head.sha` is the branch NOW. A fix job can fail with
// the changes already pushed — its own commit landed and a later step
// died, or (before the concurrency guard above) a duplicate run pushed
// while this one burned its turn ceiling.
//
// On #648 the flat "the requested changes were not applied" was simply
// FALSE: `102e0fa73` was on the branch and CI-green three minutes
// before this comment was written. That sentence then sent a human
// looking for an unresponsive fixer instead of an unreviewed commit,
// and the paged latch meant nothing ever reviewed it. A page is the
// one message a human is guaranteed to read, so it is the last place
// that should guess.
// Three states, not two. "We could not read the head" must not print
// as "pushed nothing" — that is the same false certainty in a
// different place, and it would be wrong exactly when a human most
// needs to know where to look.
const reviewedSha = context.payload.workflow_run.head_sha;
const headSha = typeof pr.head?.sha === 'string' ? pr.head.sha : '';
const advanced = headSha === '' ? null : headSha !== reviewedSha;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files:"
git ls-files | rg '(^|/)agent-review-panel\.yml$|agent-review-panel'

echo
echo "Relevant section:"
sed -n '1380,1485p' .github/workflows/agent-review-panel.yml

echo
echo "Search for reads of pr.head.sha and pulls/list usages:"
rg -n "context\.payload\.workflow_run\.head_sha|pulls\.list|pr\.head|advanced|workflow_run|gate" .github/workflows/agent-review-panel.yml

Repository: wafflebase/wafflebase

Length of output: 11904


Handle current-head read failures before the three-state classification.

pulls.list runs before advanced is computed, so a transient read error exits the step without leaving advanced === null. Add a try/catch around the current head lookup and map lookup failures to advanced === nullso the “could not read the branch head” message is emitted for failed reads. Reuse the PR number emitted bygate` for the returned PR and head lookup to avoid resolving the same PR twice.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/agent-review-panel.yml around lines 1434 - 1453, Wrap the
current-head PR lookup and head SHA extraction used before advanced
classification in try/catch, setting advanced to null when pulls.list or related
map access fails so the “could not read the branch head” message is emitted.
Reuse the PR number already emitted by gate for both the returned PR and head
lookup, avoiding a second PR resolution.

Comment on lines +1454 to +1459
const fixReason =
advanced === null
? "the fixer agent's job failed, and this run could not read the branch head to tell whether anything was pushed — check the branch before assuming the changes are missing"
: advanced
? `the fixer agent's job failed, but the branch DID advance to \`${headSha.slice(0, 9)}\` — changes were applied and have NOT been reviewed (a common cause is the fixer exhausting its turn budget after pushing)`
: 'the fixer agent failed and pushed nothing, so the requested changes were not applied';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file exists =="
git ls-files | grep -F '.github/workflows/agent-review-panel.yml' || true

echo "== relevant lines =="
sed -n '1380,1485p' .github/workflows/agent-review-panel.yml | nl -ba -v1380

echo "== searches for workflow_run.head_sha, r.fix, advanced, headSha, pre-fix =="
rg -n "workflow_run\.head_sha|r\.fix|advanced|headSha|pre-fix|reviewed|stalled|stalled reporting|runId|workflow_dispatch|Agent Review" .github/workflows/agent-review-panel.yml

Repository: wafflebase/wafflebase

Length of output: 279


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== relevant lines =="
sed -n '1380,1485p' .github/workflows/agent-review-panel.yml

echo "== searches for workflow_run.head_sha, r.fix, advanced, headSha, pre-fix, stalled =="
rg -n "workflow_run\.head_sha|r\.fix|advanced|headSha|pre-fix|reviewed|stalled|runId|workflow_dispatch|Agent Review" .github/workflows/agent-review-panel.yml

Repository: wafflebase/wafflebase

Length of output: 10161


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow jobs outline around fix/stalled =="
rg -n "name: (check|deps|review-panel|promote|fix|Reconcile agent state|Page a human|Reconcile|stalled)" .github/workflows/agent-review-panel.yml

echo "== job names/results and fix/job dependencies =="
sed -n '1,120p' .github/workflows/agent-review-panel.yml
rlimit=`wc -l < .github/workflows/agent-review-panel.yml`; echo "total lines: $rlimit"
sed -n '800,1340p' .github/workflows/agent-review-panel.yml | sed -n '/name: fix|name: promote|name: review-panel|needs:/,/_fixer\|checkout\|npx\|dependency\|install/p'

Repository: wafflebase/wafflebase

Length of output: 6895


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== jobs block =="
sed -n '125,260p' .github/workflows/agent-review-panel.yml

echo "== fix job block and relevant step names =="
sed -n '1130,1280p' .github/workflows/agent-review-panel.yml

Repository: wafflebase/wafflebase

Length of output: 15867


Do not attribute every branch move to the fixer.

r.fix === 'failure' also covers failures before Address panel findings, including Review-round guard, token creation, checkout, dependency installation, and state setup. In addition, advanced compares the current PR head with workflow_run.head_sha, not with the recorded pre-fix SHA. A human push or another run can move the branch from the reviewed SHA without the fixer running or pushing. Pass the fixer-step result and pre-fix SHA into stalled reporting, or use wording that leaves push provenance unknown.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/agent-review-panel.yml around lines 1454 - 1459, Update
the stalled-reporting logic that builds fixReason so it does not claim the fixer
pushed changes solely because the branch advanced. Use the fixer-step result and
recorded pre-fix SHA when available to establish provenance; otherwise describe
the branch movement as unknown and avoid attributing it to the fixer. Preserve
distinct messaging for confirmed fixer failure with no push and for unreadable
branch state.

worth one more panel round. It needs a decision about who may clear a latch, which
is a trust question rather than a bug fix.

#648 itself still needs unblocking by hand so one round can review `102e0fa73`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the Markdownlint warning.

#648 at the start of the line triggers MD018. Write the issue reference as normal text.

Suggested change
-#648 itself still needs unblocking by hand so one round can review `102e0fa73`.
+Issue `#648` still needs unblocking by hand so one round can review `102e0fa73`.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#648 itself still needs unblocking by hand so one round can review `102e0fa73`.
Issue `#648` still needs unblocking by hand so one round can review `102e0fa73`.
🧰 Tools
🪛 markdownlint-cli2 (0.23.1)

[warning] 82-82: No space after hash on atx style heading

(MD018, no-missing-space-atx)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/tasks/active/20260804-panel-concurrency-todo.md` at line 82, Update the
line beginning with “#648” in the task document so the issue reference is
written as normal text rather than a Markdown heading trigger, while preserving
the existing wording and reference.

Source: Linters/SAST tools

@codecov

codecov Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Review response on wafflebase#649. Three fixed, two skipped with reasons.

A FORK COULD CANCEL A LEGITIMATE PANEL. `concurrency` is evaluated at the
workflow level, BEFORE `gate` applies its head_repository check, so a key
of head_branch alone let anyone fork this PUBLIC repo, open a PR from a
branch named to match an in-flight agent branch, and have their CI
completion cancel the real panel -- denial of review from an unprivileged
position, and the cancelling run is then skipped by the gate, so nothing
records the review that should have happened. The group now includes
head_repository.full_name. Same threat model the paged latch in `gate`
already reasons about out loud.

MOVING `stalled` TO !cancelled() STRANDED THE STUCK-CHECK CLEANUP. That
step exists for the "panel KILLED mid-run (timeout/cancel)" case, in its
own words, and rode on `stalled`'s always(). Taking `stalled` off always()
removed it at exactly the moment the concurrency guard made cancellation
the COMMON outcome, so every superseded round would have left six
agent-review-* checks spinning forever. Split into a close-stuck-checks
job that runs on cancellation, never pages, never labels, and holds
checks:write alone. A separate job rather than another condition because
the two need OPPOSITE cancellation behaviour -- paging must not happen on
a cancelled run and cleanup must -- which is precisely why bundling them
regressed.

MY OWN FIX STILL ATTRIBUTED THE PUSH. "the branch DID advance ... changes
were applied" claims the FIXER pushed the REQUESTED fix, and this run can
observe neither: a human push and a concurrent run's fixer are
indistinguishable from here. That is the same unproven claim the old
sentence made, pointing the other way. It now states both shas and says a
commit landed unreviewed, without saying who or what.

SKIPPED: wrapping the pulls.list lookup in try/catch and reusing gate's PR
number. The `advanced` computation cannot throw (typeof pr.head?.sha ===
'string'), so the new path is covered; a pulls.list throw failing the step
is pre-existing behaviour, and gate emits the PR number but not the head
sha this needs, so reusing it would still cost a second call.

SKIPPED: `wafflebase#648` at line start rendering as a heading. CommonMark requires
whitespace after the # run, so it is not an ATX heading -- GitHub follows
CommonMark and auto-links it, which is the intent.

verify:self green 11/11, workflow parses, 7 jobs resolve. The reason logic
was re-executed against all three shapes and asserted to carry no
unproven attribution. Both docs re-synced: four lines went stale with
these fixes and are corrected rather than left describing the previous
shape.

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

@harrykim8672 harrykim8672 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for your contribution

@harrykim8672
harrykim8672 merged commit b252617 into wafflebase:main Aug 3, 2026
4 checks passed
hwisoo-kim added a commit to hwisoo-kim/wafflebase that referenced this pull request Aug 4, 2026
`@claude rerun` on wafflebase#632 and wafflebase#648 said "Re-running CI now; the review
panel will run again". CI genuinely re-ran -- run_started_at 06:01,
success at 06:15 -- and NO panel run was created. Both PRs sat with stale
verdicts and no fixer, twice, with nothing reporting a failure.

A RE-RUN DOES NOT EMIT workflow_run: requested. That fires when a run is
CREATED, and a re-run reuses the run id, so only `completed` fires. The
panel's trigger became `requested`-only in wafflebase#651 -- correctly, for the
latency win -- and `@claude rerun`'s entire mechanism is reRunWorkflow on
the PR's CI run. The command was uncoupled from the trigger it depends on
without either being touched.

Confirmed rather than inferred: at 06:15, agent-iterate-ci.yml -- which
listens to `completed` -- fired for both PRs, and no Agent Review Panel
run exists anywhere in the window (checked by creation time and by
completion time, paginated).

The panel now subscribes to [requested, completed], and the gate admits
`completed` ONLY when run_attempt > 1:

  requested / attempt 1  -> admit   fresh CI, the parallel-start path
  completed / attempt 1  -> refuse  requested already started this round
  completed / attempt 2+ -> admit   a re-run, where requested never fires

So one panel per CI run, and subscribing to both does not double the ~$12
round. Two existing properties make that safe rather than merely intended:
the concurrency group from wafflebase#649 cancels a superseded run, so even if
`requested` did fire on a re-run the pair collapses to one; and the `ci`
job's decide() returns 'proceed' immediately for an already-completed
successful run, so the completed-triggered path does not park waiting for
a conclusion it already has.

wafflebase#651's own guard asserted `types: [requested]` as a literal, with the
reasoning that subscribing to both doubles every round. That reasoning
held for a fresh run and missed re-runs entirely. The test now pins the
stronger contract -- requested present, completed present, and the gate
clause that prevents the doubling -- so what stops the cost is asserted
where it actually lives. Mutation-tested: removing the gate clause fails
it.

The four trigger cases were evaluated by extracting the gate expression
from the YAML and running it, since a workflow `if:` holds no test.

verify:self green 11/11.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
hwisoo-kim added a commit to hwisoo-kim/wafflebase that referenced this pull request Aug 4, 2026
`@claude rerun` on wafflebase#632 and wafflebase#648 said "Re-running CI now; the review
panel will run again". CI genuinely re-ran -- run_started_at 06:01,
success at 06:15 -- and NO panel run was created. Both PRs sat with stale
verdicts and no fixer, twice, with nothing reporting a failure.

A RE-RUN DOES NOT EMIT workflow_run: requested. That fires when a run is
CREATED, and a re-run reuses the run id, so only `completed` fires. The
panel's trigger became `requested`-only in wafflebase#651 -- correctly, for the
latency win -- and `@claude rerun`'s entire mechanism is reRunWorkflow on
the PR's CI run. The command was uncoupled from the trigger it depends on
without either being touched.

Confirmed rather than inferred: at 06:15, agent-iterate-ci.yml -- which
listens to `completed` -- fired for both PRs, and no Agent Review Panel
run exists anywhere in the window (checked by creation time and by
completion time, paginated).

The panel now subscribes to [requested, completed], and the gate admits
`completed` ONLY when run_attempt > 1:

  requested / attempt 1  -> admit   fresh CI, the parallel-start path
  completed / attempt 1  -> refuse  requested already started this round
  completed / attempt 2+ -> admit   a re-run, where requested never fires

So one panel per CI run, and subscribing to both does not double the ~$12
round. Two existing properties make that safe rather than merely intended:
the concurrency group from wafflebase#649 cancels a superseded run, so even if
`requested` did fire on a re-run the pair collapses to one; and the `ci`
job's decide() returns 'proceed' immediately for an already-completed
successful run, so the completed-triggered path does not park waiting for
a conclusion it already has.

reasoning that subscribing to both doubles every round. That reasoning
held for a fresh run and missed re-runs entirely. The test now pins the
stronger contract -- requested present, completed present, and the gate
clause that prevents the doubling -- so what stops the cost is asserted
where it actually lives. Mutation-tested: removing the gate clause fails
it.

The four trigger cases were evaluated by extracting the gate expression
from the YAML and running it, since a workflow `if:` holds no test.

verify:self green 11/11.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
harrykim8672 pushed a commit that referenced this pull request Aug 4, 2026
harvest.mjs keyed "when did the panel let go of this PR" off the
<!-- agent-handoff --> comment, with the ready_for_review timeline event
as a fallback, and then read "what had the panel concluded" THROUGH that
cutoff. Four problems, and the fourth is the one that cost data.

- No marker, no harvest. mark-ready.mjs posts the marker inside a
  try/catch AFTER flipping the PR ready, so a genuinely promoted PR can
  carry none. Measured: 18 of 33 agent PRs have neither a marker nor a
  ready_for_review event, having been opened ready rather than promoted.
- ready_for_review answers a different question. It fires whether or not
  the panel ever spoke, so on a manually-readied PR every later human
  commit became a candidate. It over-fired on the PRs it covered and
  covered none of the PRs it was reached for.
- A rebase erased it. The cutoff was a comment timestamp compared against
  COMMITTER DATES, so a force-push after promotion pushed every commit
  past it, emptied beforeHandoff, and left headAtHandoff undefined.
- Signature 2 read the panel through signature 1's cutoff. The comparison
  set came from whichever commit was head at hand-off, so no hand-off
  meant no set and every CodeRabbit candidate withheld on a PR whose
  check runs were readable the whole time -- and one set was applied to
  every finding regardless of which commit it was about.

Signature 2 never needed a hand-off: "CodeRabbit flagged something our
panel reviewed and did not raise" is exactly as true on a human PR
reviewed via @claude review, where nothing was handed off at all.

The panel's own check runs are the only signal that is present whenever
the panel ran, carries a server timestamp, and means "the panel
approved". completed_at is stamped by GitHub, so the rebase that rewrites
every committer date on the PR cannot move it. panelRounds walks the
commit list and yields one round per commit the panel CONCLUDED on;
panelApprovedAt takes the cutoff from those; roundsUpTo resolves a
comparison set PER CodeRabbit COMMENT, from the rounds at or before the
commit that comment is about.

ready_for_review is removed outright rather than demoted. Keeping it as a
last resort would preserve the over-fire in exactly the cases nothing
else can check. Its timeline request is gone with it. The marker stays as
a fallback for a PR with no readable round, because it does mean the
panel approved -- it is only unreliably present.

An @claude review is a round too (commentRounds), with conclusion "" --
not a missing value but the fact that it reached no gate conclusion,
which is also what keeps it out of panelApprovedAt. It used to be a
whole-PR fallback reached only when check runs were absent; as a round it
gets the same per-commit treatment the gating arm does, and gating and
advisory rounds are now UNIONED rather than ranked. The record's claim is
"the panel did not raise this", and both arms run the same lens panel, so
a finding either raised is a finding the panel raised. Ranking made sense
when the question was which rendering of ONE round to trust; across
rounds it discards real findings and files them as misses. panelSaw still
prefers the gating round, which is where "check runs are the structured
record" is actually observable.

Two phases, because establishing the history is cheap and reading it is
not. panelRoundAt splits out the half answerable from the check-runs LIST
response (conclusion, reviewedSha, completedAt); the per-run
withFullOutput refetch that carries output.text runs only for rounds a
candidate is actually compared against, cached by sha. panelVerdictAt now
calls panelRoundAt, so the aggregate conclusion rule ("one failing lens
means the panel did not let it through") stays in one place instead of
growing a second copy for the cheap path.

Corrected while building, and only real data caught it: the first
implementation took the NEWEST all-success round. Every human fix opens a
new round, and that round approves too -- so on #548 the cutoff landed on
2026-07-28, four days after the three human fixes of 2026-07-25, and lost
all three, including rows a human has already curated into misses.jsonl.
It is the FIRST approval, matching markerHandoffAt's own "FIRST marker,
not the last": a later re-approval does not un-say the first one.

An unreadable commit yields an `unreadable: true` round rather than
nothing, which keeps the property the previous code deliberately had --
"we know which commit the panel was looking at and we could not read what
it concluded" -- and makes the comparison set refuse rather than resolve
to [], which would file every CodeRabbit finding on the PR as a miss
against a verdict nobody has seen. One rule covers both unplaceable
directions: what cannot be placed cannot be excluded.

No schema change. wafflebase/miss@1, same fields, same order; handoffAt
keeps its name and now means "when the panel approved", which is what
every consumer already assumed. Ids are unchanged, so dedupeById's
first-wins rule still protects every curated verifiedBy.

676 agent tests green, 662 on main; eslint scripts clean under the
lockfile's pinned 9.24.0. Real PRs before and after, read-only: candidate
sets IDENTICAL across #548, #559, #578, #582, #591, #649 and the
no-marker population #581, #590, #605, #652, with #548 re-harvesting to
the same four ids. API cost measured at 13 to 24 calls on a 9-commit PR.

Stated plainly because the plan predicted otherwise: there is NO
measurable gain on today's data. #581, #590 and #605 have no marker, but
the panel never approved them either, and they carry no CodeRabbit
blocking findings -- so neither signature was unlocked. The gains are
demonstrated synthetically. What this buys is a mechanism that is correct
for the next PR; the rebase case and the per-comment anchoring are latent
bugs rather than observed losses.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
hwisoo-kim added a commit to hwisoo-kim/wafflebase that referenced this pull request Aug 4, 2026
The review panel caught this and it is the worst thing in this branch: as
pushed, subscribing to `completed` DISABLED THE REVIEW PANEL on every PR.

`concurrency` is claimed at the WORKFLOW level -- at run creation, before
any job's `if:` is evaluated. With one undifferentiated group, a fresh CI
run's `completed` event creates a second run that lands in the same
`cancel-in-progress` group and KILLS the panel the `requested` event
started ~13 minutes earlier, mid-review. The second run then refuses the
event and skips every job, so no verdicts are recorded -- and `stalled` is
`!cancelled()`, added in wafflebase#649, so nothing pages either. The panel takes
~14 min against CI's ~13, so the collision is near-certain rather than
occasional. A job-level `if:` cannot protect a group already claimed on
its behalf.

My commit message cited that same concurrency guard as the reason the
change was SAFE. It was the mechanism of the failure. I reasoned only
about a re-run emitting both events and never about the fresh-run case
the timing makes routine.

The group now carries a suffix. Runs that will be refused (`completed` on
attempt 1) land in a `noop` lane where cancelling each other costs
nothing; every run that will actually review shares `active`, which keeps
what the guard exists for -- wafflebase#605's two same-second contradictory
verdicts, wafflebase#648's two fixers on one branch for 21 minutes.

That puts the admission rule in TWO places, so checks.test.mjs now
extracts both the gate `if:` and the group suffix from the YAML,
evaluates them across all four event/attempt combinations, and fails if
they disagree. It also asserts the partition is not degenerate: a group
that always answered `active` would pass a same-answer check while
restoring the bug. Mutation-tested by removing the suffix.

The design doc's claim that concurrency made this safe is corrected rather
than deleted -- it was wrong in a way worth recording.

Also fixes three violations the wafflebase#658 lint caught in this branch's own new
test code: a literal double-space in a regex, and two eslint-disable
directives for a rule that is not enabled.

verify:self green 11/11, eslint scripts clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
harrykim8672 pushed a commit to hwisoo-kim/wafflebase that referenced this pull request Aug 4, 2026
`@claude rerun` on wafflebase#632 and wafflebase#648 said "Re-running CI now; the review
panel will run again". CI genuinely re-ran -- run_started_at 06:01,
success at 06:15 -- and NO panel run was created. Both PRs sat with stale
verdicts and no fixer, twice, with nothing reporting a failure.

A RE-RUN DOES NOT EMIT workflow_run: requested. That fires when a run is
CREATED, and a re-run reuses the run id, so only `completed` fires. The
panel's trigger became `requested`-only in wafflebase#651 -- correctly, for the
latency win -- and `@claude rerun`'s entire mechanism is reRunWorkflow on
the PR's CI run. The command was uncoupled from the trigger it depends on
without either being touched.

Confirmed rather than inferred: at 06:15, agent-iterate-ci.yml -- which
listens to `completed` -- fired for both PRs, and no Agent Review Panel
run exists anywhere in the window (checked by creation time and by
completion time, paginated).

The panel now subscribes to [requested, completed], and the gate admits
`completed` ONLY when run_attempt > 1:

  requested / attempt 1  -> admit   fresh CI, the parallel-start path
  completed / attempt 1  -> refuse  requested already started this round
  completed / attempt 2+ -> admit   a re-run, where requested never fires

So one panel per CI run, and subscribing to both does not double the ~$12
round. Two existing properties make that safe rather than merely intended:
the concurrency group from wafflebase#649 cancels a superseded run, so even if
`requested` did fire on a re-run the pair collapses to one; and the `ci`
job's decide() returns 'proceed' immediately for an already-completed
successful run, so the completed-triggered path does not park waiting for
a conclusion it already has.

reasoning that subscribing to both doubles every round. That reasoning
held for a fresh run and missed re-runs entirely. The test now pins the
stronger contract -- requested present, completed present, and the gate
clause that prevents the doubling -- so what stops the cost is asserted
where it actually lives. Mutation-tested: removing the gate clause fails
it.

The four trigger cases were evaluated by extracting the gate expression
from the YAML and running it, since a workflow `if:` holds no test.

verify:self green 11/11.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
harrykim8672 pushed a commit to hwisoo-kim/wafflebase that referenced this pull request Aug 4, 2026
The review panel caught this and it is the worst thing in this branch: as
pushed, subscribing to `completed` DISABLED THE REVIEW PANEL on every PR.

`concurrency` is claimed at the WORKFLOW level -- at run creation, before
any job's `if:` is evaluated. With one undifferentiated group, a fresh CI
run's `completed` event creates a second run that lands in the same
`cancel-in-progress` group and KILLS the panel the `requested` event
started ~13 minutes earlier, mid-review. The second run then refuses the
event and skips every job, so no verdicts are recorded -- and `stalled` is
`!cancelled()`, added in wafflebase#649, so nothing pages either. The panel takes
~14 min against CI's ~13, so the collision is near-certain rather than
occasional. A job-level `if:` cannot protect a group already claimed on
its behalf.

My commit message cited that same concurrency guard as the reason the
change was SAFE. It was the mechanism of the failure. I reasoned only
about a re-run emitting both events and never about the fresh-run case
the timing makes routine.

The group now carries a suffix. Runs that will be refused (`completed` on
attempt 1) land in a `noop` lane where cancelling each other costs
nothing; every run that will actually review shares `active`, which keeps
what the guard exists for -- wafflebase#605's two same-second contradictory
verdicts, wafflebase#648's two fixers on one branch for 21 minutes.

That puts the admission rule in TWO places, so checks.test.mjs now
extracts both the gate `if:` and the group suffix from the YAML,
evaluates them across all four event/attempt combinations, and fails if
they disagree. It also asserts the partition is not degenerate: a group
that always answered `active` would pass a same-answer check while
restoring the bug. Mutation-tested by removing the suffix.

The design doc's claim that concurrency made this safe is corrected rather
than deleted -- it was wrong in a way worth recording.

Also fixes three violations the wafflebase#658 lint caught in this branch's own new
test code: a literal double-space in a regex, and two eslint-disable
directives for a rule that is not enabled.

verify:self green 11/11, eslint scripts clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
harrykim8672 pushed a commit that referenced this pull request Aug 4, 2026
* Grant pull-requests:write where labels are actually written

`@claude rerun` on #632 and #648 announced that it had dropped
agent:blocked. Both stayed blocked. The run logs say why:

  ##[warning]Could not remove agent:blocked
            (Resource not accessible by integration).

LABELS ON A PULL REQUEST NEED pull-requests:write. A PR is an issue for
most of the API and its COMMENTS are reachable with issues:write -- which
is why the paged-comment deletion in the same step succeeded, reporting
"cleared 1 paged marker(s)" -- but its LABELS are not. The job held
pull-requests:read.

AND THE SUMMARY CLAIMED THE DROP REGARDLESS. The old code warned on
failure and then built the message unconditionally, so a human read
"dropped agent:blocked" with the label sitting right next to it. It now
reports which of dropped / was not set / could not drop actually
happened; the three branches were extracted from the YAML and executed,
since inline github-script JS holds no test.

THE SAME GRANT WAS MISSING FROM review-panel, and there it has been
silently dead since it was written: agent:reviewing has NEVER appeared on
a PR. set-state.mjs is fail-safe -- any API error logs and exits 0, which
is right for a label that gates nothing -- so the step reported success
every time. Confirmed by timeline rather than assumed: #648, #632, #605
and #633 all go implementing -> fixing -> blocked with no reviewing state
between them. The comment above that grant claimed "labels use the issues
API", which is the misconception itself, and is now corrected.

Widening review-panel is safe on the argument its issues:write already
rested on: the SDK step receives no GitHub token (its env is
CLAUDE_CODE_OAUTH_TOKEN only), so neither grant widens what
prompt-injected branch code can reach.

Enumerated rather than guessed. Jobs that write labels with the default
GITHUB_TOKEN: `iterate` inherits the grant workflow-level, `fix` and
`stalled` declare it, and these two were the only gaps. An earlier scan
also flagged agent-implement, wrongly -- its label write happens inside
the agent's PROMPT using the App token, whose installation permissions
the workflow block does not govern, and #648's timeline shows
agent:implementing applied successfully by yorkie-agent[bot].

The stale labels on #632 and #648 were removed by hand; both PRs' paged
markers were already gone, so the label was the only thing left wrong.

verify:self green 11/11.

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

* Re-engage the panel on a CI re-run, not just a fresh one

`@claude rerun` on #632 and #648 said "Re-running CI now; the review
panel will run again". CI genuinely re-ran -- run_started_at 06:01,
success at 06:15 -- and NO panel run was created. Both PRs sat with stale
verdicts and no fixer, twice, with nothing reporting a failure.

A RE-RUN DOES NOT EMIT workflow_run: requested. That fires when a run is
CREATED, and a re-run reuses the run id, so only `completed` fires. The
panel's trigger became `requested`-only in #651 -- correctly, for the
latency win -- and `@claude rerun`'s entire mechanism is reRunWorkflow on
the PR's CI run. The command was uncoupled from the trigger it depends on
without either being touched.

Confirmed rather than inferred: at 06:15, agent-iterate-ci.yml -- which
listens to `completed` -- fired for both PRs, and no Agent Review Panel
run exists anywhere in the window (checked by creation time and by
completion time, paginated).

The panel now subscribes to [requested, completed], and the gate admits
`completed` ONLY when run_attempt > 1:

  requested / attempt 1  -> admit   fresh CI, the parallel-start path
  completed / attempt 1  -> refuse  requested already started this round
  completed / attempt 2+ -> admit   a re-run, where requested never fires

So one panel per CI run, and subscribing to both does not double the ~$12
round. Two existing properties make that safe rather than merely intended:
the concurrency group from #649 cancels a superseded run, so even if
`requested` did fire on a re-run the pair collapses to one; and the `ci`
job's decide() returns 'proceed' immediately for an already-completed
successful run, so the completed-triggered path does not park waiting for
a conclusion it already has.

reasoning that subscribing to both doubles every round. That reasoning
held for a fresh run and missed re-runs entirely. The test now pins the
stronger contract -- requested present, completed present, and the gate
clause that prevents the doubling -- so what stops the cost is asserted
where it actually lives. Mutation-tested: removing the gate clause fails
it.

The four trigger cases were evaluated by extracting the gate expression
from the YAML and running it, since a workflow `if:` holds no test.

verify:self green 11/11.

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

* Partition the concurrency group, or `completed` kills the panel

The review panel caught this and it is the worst thing in this branch: as
pushed, subscribing to `completed` DISABLED THE REVIEW PANEL on every PR.

`concurrency` is claimed at the WORKFLOW level -- at run creation, before
any job's `if:` is evaluated. With one undifferentiated group, a fresh CI
run's `completed` event creates a second run that lands in the same
`cancel-in-progress` group and KILLS the panel the `requested` event
started ~13 minutes earlier, mid-review. The second run then refuses the
event and skips every job, so no verdicts are recorded -- and `stalled` is
`!cancelled()`, added in #649, so nothing pages either. The panel takes
~14 min against CI's ~13, so the collision is near-certain rather than
occasional. A job-level `if:` cannot protect a group already claimed on
its behalf.

My commit message cited that same concurrency guard as the reason the
change was SAFE. It was the mechanism of the failure. I reasoned only
about a re-run emitting both events and never about the fresh-run case
the timing makes routine.

The group now carries a suffix. Runs that will be refused (`completed` on
attempt 1) land in a `noop` lane where cancelling each other costs
nothing; every run that will actually review shares `active`, which keeps
what the guard exists for -- #605's two same-second contradictory
verdicts, #648's two fixers on one branch for 21 minutes.

That puts the admission rule in TWO places, so checks.test.mjs now
extracts both the gate `if:` and the group suffix from the YAML,
evaluates them across all four event/attempt combinations, and fails if
they disagree. It also asserts the partition is not degenerate: a group
that always answered `active` would pass a same-answer check while
restoring the bug. Mutation-tested by removing the suffix.

The design doc's claim that concurrency made this safe is corrected rather
than deleted -- it was wrong in a way worth recording.

Also fixes three violations the #658 lint caught in this branch's own new
test code: a literal double-space in a regex, and two eslint-disable
directives for a rule that is not enabled.

verify:self green 11/11, eslint scripts clean.

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

---------

Co-authored-by: Harry Kim <309633041+hwisoo-kim@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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