{"record":{"id":"29f3cf77d9977dce","repo":"larksuite/cli","slug":"env-variable-q-referenced-in-openclaw-json-is-not","errorCode":null,"errorMessage":"env variable %q referenced in openclaw.json is not set or empty","messagePattern":"env variable %q referenced in openclaw\\.json is not set or empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/binding/secret_resolve.go","lineNumber":49,"sourceCode":"\t}\n\n\t// SecretRef object form\n\treturn resolveSecretRef(input.Ref, cfg, getenv)\n}\n\n// resolvePlainOrTemplate handles plain strings and \"${VAR}\" templates.\nfunc resolvePlainOrTemplate(value string, getenv func(string) string) (string, error) {\n\tif value == \"\" {\n\t\treturn \"\", fmt.Errorf(\"appSecret is empty string\")\n\t}\n\n\t// Check for env template pattern: \"${VAR_NAME}\"\n\tmatches := EnvTemplateRe.FindStringSubmatch(value)\n\tif matches != nil {\n\t\tvarName := matches[1]\n\t\tenvValue := getenv(varName)\n\t\tif envValue == \"\" {\n\t\t\treturn \"\", fmt.Errorf(\"env variable %q referenced in openclaw.json is not set or empty\", varName)\n\t\t}\n\t\treturn envValue, nil\n\t}\n\n\t// Plain string: use as-is\n\treturn value, nil\n}\n\n// resolveSecretRef dispatches a SecretRef to the appropriate sub-resolver.\nfunc resolveSecretRef(ref *SecretRef, cfg *SecretsConfig, getenv func(string) string) (string, error) {\n\t// Lookup provider configuration\n\tproviderConfig, err := LookupProvider(ref, cfg)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// Resolve the effective provider name once so downstream resolvers\n\t// (notably the exec JSON payload) see the config-defaulted value instead","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/internal/binding/secret_resolve.go#L31-L67","documentation":"resolvePlainOrTemplate expands \"${VAR_NAME}\" env templates in plain appSecret strings using the injected getenv (default os.Getenv). This error means the template matched EnvTemplateRe (uppercase name, 1-128 chars) but the referenced environment variable is unset or empty, so the secret cannot be resolved to a usable value. The variable name is quoted in the message.","triggerScenarios":"openclaw.json has \"appSecret\": \"${SOME_VAR}\" and getenv(\"SOME_VAR\") returns \"\" at bind time: the variable was never exported, was exported only in another shell/session, was misspelled, or is set to empty string. Note the regex anchors the whole string, so \"prefix${VAR}\" does not match the template branch at all.","commonSituations":"Secret exported in ~/.zshrc but the CLI runs under a systemd unit or CI job without it; dotenv file not sourced before running bind; variable name typo between openclaw.json and export statement; secrets manager wrote an empty value.","solutions":["Export the named variable with a non-empty value in the same environment that runs the bind: export FEISHU_APP_SECRET=... then re-run.","Verify the exact name/typo: echo \"${FEISHU_APP_SECRET}\" in the failing shell; the message shows the name being looked up.","If using a .env file, source it (set -a; . ./.env; set +a) or use the launcher (direnv, systemd EnvironmentFile) that forwards it to the process.","Fix empty-string exports: a variable set to \"\" still fails; ensure a real secret value.","In code, pre-check with the same getenv: os.LookupEnv(name) and reject !ok || value == \"\" before binding."],"exampleFix":"// before (shell)\nlark bind  # FEISHU_APP_SECRET not exported\n// after\nexport FEISHU_APP_SECRET=\"xxxx\"\nlark bind","handlingStrategy":"validation","validationCode":"func checkEnvTemplates(data []byte, lookup func(string) (string, bool)) error {\n\tre := regexp.MustCompile(`^\\$\\{([A-Z][A-Z0-9_]{0,127})\\}$`)\n\tvar cfg struct {\n\t\tChannels struct {\n\t\t\tFeishu struct {\n\t\t\t\tAppSecret string `json:\"appSecret\"`\n\t\t\t} `json:\"feishu\"`\n\t\t} `json:\"channels\"`\n\t}\n\tif err := json.Unmarshal(data, &cfg); err != nil { return err }\n\tv := cfg.Channels.Feishu.AppSecret\n\tif strings.HasPrefix(v, \"${\") && strings.HasSuffix(v, \"}\") {\n\t\tname := re.FindStringSubmatch(v)\n\t\tif name == nil { return nil }\n\t\tif val, ok := lookup(name[1]); !ok || val == \"\" {\n\t\t\treturn fmt.Errorf(\"env variable %s must be exported before binding\", name[1])\n\t\t}\n\t}\n\treturn nil\n}","typeGuard":"func envVarSet(name string) bool {\n\tv, ok := os.LookupEnv(name)\n\treturn ok && v != \"\"\n}","tryCatchPattern":"secret, err := binding.ResolveSecretInput(input, cfg, os.Getenv)\nif err != nil {\n\tvar envErr *os.PathError // not expected; demonstrate typed inspection\n\t_ = envErr\n\tif m := regexp.MustCompile(`env variable \"([^\"]+)\"`).FindStringSubmatch(err.Error()); m != nil {\n\t\treturn fmt.Errorf(\"export %s before running bind (see .env / shell profile): %w\", m[1], err)\n\t}\n\treturn err\n}","preventionTips":["Source your .env (set -a; . ./.env; set +a) or use direnv/systemd EnvironmentFile before running bind.","Keep variable names in openclaw.json and your shell profile identical; grep both for the exact name.","Remember the template must match ^\\$\\{[A-Z][A-Z0-9_]*\\}$ — uppercase, no spaces, whole string.","Check unset-but-exported-to-empty cases with os.LookupEnv in pre-flight scripts.","Document required env vars in a README or .env.example checked into the repo."],"tags":["env","secrets","config"],"backgroundTag":"missing-env-var","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"}