Skip to content

feat: workflow list and trigger commands#68

Open
adityachoudhari26 wants to merge 1 commit intomainfrom
ctrlc-workflow-cmd
Open

feat: workflow list and trigger commands#68
adityachoudhari26 wants to merge 1 commit intomainfrom
ctrlc-workflow-cmd

Conversation

@adityachoudhari26
Copy link
Copy Markdown
Member

@adityachoudhari26 adityachoudhari26 commented Mar 31, 2026

Summary by CodeRabbit

Release Notes

  • New Features
    • List workflows: View all available workflows in your workspace with pagination support
    • Trigger workflows: Execute workflows with custom input parameters and monitor execution status
    • Verification tracking: Added support for detailed verification metrics and release target state monitoring during workflow execution

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 31, 2026

📝 Walkthrough

Walkthrough

This change adds a new workflow subcommand to the CLI with list and trigger operations. The API client is updated to replace job template models with job agent models, add verification status tracking types, and adjust workflow creation response handling from HTTP 202 to 201 status.

Changes

Cohort / File(s) Summary
Workflow CLI Command Registration
cmd/ctrlc/root/root.go, cmd/ctrlc/root/workflow/workflow.go
Registers new workflow subcommand and its two operations: list and trigger.
Workflow List Subcommand
cmd/ctrlc/root/workflow/list.go
Implements list command to retrieve workspace workflows with optional pagination via limit and offset flags; reads configuration from Viper, creates API client, resolves workspace ID, and delegates output formatting.
Workflow Trigger Subcommand
cmd/ctrlc/root/workflow/trigger.go
Implements trigger command to start a workflow run; parses repeated --input/-i flags as key-value pairs, validates format, creates API client, and invokes workflow execution with resolved workspace ID.
API Client Models and Types
internal/api/client.gen.go
Replaces WorkflowJobTemplate with WorkflowJobAgent in workflow creation/update/retrieval types; introduces verification-related types (ReleaseTargetStateResponse, VerificationMeasurement, VerificationMetricStatus); adds status constants for verifications; updates CreateWorkflowResponse success status from HTTP 202 to 201.

Sequence Diagram(s)

sequenceDiagram
    actor CLI as List Command
    participant Viper as Viper Config
    participant APIClient as API Client
    participant Backend as Backend API
    participant Output as Output Handler

    CLI->>Viper: Read url, api-key, workspace
    CLI->>APIClient: NewAPIKeyClientWithResponses
    Note over APIClient: Initialize authenticated client
    CLI->>Backend: ListWorkflows(workspaceID, params)
    Backend-->>CLI: Workflows response
    CLI->>Output: HandleResponseOutput
    Output-->>CLI: Formatted result
Loading
sequenceDiagram
    actor CLI as Trigger Command
    participant Flags as Flag Parser
    participant Viper as Viper Config
    participant APIClient as API Client
    participant Backend as Backend API
    participant Output as Output Handler

    CLI->>Flags: Parse --input flags as key=value pairs
    Flags-->>CLI: Validated input map
    CLI->>Viper: Read url, api-key, workspace
    CLI->>APIClient: NewAPIKeyClientWithResponses
    Note over APIClient: Initialize authenticated client
    CLI->>Backend: CreateWorkflowRun(workspaceID, workflowID, inputs)
    Backend-->>CLI: Workflow run response
    CLI->>Output: HandleResponseOutput
    Output-->>CLI: Formatted result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • ctrlplane api updates #54 — Modifies the same workflow API surface in internal/api/client.gen.go with type and model updates that align with the new CLI workflow commands.

Suggested reviewers

  • jsbroks

Poem

🐰 Hop hop through workflows we go,
List and trigger, a marvelous show,
Job agents dance where templates once stood,
Verifications measure all that's good,
The CLI hops forward, swift and light!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: workflow list and trigger commands' accurately describes the main additions: new CLI commands for listing and triggering workflows.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ctrlc-workflow-cmd

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
internal/api/client.gen.go (1)

295-312: Confirm the minimum server version for the workflow contract changes.

Lines 295-312 and 1317-1368 now only read/write jobAgents, and Line 14994 only treats 201 as the typed create success. If this CLI can still target pre-migration API deployments, workflow reads will silently drop jobs and create calls will fall off the typed happy path. Please either version-gate these commands or keep dual-format handling in the API/spec until the rollout is complete.

Also applies to: 1068-1073, 1317-1368, 10783-10787, 14993-14999

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/api/client.gen.go` around lines 295 - 312, The generated client now
only serializes/deserializes CreateWorkflow.JobAgents and treats HTTP 201 as the
sole typed create success, which will break compatibility with pre-migration
APIs that use jobs or different success codes; update the client to either check
the server version before using the new schema or implement dual-format
handling: in functions that marshal/unmarshal CreateWorkflow and
CreateWorkflowJobAgent (and any create workflow response handling that currently
assumes 201), add logic to accept both "jobs" and "jobAgents" fields when
reading workflows and to emit both formats (or choose format based on a
negotiated/minimum server version); likewise, adjust the create response parsing
to accept alternative success codes or fall back to untyped handling when the
typed 201 path fails, using the CreateWorkflow and CreateWorkflowJobAgent types
as the primary identifiers to locate relevant code paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/ctrlc/root/workflow/list.go`:
- Line 30: The call to client.GetWorkspaceID(cmd.Context(), workspace) can
return a zero UUID on failure; before calling client.ListWorkflows, check the
returned workspaceID for a zero/empty UUID and return an explicit error (or wrap
with fmt.Errorf) so we fail fast and don't send an invalid ID to ListWorkflows;
update the block around GetWorkspaceID and the subsequent call to ListWorkflows
to validate workspaceID and return early if it is the zero value.
- Around line 33-38: Replace the silent-ignore behavior for negative pagination
flags by validating the limit and offset variables before assigning to params:
check if limit < 0 or offset < 0 and return a CLI validation error (e.g.,
formatted error via fmt.Errorf) instead of falling through; only set
params.Limit and params.Offset when values are non-negative and valid. Update
the code paths around the existing assignments to params.Limit/params.Offset so
invalid inputs cause an early error return from the command handler (or pre-run)
and valid inputs continue to set the pointers as currently done.

In `@cmd/ctrlc/root/workflow/trigger.go`:
- Line 33: The code assigns workspaceID via client.GetWorkspaceID but doesn't
guard against a failed lookup returning a zero UUID; before making the trigger
call, check that workspaceID is not the zero value (compare against uuid.Nil or
the project's zero-UUID constant) and return a clear error (e.g., "workspace not
found" with the original workspace string) if it is zero to avoid calling the
trigger with an invalid identifier; update the function that calls
client.GetWorkspaceID and the subsequent trigger invocation to perform this
validation and short-circuit with an error.
- Around line 37-42: The parsing loop that uses strings.Cut(input, "=")
currently allows an empty key (e.g., "--input =foo"); after extracting key,
value, found in trigger.go validate that found is true and also that key != ""
(non-empty); if key is empty return a descriptive error (e.g., "invalid input
format %q, empty key, expected key=value") instead of assigning into the inputs
map so empty keys are rejected (refer to the variables key, value, found and the
inputs map where the assignment inputs[key] = value occurs).

---

Nitpick comments:
In `@internal/api/client.gen.go`:
- Around line 295-312: The generated client now only serializes/deserializes
CreateWorkflow.JobAgents and treats HTTP 201 as the sole typed create success,
which will break compatibility with pre-migration APIs that use jobs or
different success codes; update the client to either check the server version
before using the new schema or implement dual-format handling: in functions that
marshal/unmarshal CreateWorkflow and CreateWorkflowJobAgent (and any create
workflow response handling that currently assumes 201), add logic to accept both
"jobs" and "jobAgents" fields when reading workflows and to emit both formats
(or choose format based on a negotiated/minimum server version); likewise,
adjust the create response parsing to accept alternative success codes or fall
back to untyped handling when the typed 201 path fails, using the CreateWorkflow
and CreateWorkflowJobAgent types as the primary identifiers to locate relevant
code paths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c68df1ae-9981-47c0-8b11-71cebac9d0be

📥 Commits

Reviewing files that changed from the base of the PR and between 20021c3 and fb893b7.

📒 Files selected for processing (5)
  • cmd/ctrlc/root/root.go
  • cmd/ctrlc/root/workflow/list.go
  • cmd/ctrlc/root/workflow/trigger.go
  • cmd/ctrlc/root/workflow/workflow.go
  • internal/api/client.gen.go

return fmt.Errorf("failed to create API client: %w", err)
}

workspaceID := client.GetWorkspaceID(cmd.Context(), workspace)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fail fast when workspace resolution returns a zero UUID.

Line 30 can produce a zero UUID on lookup failure, and Line 40 then sends that value to the API. Return an explicit error before calling ListWorkflows to avoid misleading failures.

Proposed fix
 		RunE: func(cmd *cobra.Command, args []string) error {
 			apiURL := viper.GetString("url")
 			apiKey := viper.GetString("api-key")
 			workspace := viper.GetString("workspace")
+			if workspace == "" {
+				return fmt.Errorf("workspace is required")
+			}
 
 			client, err := api.NewAPIKeyClientWithResponses(apiURL, apiKey)
 			if err != nil {
 				return fmt.Errorf("failed to create API client: %w", err)
 			}
 
 			workspaceID := client.GetWorkspaceID(cmd.Context(), workspace)
+			if workspaceID.String() == "00000000-0000-0000-0000-000000000000" {
+				return fmt.Errorf("failed to resolve workspace %q", workspace)
+			}

Also applies to: 40-40

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/ctrlc/root/workflow/list.go` at line 30, The call to
client.GetWorkspaceID(cmd.Context(), workspace) can return a zero UUID on
failure; before calling client.ListWorkflows, check the returned workspaceID for
a zero/empty UUID and return an explicit error (or wrap with fmt.Errorf) so we
fail fast and don't send an invalid ID to ListWorkflows; update the block around
GetWorkspaceID and the subsequent call to ListWorkflows to validate workspaceID
and return early if it is the zero value.

Comment on lines +33 to +38
if limit > 0 {
params.Limit = &limit
}
if offset > 0 {
params.Offset = &offset
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Validate pagination flags instead of silently ignoring invalid values.

Negative --limit / --offset currently fall through and are treated as unset. It's better to return a direct CLI validation error.

Proposed fix
 		RunE: func(cmd *cobra.Command, args []string) error {
+			if limit < 0 {
+				return fmt.Errorf("--limit must be >= 0")
+			}
+			if offset < 0 {
+				return fmt.Errorf("--offset must be >= 0")
+			}
+
 			apiURL := viper.GetString("url")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/ctrlc/root/workflow/list.go` around lines 33 - 38, Replace the
silent-ignore behavior for negative pagination flags by validating the limit and
offset variables before assigning to params: check if limit < 0 or offset < 0
and return a CLI validation error (e.g., formatted error via fmt.Errorf) instead
of falling through; only set params.Limit and params.Offset when values are
non-negative and valid. Update the code paths around the existing assignments to
params.Limit/params.Offset so invalid inputs cause an early error return from
the command handler (or pre-run) and valid inputs continue to set the pointers
as currently done.

return fmt.Errorf("failed to create API client: %w", err)
}

workspaceID := client.GetWorkspaceID(cmd.Context(), workspace)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard against unresolved workspace IDs before triggering runs.

Line 33 can yield a zero UUID when workspace lookup fails; Line 48 then issues the trigger call with that invalid workspace identifier. Return a clear error first.

Proposed fix
 			workspaceID := client.GetWorkspaceID(cmd.Context(), workspace)
+			if workspaceID.String() == "00000000-0000-0000-0000-000000000000" {
+				return fmt.Errorf("failed to resolve workspace %q", workspace)
+			}
 
 			inputs := make(map[string]interface{})

Also applies to: 48-48

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/ctrlc/root/workflow/trigger.go` at line 33, The code assigns workspaceID
via client.GetWorkspaceID but doesn't guard against a failed lookup returning a
zero UUID; before making the trigger call, check that workspaceID is not the
zero value (compare against uuid.Nil or the project's zero-UUID constant) and
return a clear error (e.g., "workspace not found" with the original workspace
string) if it is zero to avoid calling the trigger with an invalid identifier;
update the function that calls client.GetWorkspaceID and the subsequent trigger
invocation to perform this validation and short-circuit with an error.

Comment on lines +37 to +42
key, value, found := strings.Cut(input, "=")
if !found {
return fmt.Errorf("invalid input format %q, expected key=value", input)
}
inputs[key] = value
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Reject empty input keys in --input key=value.

--input =foo currently passes parsing and sends an empty key in the payload map. Validate key presence before assignment.

Proposed fix
 			for _, input := range inputFlags {
 				key, value, found := strings.Cut(input, "=")
-				if !found {
-					return fmt.Errorf("invalid input format %q, expected key=value", input)
+				if !found || strings.TrimSpace(key) == "" {
+					return fmt.Errorf("invalid input format %q, expected key=value with non-empty key", input)
 				}
 				inputs[key] = value
 			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
key, value, found := strings.Cut(input, "=")
if !found {
return fmt.Errorf("invalid input format %q, expected key=value", input)
}
inputs[key] = value
}
key, value, found := strings.Cut(input, "=")
if !found || strings.TrimSpace(key) == "" {
return fmt.Errorf("invalid input format %q, expected key=value with non-empty key", input)
}
inputs[key] = value
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/ctrlc/root/workflow/trigger.go` around lines 37 - 42, The parsing loop
that uses strings.Cut(input, "=") currently allows an empty key (e.g., "--input
=foo"); after extracting key, value, found in trigger.go validate that found is
true and also that key != "" (non-empty); if key is empty return a descriptive
error (e.g., "invalid input format %q, empty key, expected key=value") instead
of assigning into the inputs map so empty keys are rejected (refer to the
variables key, value, found and the inputs map where the assignment inputs[key]
= value occurs).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant