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
- Break the recursion: replace the back-reference with a concrete leaf type or a flattened representation (e.g. parent IDs instead of child pointers).
- Provide an explicit Output.Data.Shape with a bounded depth for the recursive structure.
- Re-generate or bound the data model so no type on a single path appears twice (siblings repeating a type are still allowed).
- 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
- Never let a Data struct reference itself directly or transitively; flatten trees to parent/child IDs.
- Pre-check new types with a path-scoped recursion detector before adding them to Data.
- When reusing domain models in Data, audit them for back-references introduced by later refactors.
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
- json.RawMessage requires an explicit Shape
- byte slice or array %s requires an explicit Shape
- custom JSON type %s requires an explicit Shape
- map type %s requires an explicit Shape
- interface type %s requires an explicit Shape
AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04).
Data as JSON: /api/errors/40bc7e86f7c86d7d.
Report an issue: GitHub.