larksuite/cli · error
%s got both %q and %q — keep %q and drop the other
Error message
%s got both %q and %q — keep %q and drop the other
What it means
normalizeSubOpInputKeys converts a kebab-case vocab key to its canonical snake_case target. If the input already contains the snake_case target while the current kebab-case key is also present, both would fight for the same logical field, so the dispatcher errors and asks you to keep the snake_case key.
Source
Thrown at shortcuts/sheets/batch_op_dispatch.go:450
}
// Normalize the surviving spelling to the underscore form the tool
// bodies use, so exactly one key reaches the flag view.
if target := strings.ReplaceAll(hv, "-", "_"); target != k {
if _, taken := input[target]; !taken {
input[target] = input[k]
delete(input, k)
canonical[hv] = target
}
}
continue
}
if kebab := camelToKebab(k); kebab != "" && vocab[kebab] {
if err := claim(kebab, k); err != nil {
return err
}
target := strings.ReplaceAll(kebab, "-", "_")
if _, taken := input[target]; taken {
return fmt.Errorf("%s got both %q and %q — keep %q and drop the other", sc, k, target, target) //nolint:forbidigo // intermediate error; the batch dispatcher wraps it into a typed operations validation error
}
if _, taken := input[kebab]; taken && kebab != target {
return fmt.Errorf("%s got both %q and %q — keep %q and drop the other", sc, k, kebab, kebab) //nolint:forbidigo // intermediate error; the batch dispatcher wraps it into a typed operations validation error
}
input[target] = input[k]
delete(input, k)
canonical[kebab] = target
continue
}
if target, ok := aliases[strings.ToLower(hv)]; ok && vocab[target] {
if err := claim(target, k); err != nil {
return err
}
underscored := strings.ReplaceAll(target, "-", "_")
_, hyphenTaken := input[target]
_, underscoreTaken := input[underscored]
if !hyphenTaken && !underscoreTaken {
input[target] = input[k]View on GitHub (pinned to 7fd6ef3c07)
Solutions
- Drop the kebab-case spelling and keep the snake_case key (`target`) with your intended value.
- If both are intended for the same field, copy the kebab value into the snake_case key and delete the kebab one.
- Normalize your payload generator to emit snake_case keys only.
Example fix
// before
{"values": [...], "cell-range": "A1:B2", "cell_range": "C3:D4"}
// after
{"values": [...], "cell_range": "C3:D4"} Defensive patterns
Strategy: validation
Validate before calling
func hasKebabSnakeConflict(op map[string]interface{}) bool {
for k := range op {
if strings.Contains(k, "-") {
if _, exists := op[strings.ReplaceAll(k, "-", "_")]; exists {
return true
}
}
}
return false
} Prevention
- Use snake_case keys exclusively in batch op inputs.
- Normalize camelCase/kebabCase keys to snake_case before dispatching.
- Validate merged payloads for duplicate logical fields.
When it happens
Trigger: Sub-op input contains e.g. both `cell-range` and `cell_range` for a vocab-defined kebab key. The camelToKebab normalization produced a kebab spelling whose snake_case form already exists in the input with its own value.
Common situations: Mixing old kebab-case-style payloads with the canonical snake_case format in the same batch op; merging two JSON fragments each using a different convention for the same flag.
Related errors
- %s got conflicting values for %q under two spellings (%q and
- %s got both %q and %q, which are two names for the same flag
- %s got both %q and "range" — keep "range" and drop %q
- Range needs a maximum column: {range_ref}
- Range needs a maximum row: {range_ref}
AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04).
Data as JSON: /api/errors/9a4369be584c9c5a.
Report an issue: GitHub.