charmbracelet/crush · error

failed to get session: %w

Error message

failed to get session: %w

What it means

The session ID was present in the context but sessions.Get(ctx, sessionID) failed to load the session from the session service (SQLite-backed). The underlying error (e.g. record not found, DB failure) is wrapped with %w so the root cause is preserved for errors.Is/As.

Source

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

	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)
				}
			}

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

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Verify the session ID exists before invoking the tool (sessions.Get or session list)
  2. Check the DB file exists, is writable, and migrations ran (the wrapped error tells you if it's not-found vs I/O)
  3. Create a fresh session if the old one was deleted
  4. If DB-locked, close other connections or enable busy timeout

Example fix

// before
sessions.Get(ctx, "abc123") // stale/deleted ID
// after
if _, err := sessions.Get(ctx, sessionID); err != nil {
    sessionID, err = createOrResolveSession(ctx)
}
Defensive patterns

Strategy: validation

Validate before calling

if _, err := sessions.Get(ctx, sessionID); err != nil {
    return fmt.Errorf("session %s does not exist: %w", sessionID, err)
}

Type guard

func sessionExists(ctx context.Context, svc session.Service, id string) bool {
    _, err := svc.Get(ctx, id)
    return err == nil
}

Try / catch

var nf *session.NotFoundError
if errors.As(err, &nf) {
    // recreate or re-resolve the session, then retry
} else if errors.Is(err, sqlite.ErrLocked) {
    // wait/retry with backoff
}

Prevention

When it happens

Trigger: Calling the todos tool with a session ID that does not exist in the database (deleted or stale session), the session service not being initialized/connected, or the SQLite DB being corrupted or locked.

Common situations: Resuming a session that was deleted; passing a fabricated session ID in tests; DB migration mismatch or read-only database file; concurrent access locking the SQLite store.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


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