github/github-mcp-server · error

each item must set exactly one of node_id, item_id, or item_

Error message

each item must set exactly one of node_id, item_id, or item_owner + item_repo + issue_number, not more than one

What it means

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.

Source

Thrown at pkg/github/projects_batch.go:409

	_, hasIssueNumber := entry["issue_number"]
	hasIssueRef := hasOwner || hasRepo || hasIssueNumber

	formsPresent := 0
	if hasNodeID {
		formsPresent++
	}
	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:

View on GitHub (pinned to 0ea1f775a7)

Solutions

  1. Keep exactly one form per item and delete the other reference keys
  2. Prefer node_id — it is used directly without a lookup; item_id and issue refs each cost an extra resolution call
  3. If the data source yields multiple identifiers, pick one deterministically (node_id first) before building the request
  4. Pre-validate: count the forms per item and reject any item whose count is not exactly 1

Example fix

// before
{"items": [{"node_id": "PVTI_x", "item_id": 12345}]}
// after
{"items": [{"node_id": "PVTI_x"}]}
Defensive patterns

Strategy: validation

Validate before calling

func refFormCount(entry map[string]any) int {
	n := 0
	if _, ok := entry["node_id"]; ok { n++ }
	if _, ok := entry["item_id"]; ok { n++ }
	issue := false
	for _, k := range []string{"item_owner", "item_repo", "issue_number"} {
		if _, ok := entry[k]; ok { issue = true }
	}
	if issue { n++ }
	return n
}
// require refFormCount(item) == 1 for every item before sending

Type guard

func hasExactlyOneRefForm(entry map[string]any) bool { return refFormCount(entry) == 1 }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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