gastownhall/beads · error

node %q: storage_class ephemeral conflicts with ephemeral: f

Error message

node %q: storage_class ephemeral conflicts with ephemeral: false

What it means

A node explicitly set `storage_class: "ephemeral"` but also `ephemeral: false`. The explicit false contradicts the storage class, so apply aborts. The node cannot simultaneously claim to be ephemeral (via class) and not ephemeral (via flag).

Source

Thrown at cmd/bd/graph_apply.go:911

		noHistory = *node.NoHistory
	}
	if ephemeral && noHistory {
		return false, false, "", fmt.Errorf("node %q: ephemeral and no_history are mutually exclusive", node.Key)
	}
	issueType := types.IssueType(node.Type)
	if issueType == "" {
		issueType = types.TypeTask
	}
	class, err = resolveStorageClass(node.StorageClass, issueType.Normalize())
	if err != nil {
		return false, false, "", fmt.Errorf("node %q: %w", node.Key, err)
	}
	if class == types.StorageClassEphemeral {
		if noHistory {
			return false, false, "", fmt.Errorf("node %q: storage_class ephemeral and no_history are mutually exclusive", node.Key)
		}
		if node.Ephemeral != nil && !*node.Ephemeral {
			return false, false, "", fmt.Errorf("node %q: storage_class ephemeral conflicts with ephemeral: false", node.Key)
		}
		ephemeral = true
		class = ""
	}
	// Reconcile the requested durable class with the effective wisp plane
	// (flag > config, Protocol v0.1 §C1.3): an explicit node storage_class
	// contradicts an ephemeral/no_history node and is rejected, so the durable
	// intent is preserved rather than silently collapsed into an
	// effective-ephemeral record; a per-type config default yields to the
	// effective plane. versioned normalizes to the unset marker only after the
	// check (C2.4).
	var conflict bool
	class, conflict = reconcileStorageClassPlane(class, node.StorageClass != "", ephemeral || noHistory)
	if conflict {
		return false, false, "", fmt.Errorf("node %q: storage_class %s conflicts with ephemeral/no_history: wisp-plane records are storage class ephemeral", node.Key, class)
	}
	return ephemeral, noHistory, class, nil
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Remove `"ephemeral": false` from the node, keeping storage_class ephemeral.
  2. Or change storage_class to a durable value if ephemeral:false is the intended state.
  3. Fix the plan generator so it emits only one of the two settings.

Example fix

// before
{"key": "bd-123", "storage_class": "ephemeral", "ephemeral": false}
// after
{"key": "bd-123", "storage_class": "ephemeral"}
Defensive patterns

Strategy: validation

Validate before calling

for _, n := range plan.Nodes {
  if n.StorageClass == "ephemeral" && n.Ephemeral != nil && !*n.Ephemeral {
    return fmt.Errorf("node %q: ephemeral:false contradicts storage_class ephemeral", n.Key)
  }
}

Type guard

func contradictsEphemeral(n Node) bool {
  return n.StorageClass == "ephemeral" && n.Ephemeral != nil && !*n.Ephemeral
}

Try / catch

if err := bd.GraphApply(ctx, plan); err != nil {
  if strings.Contains(err.Error(), "conflicts with ephemeral: false") {
    // fix plan generator output and retry
  }
}

Prevention

When it happens

Trigger: Applying a plan node with both `storage_class: "ephemeral"` and `"ephemeral": false`.

Common situations: Generated plans where one code path sets ephemeral:false by default and another sets storage_class; manual edits flipping the boolean without removing the storage class.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/765575b8d18742dd. Report an issue: GitHub.