CI: interactive PR control panel comment - #9104
Conversation
Adds a bot comment that acts as a form: markdown task-list checkboxes are the controls, ticking one is delivered as `issue_comment: edited`, and the workflow redraws the comment from canonical state and dispatches a second workflow when a button is pressed. This is the UX groundwork for replacing the `action/benchmark*` label dance, where the combinations have outgrown what a label list can display. Nothing is wired to a real benchmark yet: `pr-panel-run.yml` only renders the state it received, proving the settings survive the hop into a separate workflow run. - `.github/scripts/pr_panel` holds the declarative control spec plus the render, parse, and click-semantics code, with toggles, pick-one radio groups, momentary buttons, and collapsible sections. - `pr-panel.yml` posts the panel on PR open and applies clicks. - `pr-panel-run.yml` is the dispatched placeholder for a future benchmark run. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T8xYqGxvVBTBjXbN8qa6Vw
Merging this PR will improve performance by 26.88%
Performance Changes
Tip Curious why this is faster? Comment Comparing Footnotes
|
`issue_comment` and `pull_request_target` workflows only ever run from the default branch, so as first written none of the panel could fire on the PR that introduces it. Two changes close that gap. Same-repo PRs now post the panel via `pull_request`, which runs the PR's own version of the code; forks keep `pull_request_target`, since a fork gets a read-only token under `pull_request` and its head must never be checked out. Beyond testing, this means anyone iterating on the panel sees their render on their own PR. Clicks are handled by a new `preview` job, gated to PRs that touch the panel. It polls the panel comment for 30 minutes and applies what it finds; everything downstream of "the body changed" is the same code the webhook path runs. Dispatching `pr-panel-run.yml` is the one hop that cannot be exercised pre-merge, so the preview renders the run report inline instead. Also adds a `notice` banner to the panel state, used to tell the reader that their clicks are live and until when. It survives clicks, so a future run-in-progress banner has somewhere to live. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T8xYqGxvVBTBjXbN8qa6Vw
Benchmark Control PanelConfigure a benchmark run by clicking the checkboxes below, then press Run to dispatch it. Your click is applied by a workflow, so the panel takes a few seconds to redraw. Important Preview window closed. Push to this branch to reopen it. Note Nothing dispatched yet. SuitesWhich benchmark suites to include in the run.
RunnerWhere the run executes. Machine — pick one:
OptionsExtra knobs applied to every selected suite. Matrix preset — pick one:
ActionsTick one of these to press it. GitHub strips
Checkboxes are editable by anyone with write access. Every click is applied by the |
Thirty minutes only helps if you happen to be watching the PR when the run starts. The window is now a single env knob on the job, and the banner derives its wording from it rather than hardcoding the number. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T8xYqGxvVBTBjXbN8qa6Vw
Panel run reportDispatched by: Rendered by this workflow run.
Panel revision 3. |
GitHub strips `<button>` from comment markdown, so a task-list checkbox is the only clickable control there is. Lean into it instead of hiding it: the action rows now carry a glyph and a caps label so they read as buttons rather than as settings, and the section help says plainly why they are checkboxes. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T8xYqGxvVBTBjXbN8qa6Vw
Whether `pr_panel` and `fuzz_report` count as first-party was inferred from the directory ruff happened to be invoked from, so the import-order lint flipped between callers. Name them explicitly. Signed-off-by: "Joe Isaacs" <joe.isaacs@live.co.uk> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T8xYqGxvVBTBjXbN8qa6Vw
|
where's the polarsignals comment? |
|
no idea what do you think of the UX? |
Rationale for this change
Benchmarks are triggered today by adding an
action/benchmark*label, which the bot removes once the run starts. That works for a handful of switches, but the combinations (suite × runner × preset × flags) have outgrown what a flat label list can express or display — you cannot see the current configuration anywhere, and every new knob is another label.This PR proves out an alternative UX: a comment posted unconditionally on every PR that behaves like a form. It is deliberately not wired to any real benchmark — the goal is to validate the interaction model before moving the benchmark plumbing onto it.
Does GitHub actually support this?
Yes, with one primitive: the markdown task-list checkbox is the only clickable control GitHub renders in a comment. Ticking one rewrites the comment body and delivers
issue_comment: edited. Everything else is built on top of that:The comment body is the entire storage layer. Each control is a tagged task-list line, and a trailing HTML comment records the state as of the last render:
Diffing the checkboxes against that blob identifies which control was clicked, which is what makes controls richer than a bare checkbox expressible:
ToggleRadioButtonSection<details>block.Properties that fall out of the design:
GITHUB_TOKEN, and edits made with that token do not trigger workflows.What changes are included in this PR?
.github/scripts/pr_panel/— the panel itself.spec.pyis a declarative control tree (adding a knob is a few lines there and nothing else);panel.pyholds render, parse, and click semantics;__main__.pyis the CLI used by the workflows, plus ademosubcommand that renders the panel and simulates clicks locally..github/workflows/pr-panel.yml— posts the panel on PR open/reopen, and applies clicks onissue_comment: edited..github/workflows/pr-panel-run.yml— the dispatched downstream workflow. It renders the received state as a table into the job summary and a PR comment, which is what demonstrates that the settings cross the workflow boundary intact. This is where a benchmark run would eventually be launched from..github/scripts/upsert-comment.sh— marker-based find-or-create so repeated runs edit one comment instead of piling up.ci.yml— apr-paneljob running the 24 unit tests (pure stdlib, well under a second)..github/scripts/pyproject.toml— pinspackages = ["fuzz_report"], so that adding a sibling package here does not breakpip install -e .github/scriptsvia setuptools flat-layout auto-discovery.Two things to validate on a live PR
Both are load-bearing assumptions that unit tests cannot cover, and neither takes effect until this is merged, because
issue_commentandpull_request_targetworkflows always run from the default branch:<details>block stays clickable. TheRunnerandOptionssections are deliberately collapsed to exercise this; if it does not hold, the fallback is rendering every section as a plain open heading, which is a one-line change inspec.py.What APIs are changed? Are there any user-facing changes?
No library or public API changes — this is CI tooling only. It adds no behaviour to existing workflows: the current
action/benchmark*labels andbench-dispatch.ymlare untouched, and the panel dispatches nothing but its own no-op reporting workflow.The user-facing change is a new bot comment on every PR once merged.
.github/scripts/pr_panel/README.mddocuments the design and how to iterate locally.Checks run
uv run --no-project --with pytest pytest .github/scripts/tests/test_pr_panel.py— 24 passedyamllint --strict -c .yamllint.yaml .github/— cleanruff check --config .github/scripts/pyproject.toml pr_panel tests— cleanreuse lint— compliantbash -n .github/scripts/upsert-comment.shuv pip install --no-deps -e .github/scriptsstill resolvesfuzz_reportafter the setuptools pinNot run: Rust checks (
cargo fmt/clippy/nextest), as no Rust, feature flags, or generated files are touched.🤖 Generated with Claude Code
https://claude.ai/code/session_01T8xYqGxvVBTBjXbN8qa6Vw
Generated by Claude Code