mvanhorn/last30days-skill · error

%s must be a non-empty string

Error message

%s must be a non-empty string

What it means

Thrown by requireString (mcp/internal/tools/research.go:112) when the required argument key is present but is either not a Go string (e.g. a JSON number, object, or bool) or is a string that trims to empty (whitespace-only counts as empty). For the 'research' tool this means "topic must be a non-empty string". The TrimSpace check means " " fails just like "" — the server refuses to spawn a research subprocess for a topic with no content.

Source

Thrown at mcp/internal/tools/research.go:112

	return runArgs
}

func mcpSaveDir() string {
	saveDir := os.Getenv("LAST30DAYS_MEMORY_DIR")
	if saveDir == "" {
		return "~/Documents/Last30Days"
	}
	return saveDir
}

func requireString(args map[string]any, name string) (string, error) {
	raw, ok := args[name]
	if !ok {
		return "", fmt.Errorf("%s is required", name)
	}
	value, ok := raw.(string)
	if !ok || strings.TrimSpace(value) == "" {
		return "", fmt.Errorf("%s must be a non-empty string", name)
	}
	return value, nil
}

func emitArgument(args map[string]any) (string, error) {
	raw, ok := args["emit"]
	if !ok {
		return "compact", nil
	}
	value, ok := raw.(string)
	if !ok {
		return "", errors.New("emit must be a string")
	}
	switch value {
	case "":
		return "compact", nil
	case "compact", "html":
		return value, nil

View on GitHub (pinned to c7460f6114)

Solutions

  1. Send a real topic string: {"topic": "CUDA memory management"}.
  2. Validate upstream: if strings.TrimSpace(topic) == "", prompt the user for a topic instead of calling the tool.
  3. Make sure the value is a JSON string, not a number/object — quote numeric-sounding topics like "42".
  4. In client code, trim and check the topic before dispatch so your error message can be more contextual than the server's.

Example fix

// before
args := map[string]any{"topic": strings.TrimSpace(userInput)} // userInput was "   "
// -> error: topic must be a non-empty string

// after
topic := strings.TrimSpace(userInput)
if topic == "" {
    return errors.New("ask the user for a topic before calling research")
}
args := map[string]any{"topic": topic}
Defensive patterns

Strategy: validation

Validate before calling

// Reject empty/whitespace or non-string topics before dispatch.
func validTopic(v any) bool {
	s, ok := v.(string)
	return ok && strings.TrimSpace(s) != ""
}

if !validTopic(args["topic"]) {
    return errors.New("provide a non-empty research topic")
}

Type guard

func isNonEmptyString(v any) bool {
	s, ok := v.(string)
	return ok && strings.TrimSpace(s) != ""
}

Prevention

When it happens

Trigger: Calling 'research' with {"topic": ""} or {"topic": " "} or {"topic": 42}/{"topic": {"q": "..."}}. Typical sources: a model passing an empty topic when the user gave none; template interpolation producing an empty string ({{user_query}} with no input); JSON numbers for numeric-sounding topics; forwarding an unvalidated upstream field.

Common situations: Prompt-template pipelines where the topic variable is empty; UI clients sending the form state before the user typed anything; models echoing a structured query object instead of a plain string; whitespace-only input from copy-paste.

Related errors


AI-assisted analysis of mvanhorn/last30days-skill@c7460f6114 (2026-08-15). Data as JSON: /api/errors/2114eb68e174a2d6. Report an issue: GitHub.