mvanhorn/last30days-skill · error
%s must be a boolean
Error message
%s must be a boolean
What it means
Thrown by boolArgument (mcp/internal/tools/research.go:143) when an optional boolean argument is present but its type is not a Go bool. On the 'research' tool the only boolean is 'save', so the message reads "save must be a boolean". Because the argument is optional, omission is fine (defaults to false); the error fires only when a value IS supplied and JSON-decodes to a string ("true"), number (1/0), or null-like non-bool. There is no string-coercion: the server requires a real JSON boolean.
Source
Thrown at mcp/internal/tools/research.go:143
}
switch value {
case "":
return "compact", nil
case "compact", "html":
return value, nil
default:
return "", fmt.Errorf("emit must be 'compact' or 'html', got %q", value)
}
}
func boolArgument(args map[string]any, name string) (bool, error) {
raw, ok := args[name]
if !ok {
return false, nil
}
value, ok := raw.(bool)
if !ok {
return false, fmt.Errorf("%s must be a boolean", name)
}
return value, nil
}
// formatRunError flattens engine.Run's distinct error shapes into a single
// user-facing message that includes the relevant stderr context.
func formatRunError(runErr error, res *engine.RunResult) string {
var msg strings.Builder
msg.WriteString(runErr.Error())
if res != nil && len(res.Stderr) > 0 {
msg.WriteString("\nengine stderr:\n")
msg.Write(res.Stderr)
}
return msg.String()
}
View on GitHub (pinned to c7460f6114)
Solutions
- Pass a real JSON boolean: {"save": true} or {"save": false} — no quotes, no 1/0.
- If the flag comes from a string env var or config, convert it yourself before the call: save := flagStr == "1" || strings.EqualFold(flagStr, "true").
- Build the arguments map with Go bools (map[string]any{"save": true}) or a real JSON serializer so booleans stay unquoted.
Example fix
// before (string-interpolated JSON)
body := fmt.Sprintf(`{"topic":"x","save":"%t"}`, saveFlag)
// -> error: save must be a boolean
// after (typed map serialized with encoding/json)
args := map[string]any{"topic": "x", "save": saveFlag} // saveFlag is bool
payload, _ := json.Marshal(args) Defensive patterns
Strategy: type-guard
Validate before calling
// Coerce loose flag representations to a real bool before dispatch.
func toBool(v any) (bool, error) {
switch t := v.(type) {
case nil:
return false, nil
case bool:
return t, nil
case string:
switch strings.ToLower(t) {
case "true", "1", "yes":
return true, nil
case "false", "0", "no", "":
return false, nil
}
}
return false, fmt.Errorf("cannot use %v as boolean for save", v)
} Type guard
func isBool(v any) bool {
_, ok := v.(bool)
return ok
} Prevention
- Never build tool-call JSON with fmt.Sprintf — use typed maps plus encoding/json so booleans stay unquoted.
- When the flag originates from env vars or config files ("1"/"0", "true"/"false"), convert to bool in your code before forwarding.
- Omit optional booleans you don't need; absence defaults to false and cannot trigger this error.
When it happens
Trigger: Calling 'research' with {"save": "true"}, {"save": "false"}, {"save": 1}, or {"save": 0} instead of a bare JSON true/false. Typical when arguments are built with string formatting instead of a JSON encoder, when a model quotes the boolean, or when a config-driven pipeline stores flags as strings.
Common situations: Quoted booleans from LLM tool calls ({"save": "true"}); templates that interpolate booleans into JSON text; configs/env vars carrying "1"/"0" or "yes"/"no" being forwarded verbatim; strongly-typed clients mapping ints to the flag.
Related errors
- format must be 'text' or 'json', got %q
- %s is required
- %s must be a non-empty string
- emit must be 'compact' or 'html', got %q
- Unknown search source in {flag_name}: {source}
AI-assisted analysis of mvanhorn/last30days-skill@c7460f6114 (2026-08-15).
Data as JSON: /api/errors/4b22ff5fa6546c82.
Report an issue: GitHub.