larksuite/cli · error

appSecret must be a string or {source, id} object

Error message

appSecret must be a string or {source, id} object

What it means

The appSecret field in config.json accepts either a plain string or an object of shape {"source": "file"|"keychain", "id": "..."}. SecretInput.UnmarshalJSON returns this error when the JSON value is neither a valid SecretRef (source not in ValidSecretSources or empty id) nor a plain string. It is a config schema validation error raised during unmarshalling.

Source

Thrown at internal/core/secret.go:76

}

// UnmarshalJSON deserializes SecretInput from either a JSON string or a SecretRef object.
func (s *SecretInput) UnmarshalJSON(data []byte) error {
	// Try string first
	var plain string
	if err := json.Unmarshal(data, &plain); err == nil {
		s.Plain = plain
		s.Ref = nil
		return nil
	}
	// Try SecretRef object
	var ref SecretRef
	if err := json.Unmarshal(data, &ref); err == nil && isValidSource(ref.Source) && ref.ID != "" {
		s.Ref = &ref
		s.Plain = ""
		return nil
	}
	return fmt.Errorf("appSecret must be a string or {source, id} object")
}

// ValidSecretSources is the set of recognized SecretRef sources.
var ValidSecretSources = map[string]bool{
	"file": true, "keychain": true,
}

func isValidSource(source string) bool {
	return ValidSecretSources[source]
}

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Change appSecret to a plain JSON string: "appSecret": "your-secret".
  2. Or use a valid reference object: {"source": "file", "id": "/path/to/secret"} or {"source": "keychain", "id": "<account-key>"}.
  3. Ensure source is exactly file or keychain (case-sensitive) and id is a non-empty string.
  4. Run the CLI's config init / auth login to let the tool write a correctly shaped secret.

Example fix

// before (config.json)
"appSecret": {"source": "env", "id": "MY_SECRET"}
// after
"appSecret": {"source": "file", "id": "/home/me/.lark/secret.txt"}
Defensive patterns

Strategy: validation

Validate before calling

func validateAppSecret(v json.RawMessage) error {
	var s string
	if json.Unmarshal(v, &s) == nil && s != "" {
		return nil
	}
	var ref struct {
		Source string `json:"source"`
		ID     string `json:"id"`
	}
	if json.Unmarshal(v, &ref) == nil && core.ValidSecretSources[ref.Source] && ref.ID != "" {
		return nil
	}
	return fmt.Errorf("appSecret must be a string or {source: file|keychain, id: non-empty}")
}

Type guard

func isStringOrSecretRef(v json.RawMessage) bool {
	var s string
	if json.Unmarshal(v, &s) == nil {
		return true
	}
	var ref core.SecretRef
	return json.Unmarshal(v, &ref) == nil && core.ValidSecretSources[ref.Source] && ref.ID != ""
}

Try / catch

var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
	if strings.Contains(err.Error(), "appSecret must be") {
		// fix the appSecret field shape in config.json
	}
	return err
}

Prevention

When it happens

Trigger: Unmarshalling config.json where appSecret is a number, boolean, array, or null; or an object whose source is not exactly file or keychain, or whose id is an empty string.

Common situations: Hand-editing config.json and writing appSecret as a number or {"source": "env"}; copying a secret-ref example but leaving "id": ""; a converter producing a non-string type for the secret field.

Related errors


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