{"record":{"id":"df06866d19622faa","repo":"github/github-mcp-server","slug":"each-item-must-set-exactly-one-of-node-id-item-id","errorCode":null,"errorMessage":"each item must set exactly one of node_id, item_id, or item_owner + item_repo + issue_number, not more than one","messagePattern":"each item must set exactly one of node_id, item_id, or item_owner \\+ item_repo \\+ issue_number, not more than one","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/github/projects_batch.go","lineNumber":409,"sourceCode":"\t_, hasIssueNumber := entry[\"issue_number\"]\n\thasIssueRef := hasOwner || hasRepo || hasIssueNumber\n\n\tformsPresent := 0\n\tif hasNodeID {\n\t\tformsPresent++\n\t}\n\tif hasItemID {\n\t\tformsPresent++\n\t}\n\tif hasIssueRef {\n\t\tformsPresent++\n\t}\n\n\tswitch {\n\tcase formsPresent == 0:\n\t\treturn fmt.Errorf(\"each item requires exactly one of node_id, item_id, or item_owner + item_repo + issue_number\")\n\tcase formsPresent > 1:\n\t\treturn fmt.Errorf(\"each item must set exactly one of node_id, item_id, or item_owner + item_repo + issue_number, not more than one\")\n\t}\n\n\tswitch {\n\tcase hasNodeID:\n\t\ts, ok := entry[\"node_id\"].(string)\n\t\tif !ok || s == \"\" {\n\t\t\treturn fmt.Errorf(\"node_id must be a non-empty string\")\n\t\t}\n\t\tp.refKind = batchRefNodeID\n\t\tp.nodeID = s\n\tcase hasItemID:\n\t\tid, err := validatePositiveInt64(entry[\"item_id\"])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"item_id: %w\", err)\n\t\t}\n\t\tp.refKind = batchRefItemID\n\t\tp.itemID = id\n\tdefault:","sourceCodeStart":391,"sourceCodeEnd":427,"githubUrl":"https://github.com/github/github-mcp-server/blob/0ea1f775a7c73eff1bd2e25904d01136756bbfe2/pkg/github/projects_batch.go#L391-L427","documentation":"Raised by parseItemRef (pkg/github/projects_batch.go:409) when an items[] entry sets more than one of the accepted reference forms (node_id, item_id, or the issue triple). The library refuses to guess which identifier wins, so the item is rejected as 'invalid_item_ref' and excluded from writes.","triggerScenarios":"An item like {\"node_id\":\"PVTI_x\",\"item_id\":123} (two forms), or {\"node_id\":\"PVTI_x\",\"item_owner\":\"octo\",\"item_repo\":\"repo\",\"issue_number\":7} — note any one of item_owner/item_repo/issue_number present makes hasIssueRef true, so node_id plus just item_repo already counts as two forms.","commonSituations":"A 'send every identifier we have' client that stuffs all known IDs into each item; merging payloads from two sources where one used node_id and the other item_id; template defaults that pre-populate item_owner while code adds node_id.","solutions":["Keep exactly one form per item and delete the other reference keys","Prefer node_id — it is used directly without a lookup; item_id and issue refs each cost an extra resolution call","If the data source yields multiple identifiers, pick one deterministically (node_id first) before building the request","Pre-validate: count the forms per item and reject any item whose count is not exactly 1"],"exampleFix":"// before\n{\"items\": [{\"node_id\": \"PVTI_x\", \"item_id\": 12345}]}\n// after\n{\"items\": [{\"node_id\": \"PVTI_x\"}]}","handlingStrategy":"validation","validationCode":"func refFormCount(entry map[string]any) int {\n\tn := 0\n\tif _, ok := entry[\"node_id\"]; ok { n++ }\n\tif _, ok := entry[\"item_id\"]; ok { n++ }\n\tissue := false\n\tfor _, k := range []string{\"item_owner\", \"item_repo\", \"issue_number\"} {\n\t\tif _, ok := entry[k]; ok { issue = true }\n\t}\n\tif issue { n++ }\n\treturn n\n}\n// require refFormCount(item) == 1 for every item before sending","typeGuard":"func hasExactlyOneRefForm(entry map[string]any) bool { return refFormCount(entry) == 1 }","tryCatchPattern":null,"preventionTips":["Pick one canonical reference form per pipeline (prefer node_id) and strip the others at build time","Remember any one of item_owner/item_repo/issue_number counts as the whole issue-ref form","Reject items that carry more than one form before the call — the tool refuses to disambiguate"],"tags":["github-projects","batch","input-validation","conflicting-input"],"backgroundTag":null,"analyzedSha":"0ea1f775a7c73eff1bd2e25904d01136756bbfe2","analyzedAt":"2026-08-15T18:10:19.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}