charmbracelet/crush · error

%s: --%s has unknown flag kind

Error message

%s: --%s has unknown flag kind

What it means

This error is the default branch of parseFlagValue's kind switch: it fires when a flagSpec has a flagKind value the parser does not recognize. This is an internal programming error, not a user-input error — it means a developer added a new flagKind constant to the flagKind enum but did not add a corresponding case in parseFlagValue. Because flagKind is an int enum with no exhaustiveness check, the compiler cannot catch the missing case.

Source

Thrown at internal/shellconfig/flags.go:178

		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) {
	switch spec.op {
	case opSet:
		target[spec.jsonKey] = val
	case opAppend:
		arr, _ := target[spec.jsonKey].([]any)

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Add a case for the new flagKind in parseFlagValue's switch in internal/shellconfig/flags.go.
  2. Check the flagSpec tables in the shell builtins for a spec whose kind was added without parser support.
  3. As a library change, replace the int enum default with a compile-time exhaustiveness check or a unit test that iterates all flagKind values.
  4. File/fix as a bug in crush; pin or downgrade the version if hit from an unmodified release build.

Example fix

// before (new kind with no case)
const flagStringSlice flagKind = iota_last
// after
	case flagStringSlice:
		v, err := nextArg(args, i, name)
		if err != nil { return nil, 0, err }
		return strings.Split(v, ","), i + 2, nil
Defensive patterns

Strategy: type-guard

Validate before calling

// Compile-time guard in the library: ensure every flagKind is accounted for
var _ = map[flagKind]struct{}{
    flagString: {}, flagBool: {}, flagBoolTrue: {}, flagInt: {},
    flagFloat: {}, flagKeyValue: {}, flagJSONObject: {}, flagJSONAny: {},
} // extend this map when adding a new kind

Type guard

func isKnownFlagKind(k flagKind) bool {
    switch k {
    case flagString, flagBool, flagBoolTrue, flagInt, flagFloat,
        flagKeyValue, flagJSONObject, flagJSONAny:
        return true
    }
    return false
}

Try / catch

if err := applyFlags(specs, args, start, target, cmd, stderr); err != nil {
    if strings.Contains(err.Error(), "has unknown flag kind") {
        // library bug: report to crush maintainers, do not retry
        return fmt.Errorf("internal error in shellconfig flags: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: A developer defines a new flagKind constant (beyond flagJSONAny) in internal/shellconfig/flags.go and registers a flagSpec using it, then any builtin invocation that reaches that flag via applyFlags hits the default branch. Not reachable by end-user CLI input alone.

Common situations: Adding a flag kind (e.g. a future flagStringSlice) and forgetting the switch case; refactoring the enum and reordering iota constants so an unhandled value appears; merging a partial PR that adds the constant but not the parser case.

Related errors


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