charmbracelet/crush · error

session ID is required for managing todos

Error message

session ID is required for managing todos

What it means

The todos tool requires a session ID in its execution context to know which session's todo list to manage. GetSessionFromContext(ctx) returned an empty string, meaning the tool was invoked without the session-scoped context value that the agent runtime normally injects. This is an internal invariant error: the tool refuses to act rather than guess a session.

Source

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

}

type TodosResponseMetadata struct {
	IsNew         bool           `json:"is_new"`
	Todos         []session.Todo `json:"todos"`
	JustCompleted []string       `json:"just_completed,omitempty"`
	JustStarted   string         `json:"just_started,omitempty"`
	Completed     int            `json:"completed"`
	Total         int            `json:"total"`
}

func NewTodosTool(sessions session.Service) fantasy.AgentTool {
	return fantasy.NewAgentTool(
		TodosToolName,
		todosDescription,
		func(ctx context.Context, params TodosParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
			sessionID := GetSessionFromContext(ctx)
			if sessionID == "" {
				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)

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Ensure the tool is invoked through the normal agent/coordinator path which sets the session ID in the context
  2. In custom code, set the session ID in the context before calling the tool (SetSessionInContext or the equivalent context key)
  3. If this occurs during normal usage, report it — it indicates a bug in Crush's tool context wiring

Example fix

// before
resp, err := tool.Execute(ctx, call) // ctx has no session ID
// after
ctx = tools.SetSessionInContext(ctx, sessionID)
resp, err := tool.Execute(ctx, call)
Defensive patterns

Strategy: validation

Validate before calling

if tools.GetSessionFromContext(ctx) == "" {
    return errors.New("session ID must be set in context before invoking the todos tool")
}

Type guard

func hasSession(ctx context.Context) bool {
    return tools.GetSessionFromContext(ctx) != ""
}

Try / catch

if err != nil && strings.Contains(err.Error(), "session ID is required") {
    // wire session into ctx and retry
    return tool.Execute(tools.SetSessionInContext(ctx, sessionID), call)
}

Prevention

When it happens

Trigger: Calling the todos tool with a context lacking the session ID context key — e.g. invoking the fantasy.AgentTool directly in tests or custom code instead of through the coordinator, or a code path that builds the tool call context without SetSessionInContext.

Common situations: Test harnesses or scripts that invoke the tool function directly; embedding the todos tool in a custom agent runner that doesn't replicate Crush's context wiring; regressions in agent plumbing after refactoring.

Related errors


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