From f5e1424561a0823b1b9f9bd4e13d2453acc87d1d Mon Sep 17 00:00:00 2001 From: Hinne Stolzenberg Date: Mon, 23 Mar 2026 08:43:43 +0100 Subject: [PATCH 1/2] fix: auto-paginate workflow list to return all results ListWorkflows now fetches all pages automatically (250/page) instead of silently truncating at the first 100 results. With 300+ workflows on prod, the old behavior hid workflows from all list operations. Manual pagination still works via --cursor for single-page fetches. --- internal/api/client.go | 37 +++++++++++++++++++++++++++++-- internal/cmd/workflow/workflow.go | 8 ++----- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/internal/api/client.go b/internal/api/client.go index 5dbb6b0..281ceb7 100644 --- a/internal/api/client.go +++ b/internal/api/client.go @@ -170,8 +170,8 @@ func (c *Client) request(method, path string, body interface{}) ([]byte, error) return respBody, nil } -// ListWorkflows returns all workflows -func (c *Client) ListWorkflows(opts ListWorkflowsOptions) (*ListResult[Workflow], error) { +// listWorkflowsPage fetches a single page of workflows. +func (c *Client) listWorkflowsPage(opts ListWorkflowsOptions) (*ListResult[Workflow], error) { params := url.Values{} if opts.Limit > 0 { params.Set("limit", strconv.Itoa(opts.Limit)) @@ -213,6 +213,39 @@ func (c *Client) ListWorkflows(opts ListWorkflowsOptions) (*ListResult[Workflow] return &resp, nil } +// ListWorkflows returns all workflows, auto-paginating through all pages. +// If opts.Cursor is set, only that single page is returned (manual pagination). +func (c *Client) ListWorkflows(opts ListWorkflowsOptions) (*ListResult[Workflow], error) { + // Manual pagination: caller provided a cursor, return single page + if opts.Cursor != "" { + return c.listWorkflowsPage(opts) + } + + pageSize := opts.Limit + if pageSize <= 0 { + pageSize = 250 + } + + var all []Workflow + pageOpts := opts + pageOpts.Limit = pageSize + + for { + page, err := c.listWorkflowsPage(pageOpts) + if err != nil { + return nil, err + } + all = append(all, page.Data...) + + if page.NextCursor == "" || len(page.Data) == 0 { + break + } + pageOpts.Cursor = page.NextCursor + } + + return &ListResult[Workflow]{Data: all}, nil +} + // GetWorkflow returns a workflow by ID func (c *Client) GetWorkflow(id string) (*Workflow, error) { respBody, err := c.request(http.MethodGet, "/workflows/"+url.PathEscape(id), nil) diff --git a/internal/cmd/workflow/workflow.go b/internal/cmd/workflow/workflow.go index b776d5e..b9eda13 100644 --- a/internal/cmd/workflow/workflow.go +++ b/internal/cmd/workflow/workflow.go @@ -108,10 +108,6 @@ func newListCmd() *cobra.Command { fmt.Printf("%-18s %-6s %s\n", wf.ID, activeStr, wf.Name) } - if result.NextCursor != "" { - fmt.Printf("\nMore results available. Use --cursor %s to continue.\n", result.NextCursor) - } - return nil }, } @@ -119,8 +115,8 @@ func newListCmd() *cobra.Command { cmd.Flags().BoolVar(&active, "active", false, "Show only active workflows") cmd.Flags().BoolVar(&inactive, "inactive", false, "Show only inactive workflows") cmd.Flags().StringSliceVar(&tags, "tag", nil, "Filter by tag (can be repeated)") - cmd.Flags().IntVar(&limit, "limit", 100, "Maximum number of workflows to return") - cmd.Flags().StringVar(&cursor, "cursor", "", "Pagination cursor for next page") + cmd.Flags().IntVar(&limit, "limit", 0, "Maximum number of workflows per page (0 = auto-paginate all)") + cmd.Flags().StringVar(&cursor, "cursor", "", "Pagination cursor (fetches single page only)") cmd.Flags().StringVar(&projectID, "project", "", "Filter by project ID") cmd.Flags().StringVar(&name, "name", "", "Filter by workflow name") From 0ca21e153c5eb29ed215231ac31e5162ff6bbf80 Mon Sep 17 00:00:00 2001 From: Hinne Stolzenberg Date: Tue, 24 Mar 2026 05:40:17 +0100 Subject: [PATCH 2/2] refactor: extract page size constant, fix --limit help text Address Gemini review feedback: - Extract magic number 250 to defaultListPageSize constant - Clarify --limit flag description (all values auto-paginate) --- internal/api/client.go | 4 +++- internal/cmd/workflow/workflow.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/api/client.go b/internal/api/client.go index 281ceb7..0ec49f3 100644 --- a/internal/api/client.go +++ b/internal/api/client.go @@ -11,6 +11,8 @@ import ( "time" ) +const defaultListPageSize = 250 + // Client is the n8n API client type Client struct { baseURL string @@ -223,7 +225,7 @@ func (c *Client) ListWorkflows(opts ListWorkflowsOptions) (*ListResult[Workflow] pageSize := opts.Limit if pageSize <= 0 { - pageSize = 250 + pageSize = defaultListPageSize } var all []Workflow diff --git a/internal/cmd/workflow/workflow.go b/internal/cmd/workflow/workflow.go index b9eda13..66461bc 100644 --- a/internal/cmd/workflow/workflow.go +++ b/internal/cmd/workflow/workflow.go @@ -115,7 +115,7 @@ func newListCmd() *cobra.Command { cmd.Flags().BoolVar(&active, "active", false, "Show only active workflows") cmd.Flags().BoolVar(&inactive, "inactive", false, "Show only inactive workflows") cmd.Flags().StringSliceVar(&tags, "tag", nil, "Filter by tag (can be repeated)") - cmd.Flags().IntVar(&limit, "limit", 0, "Maximum number of workflows per page (0 = auto-paginate all)") + cmd.Flags().IntVar(&limit, "limit", 0, "Page size per API request (0 = default)") cmd.Flags().StringVar(&cursor, "cursor", "", "Pagination cursor (fetches single page only)") cmd.Flags().StringVar(&projectID, "project", "", "Filter by project ID") cmd.Flags().StringVar(&name, "name", "", "Filter by workflow name")