charmbracelet/crush · error

invalid status %q for todo %q

Error message

invalid status %q for todo %q

What it means

Each todo item's status must be exactly "pending", "in_progress", or "completed". The model (or caller) supplied some other string, so the tool rejects the whole batch before writing. This is a strict allow-list validation, not a type check — any casing variation or synonym fails.

Source

Thrown at internal/agent/tools/todos.go:61

				return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for managing todos")
			}

			currentSession, err := sessions.Get(ctx, sessionID)
			if err != nil {
				return fantasy.ToolResponse{}, fmt.Errorf("failed to get session: %w", err)
			}

			isNew := len(currentSession.Todos) == 0
			oldStatusByContent := make(map[string]session.TodoStatus)
			for _, todo := range currentSession.Todos {
				oldStatusByContent[todo.Content] = todo.Status
			}

			for _, item := range params.Todos {
				switch item.Status {
				case "pending", "in_progress", "completed":
				default:
					return fantasy.ToolResponse{}, fmt.Errorf("invalid status %q for todo %q", item.Status, item.Content)
				}
			}

			todos := make([]session.Todo, len(params.Todos))
			var justCompleted []string
			var justStarted string
			completedCount := 0

			for i, item := range params.Todos {
				todos[i] = session.Todo{
					Content:    item.Content,
					Status:     session.TodoStatus(item.Status),
					ActiveForm: item.ActiveForm,
				}

				newStatus := session.TodoStatus(item.Status)
				oldStatus, existed := oldStatusByContent[item.Content]

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Use only the exact strings "pending", "in_progress", "completed" in the status field
  2. Check the tool description/JSON schema the model was given matches these values
  3. Pre-normalize statuses (trim, lowercase, map synonyms) before submitting the tool call

Example fix

// before
{"content":"Run tests","status":"done"}
// after
{"content":"Run tests","status":"completed"}
Defensive patterns

Strategy: validation

Validate before calling

valid := map[string]bool{"pending":true, "in_progress":true, "completed":true}
for _, t := range todos {
    if !valid[t.Status] {
        return fmt.Errorf("status %q invalid for todo %q", t.Status, t.Content)
    }
}

Type guard

func isValidTodoStatus(s string) bool {
    return s == "pending" || s == "in_progress" || s == "completed"
}

Try / catch

if err != nil && strings.HasPrefix(err.Error(), "invalid status") {
    // normalize statuses (lowercase, map synonyms) and resubmit
}

Prevention

When it happens

Trigger: The LLM emits a status like "done", "Done", "in-progress", "complete", or "In Progress" instead of the exact lowercase snake_case tokens pending/in_progress/completed in the TodosParams.Todos payload.

Common situations: LLM hallucinating non-conforming status values; a client/library version using different status vocabularies; hand-crafted tool calls in tests with wrong enum strings.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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