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

  1. Pass the GraphQL node ID as a string, e.g. "PVTI_lADOABC..." — get it from a get/list project items call
  2. If the identifier you have is the numeric database ID, move it to item_id instead of node_id
  3. Trim/blank-check templated values so empty strings never reach the request
  4. 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

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


AI-assisted analysis of github/github-mcp-server@0ea1f775a7 (2026-08-15). Data as JSON: /api/errors/25a8293220967651. Report an issue: GitHub.