gastownhall/beads · error

cannot modify pinned issue %s (use --force to override)

Error message

cannot modify pinned issue %s (use --force to override)

What it means

NotPinned(force) blocks modification of pinned issues. An issue is pinned if its Pinned flag is true or its Status is StatusPinned; pins signal 'do not touch' (e.g. seed issues, anchors).

Source

Thrown at internal/validation/issue.go:68

}

// 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 {
			return nil // Let Exists() handle nil check if needed
		}
		if !force && (issue.Pinned || issue.Status == types.StatusPinned) {
			return fmt.Errorf("cannot modify pinned issue %s (use --force to override)", id)
		}
		return nil
	}
}

// CanonicalActor normalizes an identity string so two spellings of the same
// Gas Town identity compare equal. The same identity arrives at bd in more
// than one spelling depending on which layer produced the string it was
// handed: a dotted alias like "gastown.mayor" gets its dot replaced wherever
// a dot is unsafe for that context — "__" in a session name, "_" in a Dolt
// table/database name, "-" elsewhere — and bd only ever sees the resulting
// string, never the substitution itself (ga-wzl83). None of ".", "_", "-"
// carries meaning in an identity string: each is always a positional
// separator between a rig and a role/agent name, never part of either name.
// Collapsing a run of them to one canonical separator lets two spellings of
// the same identity compare equal without weakening comparisons between
// genuinely different identities, whose non-separator characters still
// differ (e.g. "gastown.mayor" vs "gastown.dog-3" stay distinct).

View on GitHub (pinned to 71377f2769)

Solutions

  1. Pass --force to the bd command if you genuinely need to modify the pinned issue
  2. Reconsider whether the operation should skip pinned issues (filter them out)
  3. Use 'bd show <id>' to confirm pin state and who/why it was pinned
  4. Ask the maintainer who pinned it before forcing

Example fix

// before
bd close bd-anchor  // blocked: pinned
// after
bd close bd-anchor --force  // intentional override
Defensive patterns

Strategy: validation

Validate before calling

if issue.Pinned || issue.Status == types.StatusPinned {
    return fmt.Errorf("%s is pinned; skipping (use --force deliberately)", issue.ID)
}

Type guard

func isPinned(issue *types.Issue) bool { return issue != nil && (issue.Pinned || issue.Status == types.StatusPinned) }

Try / catch

if err := closeIssue(id); err != nil {
    if strings.Contains(err.Error(), "pinned") && forceAllowed {
        return closeIssueForce(id)
    }
    return err
}

Prevention

When it happens

Trigger: Update/close/assign on an issue with Pinned=true or Status='pinned' while force=false in the validator.

Common situations: Bulk scripts that iterate all open issues and hit pinned anchors; new contributors unaware an issue is intentionally pinned; automation closing stale issues that are pinned.

Related errors


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