{"record":{"id":"8e663acd646ec26d","repo":"larksuite/cli","slug":"secretref-source-must-be-env-file-exec-got-q","errorCode":null,"errorMessage":"SecretRef.source must be env|file|exec, got %q","messagePattern":"SecretRef\\.source must be env\\|file\\|exec, got %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/binding/types.go","lineNumber":104,"sourceCode":"// EnvTemplateRe matches OpenClaw env template strings like \"${FEISHU_APP_SECRET}\".\n// Only uppercase letters, digits, and underscores; 1-128 chars; must start with uppercase.\nvar EnvTemplateRe = regexp.MustCompile(`^\\$\\{([A-Z][A-Z0-9_]{0,127})\\}$`)\n\n// UnmarshalJSON handles both string and object forms of SecretInput.\nfunc (s *SecretInput) UnmarshalJSON(data []byte) error {\n\t// Try string first\n\tvar str string\n\tif err := json.Unmarshal(data, &str); err == nil {\n\t\ts.Plain = str\n\t\ts.Ref = nil\n\t\treturn nil\n\t}\n\n\t// Try SecretRef object\n\tvar ref SecretRef\n\tif err := json.Unmarshal(data, &ref); err == nil {\n\t\tif !validSources[ref.Source] {\n\t\t\treturn fmt.Errorf(\"SecretRef.source must be env|file|exec, got %q\", ref.Source)\n\t\t}\n\t\tif ref.ID == \"\" {\n\t\t\treturn fmt.Errorf(\"SecretRef.id must be non-empty\")\n\t\t}\n\t\ts.Ref = &ref\n\t\ts.Plain = \"\"\n\t\treturn nil\n\t}\n\n\treturn fmt.Errorf(\"appSecret must be a string or {source, provider?, id} object\")\n}\n\n// MarshalJSON serializes SecretInput back to JSON.\nfunc (s SecretInput) MarshalJSON() ([]byte, error) {\n\tif s.Ref != nil {\n\t\treturn json.Marshal(s.Ref)\n\t}\n\treturn json.Marshal(s.Plain)","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/internal/binding/types.go#L86-L122","documentation":"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.","triggerScenarios":"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\"}.","commonSituations":"Hand-editing config files and mistyping source; copying refs from other secret systems (vault, aws-secrets-manager); schema drift after upstream config format changes.","solutions":["Change the source value to one of env, file, or exec","Declare a custom provider with that source in secrets.providers config before referencing it","Fix the typo in the source key"],"exampleFix":"// before\n{\"appSecret\": {\"source\": \"vault\", \"id\": \"bot-1\"}}\n// after\n{\"appSecret\": {\"source\": \"env\", \"id\": \"FEISHU_APP_SECRET\"}}","handlingStrategy":"validation","validationCode":"func validSecretRef(r map[string]any) bool {\n    src, _ := r[\"source\"].(string)\n    id, _ := r[\"id\"].(string)\n    return (src == \"env\" || src == \"file\" || src == \"exec\") && id != \"\"\n}","typeGuard":"func isSecretRef(v any) (source, id string, ok bool) {\n    m, isMap := v.(map[string]any)\n    if !isMap {\n        return \"\", \"\", false\n    }\n    source, _ = m[\"source\"].(string)\n    id, _ = m[\"id\"].(string)\n    return source, id, source == \"env\" || source == \"file\" || source == \"exec\"\n}","tryCatchPattern":null,"preventionTips":["Keep source values to the env|file|exec set; never free-text","Add schema validation (JSON Schema / config lint) for secret refs in CI","Generate refs from a helper function instead of hand-writing JSON"],"tags":["config","json","validation","secrets"],"backgroundTag":"invalid-enum-value","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}