github/github-mcp-server · error
node_id must be a non-empty string
Error message
node_id must be a non-empty string
What it means
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.
Source
Thrown at pkg/github/projects_batch.go:416
if hasItemID {
formsPresent++
}
if hasIssueRef {
formsPresent++
}
switch {
case formsPresent == 0:
return fmt.Errorf("each item requires exactly one of node_id, item_id, or item_owner + item_repo + issue_number")
case formsPresent > 1:
return fmt.Errorf("each item must set exactly one of node_id, item_id, or item_owner + item_repo + issue_number, not more than one")
}
switch {
case hasNodeID:
s, ok := entry["node_id"].(string)
if !ok || s == "" {
return fmt.Errorf("node_id must be a non-empty string")
}
p.refKind = batchRefNodeID
p.nodeID = s
case hasItemID:
id, err := validatePositiveInt64(entry["item_id"])
if err != nil {
return fmt.Errorf("item_id: %w", err)
}
p.refKind = batchRefItemID
p.itemID = id
default:
issueOwner, ownerErr := stringFromEntry(entry, "item_owner")
issueRepo, repoErr := stringFromEntry(entry, "item_repo")
issueNumber, numErr := intFromEntry(entry, "issue_number")
for _, err := range []error{ownerErr, repoErr, numErr} {
if err != nil {
return fmt.Errorf("item_owner, item_repo, and issue_number must all be provided together: %w", err)
}View on GitHub (pinned to 0ea1f775a7)
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
Example fix
// before
{"items": [{"node_id": 12345678}]}
// after
{"items": [{"item_id": 12345678}]} // numeric database ID
// or {"items": [{"node_id": "PVTI_lADOABC123"}]} // GraphQL node ID Defensive patterns
Strategy: validation
Validate before calling
if s, ok := entry["node_id"]; ok {
str, isStr := s.(string)
if !isStr || str == "" {
return fmt.Errorf("node_id must be a non-empty string (got %T)", s)
}
} Type guard
func isNonEmptyStringNodeID(v any) bool { s, ok := v.(string); return ok && s != "" } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- item_id: %w
- %s must be a non-empty string
- %s must be a positive integer: %w
- updated_field.id: %w
- updated_field.name must be a non-empty string
AI-assisted analysis of github/github-mcp-server@0ea1f775a7 (2026-08-15).
Data as JSON: /api/errors/25a8293220967651.
Report an issue: GitHub.