charmbracelet/crush · error
invalid value format: %s
Error message
invalid value format: %s
What it means
Thrown by shellVariableResolver.ResolveValue when the config value is exactly "$". This preserves a backward-compat contract: a lone "$" is treated as a malformed config value rather than a literal, even though the underlying shell parser would accept it, so configs relying on validation fail early.
Source
Thrown at internal/config/resolve.go:94
// ResolveValue resolves shell-style substitution anywhere in the string:
//
// - $(command) for command substitution, with full quoting and nesting.
// - $VAR and ${VAR} for environment variables.
// - ${VAR:-default} / ${VAR:+alt} / ${VAR:?msg} for defaulting.
//
// Unset variables expand to the empty string by default, matching bash.
// Command-substitution failures are always a hard error. Required
// credentials should use ${VAR:?message} so a missing variable fails
// loudly at load time instead of quietly resolving to empty. Global
// strict mode is available via shell.NoUnset for callers that want the
// old nounset-on behaviour back.
func (r *shellVariableResolver) ResolveValue(value string) (string, error) {
// Preserve the historical backward-compat contract: a lone "$" is a
// malformed config value, not a legal literal. The underlying shell
// parser would accept it as a literal; we reject it here so existing
// configs that relied on this validation still fail early.
if value == "$" {
return "", fmt.Errorf("invalid value format: %s", value)
}
ctx, cancel := context.WithTimeout(context.Background(), resolveTimeout)
defer cancel()
out, err := r.expand(ctx, value, r.env.Env())
if err != nil {
return "", sanitizeResolveError(value, err)
}
return out, nil
}
// maxResolveErrBytes bounds the size of the inner error message surfaced
// from a resolution failure. Defense-in-depth on top of shell.ExpandValue's
// own stderr budget: a custom Expander injected via WithExpander, or any
// future non-shell error path, must still produce a user-safe message.
const maxResolveErrBytes = 512
View on GitHub (pinned to 7944b8e522)
Solutions
- Fix the config value to be a full variable reference, e.g. "${MY_API_KEY}" or "$MY_API_KEY" instead of "$".
- Remove the malformed value if it was unintentional.
- Set the intended environment variable so expansion succeeds after fixing the reference.
- If you truly need a literal dollar sign, escape/quote it per the shell config rules (e.g. "$$" or '\$' as supported).
Example fix
// before (crushrc)
api_key "$"
// after
api_key "${OPENAI_API_KEY}" Defensive patterns
Strategy: validation
Validate before calling
func validValue(v string) error {
if v == "$" { return errors.New("invalid value format: $") }
return nil
}
// validate config values before writing them to crushrc Try / catch
out, err := resolver.ResolveValue(val)
if err != nil && strings.HasPrefix(err.Error(), "invalid value format") {
return fmt.Errorf("config value %q is malformed; use a full ${VAR} reference", val)
} Prevention
- Never write a bare "$" as a config value
- Validate variable references match ${NAME} or $NAME before saving config
- Escape literal dollar signs per shell config rules
- Lint generated config files for truncated variables
When it happens
Trigger: Calling ResolveValue("$") — typically via ConfigStore.Resolve or during config value expansion — with a variable placeholder in crushrc/crush.json that is empty, e.g. `"${"`, `"$"`, or a truncated env var reference.
Common situations: Typo in a config variable reference ("$" with no name); a shell script generating the config produced an empty variable; copy-paste truncation of "${API_KEY}".
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- not a valid bedrock api key
- not a valid vercel api key
- mcp stdio config requires a non-empty 'command' field
- mcp http config requires a non-empty 'url' field
- mcp sse config requires a non-empty 'url' field
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/d392a47aa77f644f.
Report an issue: GitHub.