larksuite/cli · error

recursive type %s requires an explicit Shape

Error message

recursive type %s requires an explicit Shape

What it means

compileStructShape tracks struct types open on the current recursion path; if a type refers back to itself (directly or through fields) the walk would never terminate and exhaust the stack — a fatal, unrecoverable runtime error during command registration. It is rejected up front as a compile-time diagnostic requiring an explicit Shape for the recursive type.

Source

Thrown at shortcuts/common/typed_compile_data.go:189

	if schema.nullable != nil && *schema.nullable {
		shape = typedOneOfShape{Variants: []typedValueShape{shape, typedNullShape{}}}
	}
	return shape, nil
}

// compileStructShape walks one struct into an ObjectShape. active holds the
// struct types already open on the current recursion path, so a type that
// refers back to itself is reported as a compile error. Without the guard the
// walk never terminates and the goroutine stack is exhausted -- that is a
// fatal runtime error, not a panic, so no recover boundary can contain it and
// the whole CLI dies during command registration.
//
// Membership is scoped to the path rather than the whole walk: a type is
// removed once its fields are compiled, so the same type appearing twice as a
// sibling stays legal.
func compileStructShape(t reflect.Type, input bool, path string, active map[reflect.Type]struct{}) (typedObjectShape, error) {
	if _, cyclic := active[t]; cyclic {
		return typedObjectShape{}, fmt.Errorf("recursive type %s requires an explicit Shape", t)
	}
	active[t] = struct{}{}
	defer delete(active, t)

	shape := typedObjectShape{}
	seen := make(map[string]string)
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		if !field.IsExported() {
			continue
		}
		rawJSON, ok := field.Tag.Lookup("json")
		if !ok {
			return typedObjectShape{}, fmt.Errorf("%s field %s must declare json tag", path, field.Name)
		}
		parts := strings.Split(rawJSON, ",")
		name := parts[0]
		if name == "-" {

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Break the recursion: replace the back-reference with a concrete leaf type or a flattened representation (e.g. parent IDs instead of child pointers).
  2. Provide an explicit Output.Data.Shape with a bounded depth for the recursive structure.
  3. Re-generate or bound the data model so no type on a single path appears twice (siblings repeating a type are still allowed).
  4. If a tree is required, serialize it yourself (e.g. precomputed JSON string field modeled as string).

Example fix

// before
type Node struct {
    Name     string `json:"name"`
    Children []Node `json:"children"` // recursive
}

// after
type Node struct {
    Name      string   `json:"name"`
    ChildIDs  []string `json:"child_ids"` // flattened, non-recursive
}
Defensive patterns

Strategy: validation

Validate before calling

func isRecursive(t reflect.Type, path map[reflect.Type]struct{}) bool {
    for t.Kind() == reflect.Pointer { t = t.Elem() }
    if t.Kind() != reflect.Struct { return false }
    if _, ok := path[t]; ok { return true }
    path[t] = struct{}
    defer delete(path, t)
    for i := 0; i < t.NumField(); i++ {
        if isRecursive(t.Field(i).Type, path) { return true }
    }
    return false
}
// call with map[reflect.Type]struct{}{} before registering Data

Type guard

func typeOnPath(t reflect.Type, active map[reflect.Type]struct{}) bool {
    _, ok := active[t]
    return ok
}

Prevention

When it happens

Trigger: Declaring self-referential types like `type Node struct { Children []Node }` or mutual recursion A -> B -> A anywhere inside a typed Output.Data struct tree; compileStructShape detects the cycle via the `active` map.

Common situations: Tree/graph-shaped domain models (org charts, comment threads, linked structures) reused as Data; merged DTO packages introducing a back-reference; a field type changed to a parent type creating an accidental cycle.

Related errors


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