gastownhall/beads · error

cannot modify template %s: templates are read-only; use 'bd

Error message

cannot modify template %s: templates are read-only; use 'bd mol pour' to create a work item

What it means

NotTemplate() blocks writes against template issues. Templates (IsTemplate=true) are read-only definitions used by 'bd mol pour'; they must never be modified directly as work items.

Source

Thrown at internal/validation/issue.go:46

// Exists validates that an issue is not nil.
func Exists() IssueValidator {
	return func(id string, issue *types.Issue) error {
		if issue == nil {
			return fmt.Errorf("issue %s not found", id)
		}
		return nil
	}
}

// NotTemplate validates that an issue is not a template.
// Templates are read-only and cannot be modified.
func NotTemplate() IssueValidator {
	return func(id string, issue *types.Issue) error {
		if issue == nil {
			return nil // Let Exists() handle nil check if needed
		}
		if issue.IsTemplate {
			return fmt.Errorf("cannot modify template %s: templates are read-only; use 'bd mol pour' to create a work item", id)
		}
		return nil
	}
}

// NotPinned validates that an issue is not pinned, by either of the two ways
// bd expresses a pin: the Pinned boolean column or the pinned status. Returns
// an error if either trigger fires, unless force is true.
//
// Both triggers are load-bearing because consumers disagree on which one they
// write and read (ga-z3vht). Gas Town pins by status — it enumerates pins with
// List(ListOptions{Status: StatusPinned}) across 21 sites (hook_check.go,
// prime_output.go, molecule_step.go, up.go) — while Gas City reads the boolean
// (compute_awake_set.go). Checking only one strips the other consumer's
// protection outright, so do not "simplify" either clause away.
func NotPinned(force bool) IssueValidator {
	return func(id string, issue *types.Issue) error {
		if issue == nil {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Use 'bd mol pour <template>' to create a real work item from the template, then modify that
  2. Re-check the ID — templates and work items can look similar; 'bd show <id>' reveals IsTemplate
  3. Exclude templates from bulk scripts by filtering on IsTemplate
  4. If a template itself is wrong, update it only through the intended template-authoring flow, not general issue commands

Example fix

// before
bd update bd-tmpl-1 status=in_progress  // blocked
// after
bd mol pour bd-tmpl-1  # creates work item bd-99
bd update bd-99 status=in_progress
Defensive patterns

Strategy: validation

Validate before calling

if issue.IsTemplate {
    return fmt.Errorf("%s is a template; use 'bd mol pour' instead of modifying it", id)
}

Type guard

func isTemplate(issue *types.Issue) bool { return issue != nil && issue.IsTemplate }

Try / catch

if err := updateIssue(id); err != nil {
    if strings.Contains(err.Error(), "templates are read-only") {
        return pourFromTemplate(id) // fall back to pour flow
    }
    return err
}

Prevention

When it happens

Trigger: Any update/close/assign operation on an issue whose IsTemplate field is true — e.g. running 'bd update mol-xyz ...' or 'bd close' on a molecule template row.

Common situations: Mistaking a template ID for a work item ID when scripting; trying to edit a molecule template instead of pouring it into a real work item; bulk-update loops that sweep up templates.

Related errors


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