gastownhall/beads · error

invalid --set-metadata: expected key=value, got %q

Error message

invalid --set-metadata: expected key=value, got %q

What it means

ApplyMetadataEdits parses each entry of setFlags as key=value using strings.Cut and rejects any entry with no '=' separator or an empty key. The library throws this so malformed --set-metadata flags are caught at edit time rather than silently creating wrong keys. The offending flag is echoed with %q for easy identification.

Source

Thrown at internal/storage/metadata.go:293

// ApplyMetadataEdits applies incremental set (key=value) and unset (key) edits
// to existing metadata and returns the merged JSON. Set values are typed via
// MetadataEditValue; keys are validated with ValidateMetadataKey.
func ApplyMetadataEdits(existing json.RawMessage, setFlags, unsetFlags []string) (json.RawMessage, error) {
	data := make(map[string]json.RawMessage)
	if len(existing) > 0 {
		trimmed := strings.TrimSpace(string(existing))
		if trimmed != "" && trimmed != "null" {
			if err := json.Unmarshal(existing, &data); err != nil {
				return nil, fmt.Errorf("existing metadata is not a JSON object: %w", err)
			}
		}
	}

	for _, kv := range setFlags {
		k, v, ok := strings.Cut(kv, "=")
		if !ok || k == "" {
			return nil, fmt.Errorf("invalid --set-metadata: expected key=value, got %q", kv)
		}
		if err := ValidateMetadataKey(k); err != nil {
			return nil, err
		}
		data[k] = MetadataEditValue(v)
	}

	for _, k := range unsetFlags {
		if err := ValidateMetadataKey(k); err != nil {
			return nil, err
		}
		delete(data, k)
	}

	result, err := json.Marshal(data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal metadata: %w", err)
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Rewrite the flag as key=value: --set-metadata owner=alice.
  2. If the value itself must contain '=', everything after the first '=' is the value (strings.Cut splits only once), so do not escape it.
  3. If the value contains spaces, quote the whole argument: --set-metadata "note=some text here".
  4. Check the flag list for empty strings left by over-quoting (e.g. --set-metadata "" produces this error).

Example fix

// before
bd update bd-1 --set-metadata owner   // no '='
// after
bd update bd-1 --set-metadata owner=alice
Defensive patterns

Strategy: validation

Validate before calling

func validSetFlag(f string) bool {
    k, _, ok := strings.Cut(f, "=")
    return ok && k != ""
}
// check every --set-metadata flag in the caller before invoking ApplyMetadataEdits
for _, f := range setFlags {
    if !validSetFlag(f) { return fmt.Errorf("bad flag %q", f) }
}

Try / catch

out, err := ApplyMetadataEdits(existing, setFlags, unsetFlags)
if err != nil && strings.Contains(err.Error(), "invalid --set-metadata") {
    return fmt.Errorf("usage: --set-metadata key=value (got: %v)", err)
}

Prevention

When it happens

Trigger: Passing a set flag like "mykey" (no =), "=value" (empty key), or "" (empty string) in the setFlags slice — e.g. `bd update bd-1 --set-metadata owner` instead of `--set-metadata owner=alice`.

Common situations: Shell splitting an argument containing spaces so only part of key=value arrives; forgetting the '=' when the value looks optional; copy-pasting flag docs that show key and value as separate words.

Related errors


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