Skip to content

review: a Labels workflow puts area labels on every issue and pull request, and needs-gpu on a card change - #104

Merged
juliankang4 merged 1 commit into
midagedev:mainfrom
juliankang4:issue-pr-labels
Oct 13, 2026
Merged

juliankang4 merged 1 commit into
midagedev:mainfrom
juliankang4:issue-pr-labels

Conversation

@juliankang4

Copy link
Copy Markdown
Collaborator

What and why

Labels today come from the issue templates or from a person, so contributors' issues and all 44 pull requests carry
none. This adds .github/workflows/labels.yml and tools/review/labels.py, which label an issue by its title prefix
and a pull request by the paths it changes, and put needs-gpu on an open pull request that changes a device crate
until it has gpu-evidence or gpu-verified. On today's open items it would add 36 labels to 25 of the 43 open issues
and pull requests.

1. What it does

Of the 55 issues, the 37 that carry a label were all opened by the repository owner or the review bot. None of the 13
issues contributors opened has one. needs-gpu and gpu-evidence have not been put on any pull request yet, and
neither label exists in the repository.

With the workflow:

One exception: an issue that another workflow opens with GITHUB_TOKEN (Shingler's review-bot issues, the ledger
issue) starts no workflow, so it gets labels only from a workflow_dispatch run.

2. The labels

Area labels, by path for a pull request and by title prefix for an issue. The counts are from the dry run in section
6: open items first, then everything, with merged pull requests counted by their merge diff.

Label Paths Title prefix words Open: PRs / issues All: PRs / issues
gpu crates/gpu*, crates/app gpu, gpu-gates, gpu-glm5next, gpu-deepseek41, gpu-mimo2, gpu-vision, app 5 / 3 16 / 3
host crates/{qdot,threads,engram,engram-lab,runtime,sampler,levers,decision,vision,refset} the crate names 1 / 1 10 / 1
model crates/{model,models,hf,gguf,tokenizer,jinja} (the CPU engine and the model files) the crate names 2 / 5 8 / 7
serve crates/serve serve, serve tests 0 / 1 7 / 7
placement crates/placement placement 2 / 0 4 / 0
tools tools/, .github/, .cargo/, .config/, fuzz/, justfile, Cargo.toml, rust-toolchain.toml, deny.toml, clippy.toml, crates/oxide-ice-unroll tools, tools/..., ci, review, github, release, box.sh, fuzz, cargo, justfile, recipes 1 / 1 15 / 5
documentation docs/ except docs/cards/*.card, and any *.md outside crates/ and .github/ docs, docs/..., readme, contributing 0 / 0 8 / 0

Kind labels:

Label When Open: PRs / issues All: PRs / issues
dgx-spark title prefix dgx-spark:; or a pull request that changes docs/contrib/dgx-spark.md or docs/research/sparkdec.md 0 / 4 3 / 4
measure (new) title prefix measure:, 측정: or experiment: 0 / 5 0 / 7
bug, hardware an issue title in its template's form (bug: ..., hardware: ...), for an issue opened without the template 0 / 0 0 / 1
needs-gpu section 4 5 / 0 5 / 0
tracking (new) set by a person on a status issue that stays open and is updated; never set by the workflow

How the tables were chosen:

  • CRATE_AREA names every directory under crates/ today. A crate it lacks is gpu when its name starts with
    gpu- or it reaches cuda-core, and otherwise gets no area; the run's output then says crate not mapped: <name>.
    The self-test checks only that every entry in the table is still a crate (has a Cargo.toml), so a pull request
    that adds a crate does not have to touch tools/review/, which would make Shingler's review a COMMENT.
  • Some files change beside code in another area: Cargo.lock, THIRD_PARTY_NOTICES.md, the ratchets
    (unsafe-ratchet.txt, lint-ratchet.txt), levers-direct.txt, spawn-allow.txt, gate-paths.tsv,
    defaults-twins.tsv, tools/ref/ptx-shapes.tsv, tools/bloomery/schema/, tools/flow/plans/ and the prediction
    cards docs/cards/*.card. They give no area, so a gpu pull request that moves the unsafe ratchet or adds a card
    is not also labelled tools or documentation.
  • A title prefix word is a crate name, one of the aliases above, or a path. A word that names a concept rather than a
    place gets no area: in the last 300 commit subjects and the issue titles, MTP, paged, prefill, quant,
    host tier, GLM step and gate each span several areas. A word followed by tests or 테스트 counts as the
    word; serve seats does not count as serve, because the serve seats live in crates/gpu-gates.
  • The dgx-spark path rule covers only those two documents. The other files with "spark" in their name
    (crates/*/dspark*, gate_dspark_*) belong to the DSpark draft model, not to the DGX Spark, and no code path in
    the tree is used only on the Spark.
  • new-model stays with its template. A model: title cannot tell a new architecture (model: EmbeddingGemma 2 (gemma-embedding2, 0.74B dense) #59, model: GLM-5.3 (glm-dsa, 753B/active unverified) #60, model: MiMo-V2.6-Flash-RL (mimo2, 309B/15B) #61) from work on
    crates/model (model: qwen35moe PlanInputs::describe가 호스트 room을 직접 읽음 — describe_in(split, room)으로 분리 #21), so a model: issue gets the model area only.
  • hardware is only the template's meaning, a report from hardware we have not run. A request for a measurement on a
    given card is measure.

3. What it changes and what it leaves alone

  • It adds and removes only the labels it manages (the areas, documentation, dgx-spark, measure, bug,
    hardware) and needs-gpu. Everything else on an issue or pull request stays as it is, gpu-evidence and
    gpu-verified included.
  • It reads each item's label events. A managed label a person took off is not put back on a later push or edit. A
    label is removed only when the workflow put it on (its last labeled event is github-actions[bot]'s) and the paths
    or the title no longer name it, so a label a person added stays. needs-gpu follows the paths instead (section 4).
  • "The workflow put it on" means the GITHUB_TOKEN login, github-actions[bot], which Shingler's jobs share. Run
    outside GitHub Actions, the script is always a dry run, since a write there would carry another login.
  • It creates the labels the repository lacks, with the colour and description in LABELS. It does not change the
    colour or description of a label that exists (documentation, dgx-spark, bug, hardware keep theirs). A 422 on
    creation passes only when GitHub's error says already_exists; any other error stops the run. The card labels'
    definitions come from CARD_LABELS in post.py, where gpu-evidence moves from 0e8a16 (the existing hardware
    green) to bfe5bf. Neither card label exists in the repository yet, so nothing that exists changes colour.
  • When a pull request's file list is shorter than its changed_files (GitHub lists at most 3,000 files) or a merge
    diff reaches the compare API's 300-file cap, the run adds the areas it found, removes nothing on that item, decides no
    card label, and says so in its output.
  • The unused default labels (accessibility, duplicate, invalid, question, wontfix) and the template labels
    are not touched.

4. needs-gpu and Shingler

labels.py imports CARD_LABELS, device_dirs and api from tools/review/post.py, so both use one list of device
crates (tools/recipes.py pure-crates on the trusted tree) and one definition of each card label. A renamed file
counts for the card label by its new path only, as in Shingler's file list.

The rule matches apply_labels, and is applied only to an open pull request whose file list is complete and whose
device crates were computed:

  • It changes a device crate and has neither gpu-evidence nor gpu-verified: it gets needs-gpu, even when a person
    took needs-gpu off. A card check is recorded by a maintainer's gpu-verified, not by removing needs-gpu.
  • It no longer changes a device crate, or it has gpu-evidence: a needs-gpu that github-actions[bot] put on comes
    off. A needs-gpu a person put on stays.
  • On a closed or merged pull request, or when the device crates could not be computed (the job's output says so),
    the card labels stay as they are. The area labels are still applied.

For the release step, which lists merged pull requests with needs-gpu and no gpu-verified, this means:

  • A pull request Shingler skips (a draft, the no-review label, past five automatic reviews, a failed review job)
    still carries needs-gpu while it changes a device crate, so it reaches the list if merged that way.
  • One that dropped its device change before merge no longer stays on the list with a stale needs-gpu.
  • A backfill over closed pull requests adds no needs-gpu, so it puts no old merged pull request on the list.

The self-test runs labels.py and apply_labels against one fake pull request in four orders (workflow then bot,
bot then workflow, and the two alternating three and five times), for both bot verdicts and for a rename out of
crates/gpu. Each order ends with the same labels: the areas and the bot's card label, or no card label for the
rename. It also covers a person taking needs-gpu off, a pull request Shingler skipped after its device change went
away, a short file list, a merged pull request whose stale file list names crates/gpu, and a failed device list.

Concurrent runs

Each item's labels, title and events are read again after its file list and the device crates (about 5 s on a Mac),
right before the writes. The self-test runs Shingler between the workflow's first reads and its writes, and an issue
whose title changes while a backfill is running; both end with the labels of the latest state. What is left is the
time between the last read and the write landing, about one API round trip (a GET took about 400 ms from outside GitHub;
not measured on a runner). If Shingler adds gpu-evidence inside that window, the pull request carries both card labels
until the next labels run or Shingler's next review, either of which takes needs-gpu off while the pull request is
open. The Labels runs for one item queue in one concurrency group and none cancels another, so a merge does not cut
short the run of the push before it. A backfill runs in its own group, so it relies on the re-read.

5. The workflow

  • pull_request_target (opened, reopened, synchronize, edited, closed) and issues (opened, edited), so a fork's pull
    request gets a token that can label it. The job runs on an edit only when the title or base changed, and on a close
    only when it merged. It checks out the branch the workflow runs from (github.sha), as review.yml does for its
    tools, and never the pull request's head. Only the issue or pull request number reaches the script, through env;
    a title never enters a shell line. Each output line is one line of plain text with backticks replaced, so a title
    cannot close the job summary's code fence.
  • Permissions: contents: read, issues: write, pull-requests: read. Labels go through the Issues API; the pull
    request API is only read for the file list. I could not check from outside Actions that issues: write alone lets
    the token label a pull request; the first run after merge shows it.
  • RUSTUP_TOOLCHAIN=stable on the step: recipes.py reads the workspace with
    cargo metadata --offline --no-deps --locked, which stable does with an empty CARGO_HOME. Without it,
    rust-toolchain.toml would install the pinned nightly and its components on every run. The device list is computed
    only for an open pull request.
  • workflow_dispatch with state (open or all) and dry_run. A backfill waits 1 s between the writes of two
    items, as GitHub's rate limit guidance asks for a run of write requests. A rerun is safe: it adds only what is
    missing and removes only what no longer applies.
  • The self-test is listed in tools/check-recipes.sh's selftests=(, so contrib-check runs it.

After merge, the first workflow_dispatch run with dry_run on shows the plan for every open issue and pull
request; a second run without it applies the plan.

This changes .github/ and tools/review/, so Shingler's review of it is a COMMENT (APPROVE_GUARD), and it waits
for a person's approval, as CONTRIBUTING.md says for changes to CI and the bot's tools.

6. Dry run on this repository

labels.py run --repo midagedev/bloomery --state all --dry-run (GET requests only) over all 55 issues and 44 pull
requests, at upstream 517e0a74.

Open items (what the default backfill touches):

Everything, closed included:

Rows to check by eye:

7. Limits

  • A pull request merged without a merge commit (rebased or fast-forwarded) has a merge_commit_sha that is its last
    commit, so its merge diff covers that commit only. Most merges here are merge commits; box.sh: local mode (BLOOMERY_BOX_LOCAL=1): the box command runs in this tree on a contributor's machine with a card of its own #38 is one that is not, and
    its last commit is the one it added.
  • A new crate that reaches cuda-core but is not named gpu-* gets no area until CRATE_AREA names it: the device
    list comes from the branch the workflow runs from, which does not have the crate yet.
  • An item whose file list is cut short (section 3) gets only the labels it found until a later run sees the whole
    list.
  • GitHub keeps one pending run per concurrency group. When a push and then a merge both queue behind a running Labels
    run for the same pull request, the merge run replaces the push run, and the card label is not decided for that
    push. Shingler's review of the push still sets it.

8. For the maintainers to decide

  • Concept prefixes. The MTP, paged, prefill and quant issues so far read as card-engine work, so mapping
    them to gpu would label 6 more open issues. I left them out because each of those words also names host-side code
    (crates/runtime for MTP's width, crates/model for the CPU engine's prefill). A line in ALIASES adds one.
  • measure by path. A pull request that adds docs/cards/*.card could get measure. That would catch sampler: measure relative-weight cutoffs and record the negative CPU result #65 and
    router: measure per-layer expert locality on recent prose and code #66, but a code change also adds a card for its A/B (the mtp: width commit added draftgate-noninf.card), so
    cards give no label for now.
  • tracking. It is defined and created but never set automatically; the open status issues (Status board (auto-updated) · 작업 현황 #40, dgx-spark: what is in progress on the Spark side, as a checklist #53) could get
    it by hand.
  • Docs that describe the card label as Shingler's alone. CONTRIBUTING.md ("How a pull request is verified and
    merged": "is labelled gpu-evidence ... else needs-gpu until someone runs it") and .github/review/README.md
    (the paragraph starting "Shingler only reads") now have the workflow putting needs-gpu on first. The AGENTS.md
    layout line for tools/review/ says "Shingler's tools", and labels.py lives there too because it shares
    post.py. I left these texts as they are.

Code shape

  • No second copy. The card labels, the device crate list and the API call come from tools/review/post.py.
  • Model code holds only architecture facts. No model code changed.
  • Dead code is gone. Nothing was made unused.
  • No silent failure. An API error stops the run, except a 422 that says the label exists and a 404 on a
    removal (someone took the label off since the read). A short file list, a capped merge diff and a failed
    device list are named in the output.
  • Comments state what is true now.
  • No relaxed gate.
  • cargo fmt and cargo clippy: no Rust changed.

Duplication found or removed (path:line), or "none": none.

What I ran

$ python3 -I tools/review/labels.py --self-test
labels.py self-test: ok
$ python3 -I tools/review/post.py --self-test
post.py self-test: ok
$ python3 tools/recipes.py check
check-recipes: tools/gate-paths.tsv: 118 gate rows held to the derivation, 22 rows with exceptions naming 196 gates
$ actionlint .github/workflows/labels.yml

Python 3.15.0, actionlint 1.7.12. Each of these changes to labels.py makes the self-test fail: reading the labels or
the title from the first read instead of the re-read, deciding the card label when the device list failed, putting
needs-gpu back only when no person took it off, keeping a bot needs-gpu after the device change went away or once
gpu-evidence is on, using pulls/<n>/files for a merged pull request, removing labels on a short file list,
ignoring every 422, leaving a device crate missing from CRATE_AREA without gpu, and counting a .card file as
documentation.

post.py device with RUSTUP_TOOLCHAIN=stable, an empty CARGO_HOME and no just on PATH lists
crates/app/ crates/gpu/ crates/gpu-deepseek41/ crates/gpu-gates/ crates/gpu-glm5next/ crates/gpu-mimo2/ crates/gpu-vision/
in about 5 s on a Mac, the same list as with the pinned nightly.

just mac-static upstream/main on an Apple silicon Mac: 13 of the 14 steps rc 0 (lint 41 warnings, the ratchet).
check-recipes went red in each of three runs on a loaded machine (load average 30 to 46), each time on a different
timed self-test: depth-serve.sh (/health over 3 s) and a chain case at rc 124, then the same chain case alone,
then q38-3090-server.sh's width-record. None of them reads a file this changes, and CI's t0 runs check-recipes
on this pull request.

Not run: the workflow itself, which GitHub runs from the default branch only after merge.

AI help

AI 코딩 에이전트가 코드를 작성한 후 설명을 썼고, 테스트를 마쳤습니다. diff 리뷰와 PR 초안 리뷰는 제가 직접 했습니다.

…quest, and needs-gpu on a card change

Labels so far come only from the issue templates and from whoever opens the issue, so contributors' issues and every
pull request carry none. tools/review/labels.py labels a pull request by the paths it changes (an open one's file
list, a merged one's merge commit against its first parent, since a closed one's file list can span a whole stack;
never its tree) and an issue by its title prefix: gpu, host, model, serve, placement, tools and documentation by area,
dgx-spark and measure by kind, bug and hardware from a title in the template's form. A prefix that names a concept
(paged, MTP, prefill) gets no area. A crate CRATE_AREA lacks is gpu when it is named gpu-* or reaches cuda-core, else
the output names it; the self-test only checks that every entry is still a crate.

An open pull request that changes a crate reaching cuda-core (post.py device_dirs) gets needs-gpu while it has neither
gpu-evidence nor gpu-verified, and a needs-gpu the bot put on comes off once no device path is left or gpu-evidence is
on, as Shingler's apply_labels does; gpu-evidence and gpu-verified are never touched. When the file list is short of
changed_files or the device crates are unknown, nothing is removed or decided about the card label. The self-test runs
both scripts in several orders, and with Shingler between this script's reads and its writes, to the same labels.
Other labels: one a person took off is not put back, and only one this bot put on comes off. Each item's labels, title
and events are read again after its files and device crates, right before the writes. Writes happen only inside
GitHub Actions, where the labels are github-actions[bot]'s. Missing labels are created (a 422 passes only when it says
the label exists); existing ones keep their colour and description. gpu-evidence's colour moves off hardware's green.

The workflow runs on pull_request_target (an edit only when it changes the title or base, a close only when it
merges) and issues with a base checkout, the number through env and pull-requests: read, and workflow_dispatch labels
every open (or every) issue and pull request once, with a dry run. Issues another workflow opens with GITHUB_TOKEN start
no workflow and get their labels from that run.
@juliankang4

Copy link
Copy Markdown
Collaborator Author

컨트리뷰트 체크 완료 했으니 머지합니다

@juliankang4
juliankang4 merged commit 3a21bf6 into midagedev:main Oct 13, 2026
4 of 5 checks passed
juliankang4 added a commit that referenced this pull request Oct 13, 2026
…ing it, so it runs on a merged pull request

On pull_request_target, github.sha is the default branch's head. After a merge made on GitHub that is the pull
request's merge_commit_sha, and actions/checkout refuses an explicit ref equal to it on a fork's pull request, so the
first run, on #104's own merge, ended at the checkout. With no ref, actions/checkout takes the same commit from the
event and skips that check, which applies only to a repository or ref the caller sets.
@juliankang4 juliankang4 added the tools Tools, recipes, CI and the toolchain: tools/, justfile, .github/, Cargo label Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tools Tools, recipes, CI and the toolchain: tools/, justfile, .github/, Cargo

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant