larksuite/cli · error

SecretRef.source must be env|file|exec, got %q

Error message

SecretRef.source must be env|file|exec, got %q

What it means

SecretInput.UnmarshalJSON rejects a SecretRef whose `source` field is not one of the allowed values env, file, or exec. The check exists because secret refs are dispatched to provider backends keyed by source, and an unknown source would silently fail later at lookup time. Failing fast at JSON unmarshal keeps config errors at load time.

Source

Thrown at internal/binding/types.go:104

// EnvTemplateRe matches OpenClaw env template strings like "${FEISHU_APP_SECRET}".
// Only uppercase letters, digits, and underscores; 1-128 chars; must start with uppercase.
var EnvTemplateRe = regexp.MustCompile(`^\$\{([A-Z][A-Z0-9_]{0,127})\}$`)

// UnmarshalJSON handles both string and object forms of SecretInput.
func (s *SecretInput) UnmarshalJSON(data []byte) error {
	// Try string first
	var str string
	if err := json.Unmarshal(data, &str); err == nil {
		s.Plain = str
		s.Ref = nil
		return nil
	}

	// Try SecretRef object
	var ref SecretRef
	if err := json.Unmarshal(data, &ref); err == nil {
		if !validSources[ref.Source] {
			return fmt.Errorf("SecretRef.source must be env|file|exec, got %q", ref.Source)
		}
		if ref.ID == "" {
			return fmt.Errorf("SecretRef.id must be non-empty")
		}
		s.Ref = &ref
		s.Plain = ""
		return nil
	}

	return fmt.Errorf("appSecret must be a string or {source, provider?, id} object")
}

// MarshalJSON serializes SecretInput back to JSON.
func (s SecretInput) MarshalJSON() ([]byte, error) {
	if s.Ref != nil {
		return json.Marshal(s.Ref)
	}
	return json.Marshal(s.Plain)

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Change the source value to one of env, file, or exec
  2. Declare a custom provider with that source in secrets.providers config before referencing it
  3. Fix the typo in the source key

Example fix

// before
{"appSecret": {"source": "vault", "id": "bot-1"}}
// after
{"appSecret": {"source": "env", "id": "FEISHU_APP_SECRET"}}
Defensive patterns

Strategy: validation

Validate before calling

func validSecretRef(r map[string]any) bool {
    src, _ := r["source"].(string)
    id, _ := r["id"].(string)
    return (src == "env" || src == "file" || src == "exec") && id != ""
}

Type guard

func isSecretRef(v any) (source, id string, ok bool) {
    m, isMap := v.(map[string]any)
    if !isMap {
        return "", "", false
    }
    source, _ = m["source"].(string)
    id, _ = m["id"].(string)
    return source, id, source == "env" || source == "file" || source == "exec"
}

Prevention

When it happens

Trigger: Parsing appSecret config where a SecretRef object has a `source` value outside env|file|exec, e.g. {"source":"vault","id":"x"} or a typo like {"source":"envs","id":"x"}.

Common situations: Hand-editing config files and mistyping source; copying refs from other secret systems (vault, aws-secrets-manager); schema drift after upstream config format changes.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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