{"record":{"id":"25a8293220967651","repo":"github/github-mcp-server","slug":"node-id-must-be-a-non-empty-string","errorCode":null,"errorMessage":"node_id must be a non-empty string","messagePattern":"node_id must be a non-empty string","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/github/projects_batch.go","lineNumber":416,"sourceCode":"\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:\n\t\tissueOwner, ownerErr := stringFromEntry(entry, \"item_owner\")\n\t\tissueRepo, repoErr := stringFromEntry(entry, \"item_repo\")\n\t\tissueNumber, numErr := intFromEntry(entry, \"issue_number\")\n\t\tfor _, err := range []error{ownerErr, repoErr, numErr} {\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"item_owner, item_repo, and issue_number must all be provided together: %w\", err)\n\t\t\t}","sourceCodeStart":398,"sourceCodeEnd":434,"githubUrl":"https://github.com/github/github-mcp-server/blob/0ea1f775a7c73eff1bd2e25904d01136756bbfe2/pkg/github/projects_batch.go#L398-L434","documentation":"Raised by parseItemRef (pkg/github/projects_batch.go:416) when node_id is present but is not a non-empty Go string. Presence is checked by key existence, so node_id: null or \"\" counts as 'present' and then fails the type/emptiness check. The item is marked 'invalid_item_ref' and skipped.","triggerScenarios":"Passing node_id as a JSON number ({\"node_id\": 123456789}), null, or an empty string from a blank template variable. Passing the REST numeric database ID where the GraphQL global node ID (e.g. \"PVTI_...\") is expected.","commonSituations":"Confusing the REST project item database ID with the GraphQL node ID — the database ID belongs in item_id; templating that renders an empty string when the source column is blank; JSON null from an upstream serializer that emits keys with null values.","solutions":["Pass the GraphQL node ID as a string, e.g. \"PVTI_lADOABC...\" — get it from a get/list project items call","If the identifier you have is the numeric database ID, move it to item_id instead of node_id","Trim/blank-check templated values so empty strings never reach the request","Pre-validate: assert node_id is a non-empty string when the key exists"],"exampleFix":"// before\n{\"items\": [{\"node_id\": 12345678}]}\n// after\n{\"items\": [{\"item_id\": 12345678}]}  // numeric database ID\n// or {\"items\": [{\"node_id\": \"PVTI_lADOABC123\"}]}  // GraphQL node ID","handlingStrategy":"validation","validationCode":"if s, ok := entry[\"node_id\"]; ok {\n\tstr, isStr := s.(string)\n\tif !isStr || str == \"\" {\n\t\treturn fmt.Errorf(\"node_id must be a non-empty string (got %T)\", s)\n\t}\n}","typeGuard":"func isNonEmptyStringNodeID(v any) bool { s, ok := v.(string); return ok && s != \"\" }","tryCatchPattern":null,"preventionTips":["Keep GraphQL node IDs (PVTI_... strings) and REST database IDs in separate typed fields in your code so they cannot be swapped","Null-check and trim templated node IDs before use","If the value is numeric, it belongs in item_id, not node_id"],"tags":["github-projects","batch","input-validation","type-mismatch","node-id"],"backgroundTag":null,"analyzedSha":"0ea1f775a7c73eff1bd2e25904d01136756bbfe2","analyzedAt":"2026-08-15T18:10:19.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}