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
- Use 'bd mol pour <template>' to create a real work item from the template, then modify that
- Re-check the ID — templates and work items can look similar; 'bd show <id>' reveals IsTemplate
- Exclude templates from bulk scripts by filtering on IsTemplate
- 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
- Filter templates (IsTemplate) out of bulk update loops
- Distinguish template IDs from work-item IDs before scripting
- Prefer 'bd mol pour' whenever the source is a molecule template
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
- childRef is empty after variable substitution
- invalid childRef '%s': must be alphanumeric, dash, underscor
- missing required variables: %s
- recipe %q has no file contents
- no store is open for this workspace
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/9cc19f6756912c8f.
Report an issue: GitHub.