Skip to content

Commit 793d103

Browse files
authored
Merge pull request #2215 from modelcontextprotocol/v2/chore/milestone-merge-v2.5.0
chore: merge v2/main for the v2.5.0 milestone release
2 parents edf54f5 + d92a30f commit 793d103

333 files changed

Lines changed: 48357 additions & 17800 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.claude/skills/board-ops/SKILL.md‎

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
---
2+
name: board-ops
3+
description: "gh recipes for the Inspector project boards — add a card, move its Status, set or re-score its Priority, delete a card, and recover from a deleted single-select option. Covers board #28 (v2) and board #11 (v1), their node/field/option IDs, and the option-deletion hazard."
4+
disable-model-invocation: false
5+
---
6+
7+
# Board operations
8+
9+
The rules about *what* a card's Status and Priority should be live in
10+
[`AGENTS.md`](../../../AGENTS.md) under **Issue-driven Work Style**. This skill is
11+
the *mechanics*: the exact `gh` calls, the IDs, and the ways the board can be
12+
damaged.
13+
14+
Related: `/issue-create` (the five-step create flow), `/issue-triage` (sweeping
15+
unboarded issues in, the priority rubric, the board audit).
16+
17+
## Which board
18+
19+
| Version label | Board | Owner | Has a Priority field? |
20+
| --- | --- | --- | --- |
21+
| `v2` | [#28](https://github.com/orgs/modelcontextprotocol/projects/28) | `modelcontextprotocol` | Yes |
22+
| `v1` | [#11](https://github.com/orgs/modelcontextprotocol/projects/11) | `modelcontextprotocol` | **No** |
23+
24+
Both are **org** projects, so every command takes `--owner modelcontextprotocol`.
25+
The two projects have their own field and option IDs and none of them are
26+
interchangeable — a #28 id passed to #11 is rejected with "option Id does not
27+
belong to the field", so the mistake is at least loud.
28+
29+
**Only issues go on a board — never PRs, never draft cards.** A PR is tracked
30+
through the card of the issue it closes.
31+
32+
## V2 board (#28) IDs
33+
34+
The project node id and the field ids are stable. The **option** ids are **not** —
35+
they are regenerated whenever a single-select field's option list is edited (see
36+
the hazard below). If any option id here is rejected, re-fetch:
37+
38+
```sh
39+
# Swap "Status" for "Priority" to fetch the other field's options.
40+
gh project field-list 28 --owner modelcontextprotocol --format json \
41+
| jq '.fields[] | select(.name=="Status") | .options'
42+
```
43+
44+
| Thing | ID |
45+
| --- | --- |
46+
| Project node ID | `PVT_kwDOCt2Azc4BJVxt` |
47+
| Status field ID | `PVTSSF_lADOCt2Azc4BJVxtzg5iI8c` |
48+
| Priority field ID | `PVTSSF_lADOCt2Azc4BJVxtzg5iJE4` |
49+
50+
Status option IDs (`--single-select-option-id`) — **last verified 2026-08-01**.
51+
52+
| Status | Option ID | Means |
53+
| --- | --- | --- |
54+
| Incoming | `721a3d4c` | Arrived unboarded, awaiting review — **no milestone** |
55+
| Todo | `fbdaf21e` | Approved (a milestone was assigned) |
56+
| In Progress | `195df262` | Active work, whatever the surface |
57+
| In Review | `159c8a02` | A PR is open |
58+
| Done | `259d6aab` | **Shipped** — a merged PR, or a parent whose last sub-issue closed |
59+
60+
Priority option IDs — **last verified 2026-08-01**. Derive the level with the
61+
rubric in `/issue-triage`; don't eyeball it.
62+
63+
| Priority | Option ID | Rubric total |
64+
| --- | --- | --- |
65+
| Urgent | `79628723` | 12+ |
66+
| High | `0a877460` | 9–11 |
67+
| Medium | `da944a9c` | 6–8 |
68+
| Low | `d67ac7ce` | ≤5 |
69+
70+
## V1 board (#11) IDs
71+
72+
The v1 line takes security fixes only, so this board sees little traffic — but a
73+
v1 issue still gets a card, and the same Incoming/Todo split applies.
74+
75+
| Thing | ID |
76+
| --- | --- |
77+
| Project node ID | `PVT_kwDOCt2Azc4BA5sz` |
78+
| Status field ID | `PVTSSF_lADOCt2Azc4BA5szzgzkS-g` |
79+
80+
Status option IDs — **last verified 2026-08-01**.
81+
82+
| Status | Option ID |
83+
| --- | --- |
84+
| Incoming | `831820cf` |
85+
| Todo | `f75ad846` |
86+
| In Progress | `47fc9ee4` |
87+
| In Review | `0439b2bf` |
88+
| Done | `98236657` |
89+
90+
There is **no Priority field on this board** — the priority rubric is v2-only.
91+
Don't try to set one here; the field id doesn't exist.
92+
93+
## Recipes
94+
95+
### Add a card and set its fields
96+
97+
```sh
98+
# Prints the item id (PVTI_…); capture it.
99+
ITEM_ID=$(gh project item-add 28 --owner modelcontextprotocol --url <issue-url> --format json --jq '.id')
100+
101+
# Status → Todo (an issue you filed through the create flow is approved by definition)
102+
gh project item-edit --project-id PVT_kwDOCt2Azc4BJVxt --id "$ITEM_ID" \
103+
--field-id PVTSSF_lADOCt2Azc4BJVxtzg5iI8c --single-select-option-id fbdaf21e
104+
105+
# Priority → Medium
106+
gh project item-edit --project-id PVT_kwDOCt2Azc4BJVxt --id "$ITEM_ID" \
107+
--field-id PVTSSF_lADOCt2Azc4BJVxtzg5iJE4 --single-select-option-id da944a9c
108+
```
109+
110+
Each `item-edit` sets **one** field, so setting both takes two calls — there is
111+
no combined form.
112+
113+
For an issue swept in at triage, the only difference is Status → **Incoming**
114+
(`721a3d4c`) and that you do **not** set a milestone.
115+
116+
For **v1**, the same shape against board #11:
117+
118+
```sh
119+
ITEM_ID=$(gh project item-add 11 --owner modelcontextprotocol --url <issue-url> --format json --jq '.id')
120+
gh project item-edit --project-id PVT_kwDOCt2Azc4BA5sz --id "$ITEM_ID" \
121+
--field-id PVTSSF_lADOCt2Azc4BA5szzgzkS-g --single-select-option-id f75ad846
122+
```
123+
124+
### Move an existing card
125+
126+
Look the item id up by issue number rather than re-adding it. Keep `--limit`
127+
above the board's item count (~265 as of 2026-08-01) — past it `item-list`
128+
truncates **silently**, `select` matches nothing, and `item-edit --id ""` fails
129+
with an opaque node-resolution error rather than saying the limit was too low.
130+
131+
⚠️ **Filter by repository, not by number alone.** These are **org** projects and
132+
issue numbers are **repo-local**, so an unfiltered `select` can match another
133+
repo's issue that happens to share the number — board #11 really does carry a
134+
`modelcontextprotocol/servers` card — and then moves or deletes the wrong card,
135+
or passes two ids at once (Copilot).
136+
137+
```sh
138+
ITEM_ID=$(gh project item-list 28 --owner modelcontextprotocol --format json --limit 500 \
139+
--jq '.items[] | select(.content.repository=="modelcontextprotocol/inspector"
140+
and .content.number==<ISSUE_NUMBER>) | .id')
141+
# e.g. Status → In Review, when its PR opens
142+
gh project item-edit --project-id PVT_kwDOCt2Azc4BJVxt --id "$ITEM_ID" \
143+
--field-id PVTSSF_lADOCt2Azc4BJVxtzg5iI8c --single-select-option-id 159c8a02
144+
```
145+
146+
### Delete a card
147+
148+
**`Done` means the work shipped.** An issue closed as duplicate / won't fix /
149+
not planned / obsolete / superseded shipped nothing, so its card is **deleted**,
150+
not parked in Done:
151+
152+
```sh
153+
ITEM_ID=$(gh project item-list 28 --owner modelcontextprotocol --format json --limit 500 \
154+
--jq '.items[] | select(.content.repository=="modelcontextprotocol/inspector"
155+
and .content.number==<ISSUE_NUMBER>) | .id')
156+
gh project item-delete 28 --owner modelcontextprotocol --id "$ITEM_ID"
157+
```
158+
159+
Deleting the card removes it from the board only — **the issue itself is
160+
untouched**, keeps its labels and comments, and stays searchable and linkable
161+
forever. Nothing is lost; the board simply stops claiming the work was
162+
delivered. Done is read as the record of what a milestone actually delivered, so
163+
a duplicate sitting there makes that record wrong in a way nobody can detect
164+
later.
165+
166+
The close **reason** is the machine-readable form of the same distinction.
167+
`gh issue close --reason` accepts only `completed` and `not planned`, so
168+
**`duplicate` must be set through the API**:
169+
170+
```sh
171+
gh api repos/modelcontextprotocol/inspector/issues/<N> -X PATCH \
172+
-f state=closed -f state_reason=duplicate
173+
```
174+
175+
(or "Mark as duplicate" in the web UI, which additionally records a
176+
duplicate-of link).
177+
178+
## ⚠️ The option-deletion hazard
179+
180+
**Never add, rename, or remove an option on a single-select board field (Status
181+
or Priority) with the `updateProjectV2Field` GraphQL mutation unless you pass
182+
every existing option's `id`.** That mutation does a **full replace** of the
183+
option list: resending options by name/color/description without their `id`s
184+
makes GitHub **delete all existing options and mint new ones**, which **orphans
185+
that field's value on every card on the board** *and* invalidates every option
186+
id in the tables above. This has happened once, on Status (~197 items
187+
reconstructed by inference).
188+
189+
Safe alternatives, in order of preference:
190+
191+
1. **Add or rename an option in the GitHub web UI** (Project → the field's
192+
settings). This preserves the ids of untouched options.
193+
⚠️ **Deleting is different, in the UI as much as in the API**: removing an
194+
option blanks that field's value on every card that held it, with no undo and
195+
no warning that says so.
196+
2. If you must script it, first `gh api graphql` the current options **with their
197+
`id`s**, then call `updateProjectV2Field` echoing back every existing option
198+
**including its `id`**, appending only the new one.
199+
`ProjectV2SingleSelectFieldOptionInput.id` is an optional `String`, so a mixed
200+
list works. Verify afterward that no card lost its value — snapshot
201+
`gh project item-list … --format json` before and after and diff; don't just
202+
spot-check. Send those dumps to `$BOARD_TMP` too, for the reason above.
203+
204+
Both the `Incoming` Status option and the Urgent/High/Medium/Low Priority
205+
options were added this way (#1891), with the before/after diff confirming all
206+
264 cards kept their Status.
207+
208+
`gh project item-add` and `gh project item-edit` are always safe — they set a
209+
card's value and never touch the field schema.
210+
211+
### Always snapshot before touching a field's options
212+
213+
One command, and it is the difference between a five-minute restore and
214+
reconstructing statuses by inference:
215+
216+
⚠️ **Write it outside the repo.** [The boards are private](../issue-triage/SKILL.md),
217+
so a snapshot is a full dump of item IDs and every card's Status and Priority.
218+
Left in the working tree it is one `git add -A` away from being published in a
219+
PR (Copilot).
220+
221+
```sh
222+
BOARD_TMP=$(mktemp -d)
223+
gh project item-list 28 --owner modelcontextprotocol --format json --limit 600 \
224+
> "$BOARD_TMP/board-snapshot.json"
225+
echo "snapshot: $BOARD_TMP/board-snapshot.json" # note the path; you need it to recover
226+
```
227+
228+
### Recovering from a deleted option
229+
230+
This has happened twice — once via the API (~197 items, reconstructed by
231+
inference) and once via the UI (the `Done` column, 247 items, restored from a
232+
snapshot in minutes). With a snapshot the recovery is mechanical.
233+
234+
The recipe below is written for a deleted **Status** option. For a deleted
235+
**Priority** option it is the same three steps with two substitutions: read
236+
`.priority` instead of `.status` (`gh project item-list --format json` exposes
237+
each single-select field under its lowercased name, so both keys are present),
238+
and pass the Priority field id `PVTSSF_lADOCt2Azc4BJVxtzg5iJE4`.
239+
240+
```sh
241+
# 0. Same temp dir the snapshot went to — keep every dump out of the worktree.
242+
BOARD_TMP=${BOARD_TMP:-$(mktemp -d)}
243+
244+
# 1. Which cards lost their value, and what did they hold?
245+
gh project item-list 28 --owner modelcontextprotocol --format json --limit 600 \
246+
> "$BOARD_TMP/board-broken.json"
247+
jq -r '[.items[]|select(.status==null)|.id]' "$BOARD_TMP/board-broken.json" \
248+
> "$BOARD_TMP/lost-ids.json"
249+
jq -r --slurpfile L "$BOARD_TMP/lost-ids.json" '($L[0]) as $lost
250+
| [.items[] | select(.id as $i | $lost|index($i)) | .status // "(none)"]
251+
| group_by(.) | map({s:.[0],c:length}) | .[] | "was \(.s): \(.c)"' \
252+
"$BOARD_TMP/board-snapshot.json"
253+
254+
# 2. Recreate the option, echoing every surviving option's id (see above).
255+
# NOTE: the recreated option gets a NEW id — the deleted one never comes back.
256+
257+
# 3. Re-apply it to the orphaned cards.
258+
for id in $(jq -r '.[]' "$BOARD_TMP/lost-ids.json"); do
259+
gh project item-edit --project-id PVT_kwDOCt2Azc4BJVxt --id "$id" \
260+
--field-id PVTSSF_lADOCt2Azc4BJVxtzg5iI8c --single-select-option-id <NEW_OPTION_ID>
261+
sleep 0.4
262+
done
263+
```
264+
265+
Step 1's grouping is the safety check: confirm the orphaned set is exactly the
266+
cards that held the deleted option, so you don't overwrite a card someone
267+
legitimately moved in the meantime.
268+
269+
Because the recreated option carries a **new id**, the tables above and every
270+
reference to it must be updated in the same change — `grep` the old id across
271+
the repo. The `Done` id has been `248a3910` and is now `259d6aab` for exactly
272+
this reason.
273+
274+
## ⚠️ Two different "Priority" fields
275+
276+
An issue page shows two fields named Priority, and they are unrelated. **Ours is
277+
the one under _Projects → Inspector V2_.**
278+
279+
| Where it appears | What it is | Ours? |
280+
| --- | --- | --- |
281+
| **Projects → Inspector V2 → Priority** | The **project board** field on #28 (`PVTSSF_lADOCt2Azc4BJVxtzg5iJE4`) | ✅ Yes |
282+
| **Fields → Priority** (above _Projects_) | A GitHub **issue field**, `IFSS_kgDOAdAWeg`, defined at the **org** level and shared by every repo in it | ❌ No |
283+
284+
Nothing syncs them, in either direction, and they will happily disagree.
285+
**Never delete the org-level field** — it belongs to the whole org. Don't set it
286+
either; a value there is a *reporter's* opinion and feeds the rubric as a capped
287+
+1 signal bonus and nothing more (see `/issue-triage`).
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[
2+
{
3+
"prompt": "Move the board card for issue 2189 to In Review.",
4+
"expect": "board-ops"
5+
},
6+
{
7+
"prompt": "How do I set the Priority field on a card in the v2 project board?",
8+
"expect": "board-ops"
9+
},
10+
{
11+
"prompt": "The project board rejected my Status option ID. How do I get the current ones?",
12+
"expect": "board-ops"
13+
},
14+
{
15+
"prompt": "I'm about to edit the options on one of the board's single-select fields. Should I do anything first?",
16+
"expect": "board-ops"
17+
},
18+
{
19+
"prompt": "Several cards lost their Priority value after the field was edited. How do I put it back?",
20+
"expect": "board-ops"
21+
},
22+
{
23+
"prompt": "Explain the difference between TCP and UDP.",
24+
"expect": null
25+
},
26+
{
27+
"prompt": "What year was the first Moon landing?",
28+
"expect": null
29+
}
30+
]

0 commit comments

Comments
 (0)