larksuite/cli · error

Input.Relations[%d] duplicates an earlier relation

Error message

Input.Relations[%d] duplicates an earlier relation

What it means

Thrown when two relations in Input.Relations are structurally identical — the compiler JSON-marshals each definition and rejects a relation whose key was already seen. Duplicate relations add no information and would compile to ambiguous behavior.

Source

Thrown at shortcuts/common/typed_compile_contract.go:51

			return nil, fmt.Errorf("Input.Relations[%d].Stage %q is invalid", i, definition.Stage)
		}
		compiled := compiledRelation{kind: definition.Kind, presence: definition.Presence, stage: definition.Stage}
		local := make(map[string]struct{})
		for _, name := range definition.Params {
			field, ok := fieldByName[name]
			if !ok {
				return nil, fmt.Errorf("Input.Relations[%d] references unknown param --%s", i, name)
			}
			if _, duplicate := local[name]; duplicate {
				return nil, fmt.Errorf("Input.Relations[%d] repeats param --%s", i, name)
			}
			local[name] = struct{}{}
			compiled.fields = append(compiled.fields, field)
		}
		keyBytes, _ := json.Marshal(definition)
		key := string(keyBytes)
		if _, duplicate := seen[key]; duplicate {
			return nil, fmt.Errorf("Input.Relations[%d] duplicates an earlier relation", i)
		}
		seen[key] = struct{}{}
		result = append(result, compiled)
	}
	return result, nil
}

func compileAuthorization(definition typedAuthorizationDefinition, fields []compiledInputField, fieldByName map[string]int) error {
	for identity, authorization := range definition.Identities {
		for i, conditional := range authorization.ConditionalScopes {
			path := fmt.Sprintf("Authorization.%s.ConditionalScopes[%d]", identity, i)
			if len(conditional.Params) > 0 && conditional.When == "" {
				return fmt.Errorf("%s.Params requires agent-readable When text", path)
			}
			seen := make(map[string]struct{}, len(conditional.Params))
			for j, param := range conditional.Params {
				if param == "" || param != strings.TrimSpace(param) {
					return fmt.Errorf("%s.Params[%d] must be a non-blank trimmed param", path, j)

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Delete the redundant relation entry
  2. Differentiate the relations (distinct Kind/Presence/Stage/Params) if both were intentional

Example fix

// before
Relations: []Relation{
  {Kind: "lookup", Params: []string{"--chat-id"}},
  {Kind: "lookup", Params: []string{"--chat-id"}},
}
// after
Relations: []Relation{
  {Kind: "lookup", Params: []string{"--chat-id"}},
}
Defensive patterns

Strategy: validation

Validate before calling

keys := map[string]struct{}{}
for _, rel := range def.Input.Relations {
  b, _ := json.Marshal(rel)
  if _, dup := keys[string(b)]; dup { return fmt.Errorf("duplicate relation") }
  keys[string(b)] = struct{}{}
}

Try / catch

if err := common.CompileTypedDefinition(def); err != nil {
  return fmt.Errorf("duplicate relation: %w", err)
}

Prevention

When it happens

Trigger: Input.Relations contains two Relation entries with identical Kind, Presence, Stage, and Params, e.g. from an accidental copy-paste.

Common situations: Copy-pasting a relation block and forgetting to edit it; merging definitions from two shortcuts; programmatic generation appending the same relation twice.

Related errors


AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04). Data as JSON: /api/errors/9ea5f3becbe13023. Report an issue: GitHub.