charmbracelet/crush · error

%s: --%s expects valid JSON, got %q: %s

Error message

%s: --%s expects valid JSON, got %q: %s

What it means

This error is thrown by parseFlagValue when a flag declared with kind flagJSONAny receives a value that fails json.Unmarshal. Unlike flagJSONObject, any JSON type is accepted (object, array, string, number, bool), but the text must be syntactically valid JSON. The underlying encoding/json error message is appended to help pinpoint the syntax problem.

Source

Thrown at internal/shellconfig/flags.go:173

	case flagJSONObject:
		v, err := nextArg(args, i, name)
		if err != nil {
			return nil, 0, err
		}
		var object map[string]any
		if err := json.Unmarshal([]byte(v), &object); err != nil || object == nil {
			return nil, 0, fmt.Errorf("%s: --%s expects a JSON object, got %q", args[0], name, v)
		}
		return object, i + 2, nil

	case flagJSONAny:
		v, err := nextArg(args, i, name)
		if err != nil {
			return nil, 0, err
		}
		var parsed any
		if err := json.Unmarshal([]byte(v), &parsed); err != nil {
			return nil, 0, fmt.Errorf("%s: --%s expects valid JSON, got %q: %s", args[0], name, v, err)
		}
		return parsed, i + 2, nil

	default:
		return nil, 0, fmt.Errorf("%s: --%s has unknown flag kind", args[0], name)
	}
}

// nextArg returns args[i+1], erroring if the flag is missing its value.
func nextArg(args []string, i int, flag string) (string, error) {
	if i+1 >= len(args) {
		return "", fmt.Errorf("%s: --%s requires a value", args[0], flag)
	}
	return args[i+1], nil
}

// storeFlag writes a parsed value into target according to spec.op.
func storeFlag(target map[string]any, spec flagSpec, val any) {

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Pipe the value through `jq .` locally to confirm it is valid JSON before passing it.
  2. Use single quotes around the whole JSON value on POSIX shells so double quotes survive.
  3. Quote all keys and strings and remove trailing commas; convert YAML/JSON5 to strict JSON.
  4. Read the appended json error (offset/position) to locate the exact syntax problem.

Example fix

// before
options set --data '{"model": claude-3,}'
// after
options set --data '{"model": "claude-3"}'
Defensive patterns

Strategy: validation

Validate before calling

echo "$value" | jq -e . >/dev/null 2>&1 || { echo "flag value is not valid JSON" >&2; exit 2; }

Type guard

func isJSON(s string) bool {
    var v any
    return json.Unmarshal([]byte(s), &v) == nil
}

Try / catch

if err := applyFlags(specs, args, start, target, cmd, stderr); err != nil {
    if strings.Contains(err.Error(), "expects valid JSON") {
        // err embeds the json.Syntax/Unmarshal message; print as-is with hint
        fmt.Fprintln(stderr, err, "\nHint: quote the value and validate with 'jq .'")
    }
    return err
}

Prevention

When it happens

Trigger: Calling a builtin flag that parses arbitrary JSON (flagJSONAny) with malformed text such as unquoted keys (`{a:1}`), single-quoted strings, unquoted bare words (`foo`), trailing commas, unterminated strings/brackets, or a value the shell partially mangled before it reached the parser.

Common situations: Single quotes inside the JSON conflicting with shell quoting; forgetting that bare words are not JSON (need `"foo"`); copying JSON5/YAML into a JSON flag; Windows cmd where quoting rules differ and inner double quotes get stripped.

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/33b02cc1c8d06291. Report an issue: GitHub.