larksuite/cli · error

Args field %s is unexported but declares Typed input tags

Error message

Args field %s is unexported but declares Typed input tags

What it means

collectArgFields walks the Args struct via reflection and skips unexported fields, but an unexported field that declares Typed input tags (flag, arg, schema, cli, doc, json) indicates a bug — those tags could never take effect since the field is not settable via reflection. The compiler fails fast with this error instead of silently ignoring the tags.

Source

Thrown at shortcuts/common/typed_compile_args.go:85

				return nil, nil, fmt.Errorf("Args field %s (--%s): alias --%s duplicates %s", field.goName, field.name, alias.Name, previous)
			}
			allNames[alias.Name] = "alias of --" + field.name
		}
	}
	if len(supplements) > 0 {
		for name := range supplements {
			return nil, nil, fmt.Errorf("Input.Fields references unknown flag --%s", name)
		}
	}
	return fields, fieldByName, nil
}

func collectArgFields(t reflect.Type, parentIndex []int, insideInline bool, out *[]compiledInputField, seenGo map[string]struct{}, supplements map[string]typedInputField) error {
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		if !field.IsExported() {
			if hasAnyTag(field, "flag", "arg", "schema", "cli", "doc", "json") {
				return fmt.Errorf("Args field %s is unexported but declares Typed input tags", field.Name)
			}
			continue
		}
		flagName, hasFlag := field.Tag.Lookup("flag")
		argMode, hasArg := field.Tag.Lookup("arg")
		if hasFlag == hasArg {
			return fmt.Errorf("Args field %s must declare exactly one of flag or arg", field.Name)
		}
		if _, duplicate := seenGo[field.Name]; duplicate {
			return fmt.Errorf("Args field %s is duplicated through inline expansion", field.Name)
		}
		seenGo[field.Name] = struct{}{}
		index := append(append([]int(nil), parentIndex...), i)
		if hasArg {
			switch argMode {
			case "local":
				if insideInline {
					return fmt.Errorf("Args field %s: arg:\"local\" is not allowed inside inline", field.Name)

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Export the field (capitalize its name) if it should be a CLI flag/arg.
  2. Remove the input tags from the unexported field if it is internal state.
  3. Move the value out of Args if it should not be exposed on the command line.

Example fix

// before
type Args struct {
  userID string `flag:"user-id" doc:"user id"`
}
// after
type Args struct {
  // Lark user ID to assign the task.
  UserID string `flag:"user-id"`
}
Defensive patterns

Strategy: validation

Validate before calling

func noTaggedUnexported(t reflect.Type) error {
  for i := 0; i < t.NumField(); i++ {
    f := t.Field(i)
    if !f.IsExported() {
      for _, tag := range []string{"flag","arg","schema","cli","doc","json"} {
        if _, ok := f.Tag.Lookup(tag); ok {
          return fmt.Errorf("unexported field %s has %q tag", f.Name, tag)
        }
      }
    }
  }
  return nil
}

Prevention

When it happens

Trigger: Declaring a lowercase (unexported) struct field with any of the tags flag, arg, schema, cli, doc, or json in the Args struct and compiling the definition.

Common situations: Accidentally lowercasing a field name while adding tags; copying an internal helper field from another struct along with its tags; generating structs where a field was made private but tags remained.

Related errors


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