larksuite/cli · error

L3: _meta.access_tokens must not be empty

Error message

L3: _meta.access_tokens must not be empty

What it means

The L3 policy lint requires every envelope to declare at least one access token in Meta.AccessTokens, because the runtime must know whether the command runs as 'user' or 'bot' identity. An empty list means the envelope's identity policy was never specified, so the lint rejects it; values outside the allowed set {user, bot} are also rejected by the following loop.

Source

Thrown at internal/schema/lint.go:101

	// ---- L3: cross-field self-consistency ----
	dangerExpected := env.Meta.Risk == core.RiskWrite || env.Meta.Risk == core.RiskHighRiskWrite
	if env.Meta.Danger != dangerExpected {
		errs = append(errs, fmt.Errorf("L3: _meta.danger=%v inconsistent with risk=%q", env.Meta.Danger, env.Meta.Risk))
	}

	// `yes` lives at inputSchema.properties.yes (sibling of params/data),
	// injected only for risk == RiskHighRiskWrite.
	hasYes := false
	if env.InputSchema != nil && env.InputSchema.Properties != nil {
		_, hasYes = env.InputSchema.Properties.Map["yes"]
	}
	wantYes := env.Meta.Risk == core.RiskHighRiskWrite
	if hasYes != wantYes {
		errs = append(errs, fmt.Errorf("L3: inputSchema `yes` property=%v inconsistent with risk=%q", hasYes, env.Meta.Risk))
	}

	if len(env.Meta.AccessTokens) == 0 {
		errs = append(errs, errors.New("L3: _meta.access_tokens must not be empty"))
	}
	for _, t := range env.Meta.AccessTokens {
		if !validAccessTokens[t] {
			errs = append(errs, fmt.Errorf("L3: _meta.access_tokens contains invalid value %q (allowed: user, bot)", t))
		}
	}

	return errs
}

// walkForL2 recursively applies per-field L2 checks (format:binary on
// non-string; minimum>=maximum) plus the sub-object required-exists invariant.
// Required only matters on object-typed Properties (e.g. the params / data
// wrappers); leaf scalars ignore it.
func walkForL2(props *OrderedProps, errs *[]error) {
	if props == nil {
		return
	}

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Add at least one valid access token to Meta.AccessTokens: "user", "bot", or both as appropriate for the command's identity needs.
  2. Align tokens with the declared Risk: high-risk writes typically need an explicit token decision and a matching `yes` confirmation property (the adjacent L3 check).
  3. Re-run the envelope lint suite to confirm the policy block passes.

Example fix

// before
Meta: &Meta{EnvelopeVersion: "1.0", Risk: core.RiskSafeRead}
// after
Meta: &Meta{EnvelopeVersion: "1.0", Risk: core.RiskSafeRead, AccessTokens: []string{"user"}}
Defensive patterns

Strategy: validation

Validate before calling

func validateAccessTokens(m *Meta) error {
    if len(m.AccessTokens) == 0 {
        return errors.New("meta.access_tokens must include at least one of: user, bot")
    }
    return nil
}

Type guard

if env.Meta != nil && len(env.Meta.AccessTokens) == 0 {
    // handle: identity policy missing
}

Try / catch

if err := lintEnvelope(env); err != nil {
    // detect "access_tokens must not be empty" and add tokens
}

Prevention

When it happens

Trigger: Linting an envelope with Meta present but AccessTokens empty (len == 0), e.g. &Meta{EnvelopeVersion: "1.0", Risk: ...} without tokens.

Common situations: Authors fill in risk level but forget identity policy; meta structs generated from configs where the access_tokens key was missing; plugins copied from examples that predate the access-token requirement.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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