gastownhall/beads · error

update: notes and append notes cannot both be set

Error message

update: notes and append notes cannot both be set

What it means

updateSpec rejects an UpdateRequest patch that sets both Notes (replace) and AppendNotes in the same update. The two operations are mutually exclusive — one overwrites the notes field, the other appends — so combining them is ambiguous. It is thrown as a validationError before any fields are persisted.

Source

Thrown at internal/storage/uow/issue_operations.go:315

	setField(fields, "description", patch.Description)
	setField(fields, "design", patch.Design)
	setField(fields, "acceptance_criteria", patch.AcceptanceCriteria)
	setField(fields, "spec_id", patch.SpecID)
	setField(fields, "await_id", patch.AwaitID)
	setField(fields, "status", patch.Status)
	setField(fields, "priority", patch.Priority)
	if patch.IssueType.Set {
		fields["issue_type"] = string(patch.IssueType.Value)
	}
	setField(fields, "assignee", patch.Assignee)
	setField(fields, "owner", patch.Owner)
	setField(fields, "closed_by_session", patch.ClosedBySession)
	setField(fields, "estimated_minutes", patch.EstimatedMinutes)
	setField(fields, "external_ref", patch.ExternalRef)
	setField(fields, "due_at", patch.DueAt)
	setField(fields, "defer_until", patch.DeferUntil)
	if patch.Notes.Set && patch.AppendNotes.Set {
		return domain.UpdateSpec{}, validationError(fmt.Errorf("update: notes and append notes cannot both be set"))
	}
	setField(fields, "notes", patch.Notes)
	if patch.AppendNotes.Set {
		fields[storageissueops.OpAppendNotes] = patch.AppendNotes.Value
	}
	if patch.Metadata.Replace.Set {
		replacement := json.RawMessage("{}")
		if len(patch.Metadata.Replace.Value) > 0 {
			replacement = patch.Metadata.Replace.Value
		}
		if err := storageissueops.ValidateMetadataIfConfigured(replacement); err != nil {
			return domain.UpdateSpec{}, validationError(err)
		}
		fields["metadata"] = replacement
	} else {
		if patch.Metadata.Merge.Set {
			fields[storageissueops.OpMergeMetadata] = patch.Metadata.Merge.Value
		}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Send the append as its own follow-up update: first set notes, then append in a second call.
  2. Clear one of the two: either unset AppendNotes.Set or unset Notes.Set before issuing the update.
  3. If the intent was to append, populate only patch.AppendNotes; if replacing, only patch.Notes.

Example fix

// before
patch.Notes.Set = true; patch.Notes.Value = "new"
patch.AppendNotes.Set = true; patch.AppendNotes.Value = "more" // rejected
// after
patch.Notes.Set = true; patch.Notes.Value = "new"
// second update:
patch2.AppendNotes.Set = true; patch2.AppendNotes.Value = "more"
Defensive patterns

Strategy: validation

Validate before calling

if patch.Notes.Set && patch.AppendNotes.Set {
    return errors.New("choose either notes replace or append, not both")
}

Type guard

func notesPatchValid(p domain.NotesPatch) bool { return !(p.Notes.Set && p.AppendNotes.Set) }

Try / catch

if err != nil && strings.Contains(err.Error(), "notes and append notes cannot both be set") {
    // split into two update calls and retry
    return splitAndRetry()
}

Prevention

When it happens

Trigger: Calling Update with a patch where patch.Notes.Set is true and patch.AppendNotes.Set is true simultaneously (e.g. bd update with both --notes and --append-notes style inputs).

Common situations: CLI flag handling that always sets AppendNotes.Set when an append flag is present while also filling Notes; scripted updates built from merged option structs; a UI sending both fields.

Related errors


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