{"record":{"id":"f397d51be8a67642","repo":"gastownhall/beads","slug":"invalid-set-metadata-expected-key-value-got-q","errorCode":null,"errorMessage":"invalid --set-metadata: expected key=value, got %q","messagePattern":"invalid --set-metadata: expected key=value, got %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/update.go","lineNumber":799,"sourceCode":"\tif value == nil {\n\t\treturn setField[*time.Time](nil), true\n\t}\n\tat, ok := value.(time.Time)\n\tif !ok {\n\t\treturn issueops.Field[*time.Time]{}, false\n\t}\n\treturn setField(&at), true\n}\n\n// parseSetMetadataFlags splits --set-metadata key=value pairs, matching\n// storage.ApplyMetadataEdits' parsing so the CLI contract is unchanged. Values\n// are always stored as JSON strings (GH#4146).\nfunc parseSetMetadataFlags(flags []string) (map[string]json.RawMessage, error) {\n\tset := make(map[string]json.RawMessage, len(flags))\n\tfor _, flag := range flags {\n\t\tkey, value, ok := strings.Cut(flag, \"=\")\n\t\tif !ok || key == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"invalid --set-metadata: expected key=value, got %q\", flag)\n\t\t}\n\t\tset[key] = storage.MetadataEditValue(value)\n\t}\n\treturn set, nil\n}\n\nfunc replacesExistingNotes(existing string, fields map[string]any) bool {\n\tnewNotes, replacing := fields[\"notes\"].(string)\n\treturn replacing && existing != \"\" && newNotes != existing\n}\n\nfunc warnNotesReplacement(id string) {\n\tfmt.Fprintf(os.Stderr, \"warning: %s: --notes replaced existing notes (use --append-notes to preserve history)\\n\", id) //nolint:gosec // G705: stderr, not a browser context\n}\n\n// ExitGuardMismatch is the exit code when a `bd update` run failed solely\n// because --if-assignee/--if-status guards did not match: the precondition no\n// longer held, nothing was written, and retrying is pointless — another actor","sourceCodeStart":781,"sourceCodeEnd":817,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/update.go#L781-L817","documentation":"bd update's --set-metadata flag accepts one key=value pair per flag occurrence. parseSetMetadataFlags splits each flag value on '=' and rejects any entry that has no '=' separator or an empty key, because metadata cannot be assigned without both a key and a value.","triggerScenarios":"Running `bd update <id> --set-metadata foo` (no '='), `--set-metadata =bar` (empty key), or `--set-metadata \"\"` (empty string) via buildUpdatePatch or proxiedUpdatePatch.","commonSituations":"Shell quoting stripping the '=' (e.g. `--set-metadata \"key=\"` is fine but `--set-metadata key` after variable expansion loses the value); users copying `--metadata key value` style from other CLIs; typos like a space instead of '='.","solutions":["Repeat the flag once per pair with an explicit '=': --set-metadata key=value","Quote the pair so the shell doesn't split it: --set-metadata \"key=some value\"","Check `bd update --help` for the exact --set-metadata syntax"],"exampleFix":"// before\nbd update bd-42 --set-metadata priority\n// after\nbd update bd-42 --set-metadata priority=high","handlingStrategy":"validation","validationCode":"for _, pair := range setMetadataFlags {\n    if !strings.Contains(pair, \"=\") || strings.SplitN(pair, \"=\", 2)[0] == \"\" {\n        return fmt.Errorf(\"bad --set-metadata pair %q: use key=value\", pair)\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always quote key=value pairs in scripts","Use one --set-metadata flag per pair","Verify pairs with a dry run before writing"],"tags":["cli","argument-parsing","metadata"],"backgroundTag":"invalid-flag-argument","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}