Skip to content

feat: Implement workflow step types with registry pattern (DEV-263, DEV-2969)#1899

Open
osterman wants to merge 48 commits intomainfrom
feature/dev-263-add-input-type-to-atmos-workflows
Open

feat: Implement workflow step types with registry pattern (DEV-263, DEV-2969)#1899
osterman wants to merge 48 commits intomainfrom
feature/dev-263-add-input-type-to-atmos-workflows

Conversation

@osterman
Copy link
Member

@osterman osterman commented Dec 20, 2025

what

  • Add 20+ step types across 4 categories (Interactive, Output, UI, Command) with extensible registry pattern
  • Support Go template variable passing between steps (e.g., {{ .steps.step1.value }})
  • Implement per-step output modes: viewport (pager), raw (passthrough), log (grouped), none (silent)
  • Interactive handlers with TTY detection and clear error messages in CI environments

why

Addresses DEV-263 (add input type to workflows) and DEV-2969 (add viewport support). Enables users to build complex multi-step workflows with user interaction, conditional execution, and flexible result display.

references

Summary by CodeRabbit

  • New Features

    • 25+ interactive/UI/output/terminal step types for workflows and custom commands (inputs, selects, file picker, spinners, tables, pagers, styled output, logs, exit, etc.).
    • Per-step output modes (viewport, raw, log, none), show/config overrides, and CLI workflow progress/header rendering.
  • Documentation

    • Full Step Types reference, PRD, examples, demo workflows, and blog post.
  • Tests

    • Wide-reaching unit and integration test coverage for new step handlers and execution flows.
  • UX

    • Clarified and reorganized workflow failure messaging and guidance.

@osterman osterman requested a review from a team as a code owner December 20, 2025 00:11
@github-actions github-actions bot added the size/xl Extra large size PR label Dec 20, 2025
@mergify
Copy link

mergify bot commented Dec 20, 2025

Warning

This PR exceeds the recommended limit of 1,000 lines.

Large PRs are difficult to review and may be rejected due to their size.

Please verify that this PR does not address multiple issues.
Consider refactoring it into smaller, more focused PRs to facilitate a smoother review process.

@github-actions
Copy link

github-actions bot commented Dec 20, 2025

Dependency Review

✅ No vulnerabilities or license issues found.

Scanned Files

None

@codecov
Copy link

codecov bot commented Dec 20, 2025

Codecov Report

❌ Patch coverage is 73.94199% with 548 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.70%. Comparing base (af850e8) to head (7e37a3d).

Files with missing lines Patch % Lines
pkg/workflow/progress.go 8.57% 64 Missing ⚠️
pkg/runner/step/filter.go 31.76% 57 Missing and 1 partial ⚠️
pkg/runner/step/file.go 42.39% 50 Missing and 3 partials ⚠️
pkg/runner/step/confirm.go 15.55% 38 Missing ⚠️
pkg/runner/step/atmos.go 64.64% 33 Missing and 2 partials ⚠️
pkg/runner/step/choose.go 50.70% 35 Missing ⚠️
pkg/runner/step/input.go 28.57% 35 Missing ⚠️
pkg/runner/step/write.go 17.07% 34 Missing ⚠️
pkg/workflow/show_renderer.go 17.14% 25 Missing and 4 partials ⚠️
internal/exec/workflow_utils.go 63.49% 18 Missing and 5 partials ⚠️
... and 21 more

❌ Your patch check has failed because the patch coverage (73.94%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1899      +/-   ##
==========================================
- Coverage   74.73%   74.70%   -0.04%     
==========================================
  Files         777      811      +34     
  Lines       71213    73287    +2074     
==========================================
+ Hits        53221    54747    +1526     
- Misses      14526    15030     +504     
- Partials     3466     3510      +44     
Flag Coverage Δ
unittests 74.70% <73.94%> (-0.04%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
errors/errors.go 100.00% <ø> (ø)
pkg/runner/step/alert.go 100.00% <100.00%> (ø)
pkg/runner/step/clear.go 100.00% <100.00%> (ø)
pkg/runner/step/env.go 100.00% <100.00%> (ø)
pkg/runner/step/exit.go 100.00% <100.00%> (ø)
pkg/runner/step/join.go 100.00% <100.00%> (ø)
pkg/runner/step/linebreak.go 100.00% <100.00%> (ø)
pkg/runner/step/markdown.go 100.00% <100.00%> (ø)
pkg/runner/step/sleep.go 100.00% <100.00%> (ø)
pkg/runner/step/title.go 100.00% <100.00%> (ø)
... and 35 more

... and 6 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 20, 2025

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a registry-driven workflow step system (25+ step types), expanded workflow/task schema (viewport, show, output modes), a StepExecutor with Variables propagation, OutputModeWriter, Show/Progress renderers, many step handlers and tests, plus docs/examples — all implemented via new packages and integrations into workflow execution.

Changes

Cohort / File(s) Summary
Schema & Task
pkg/schema/workflow.go, pkg/schema/task.go
Add ViewportConfig, ShowConfig, WorkflowDefinition, WorkflowManifest; expand WorkflowStep/Task with interactive/display/style/env/timeout/output fields; make command omitempty.
Step Core & Registry
pkg/runner/step/... (types.go, registry.go, executor.go, variables.go, handler_base.go, show_config.go)
Introduce StepCategory/OutputMode/StepResult, thread-safe handler registry, BaseHandler helpers (TTY, template resolution), Variables store, ShowConfig merge utilities, and StepExecutor (RunAll/Execute, var propagation, workflow context).
Step Handlers
pkg/runner/step/{input,confirm,choose,filter,file,write,toast,alert,markdown,title,clear,format,join,table,style,pager,linebreak,spin,log,env,exit,sleep,stage,spin,shell,atmos,...}
Add ~25+ handlers implementing Validate/Execute (and ExecuteWithWorkflow where applicable) across Interactive/UI/Output/Terminal/Command categories; register handlers in the new registry.
Output Modes & Execution
pkg/runner/step/output_mode.go, pkg/runner/step/output_mode_*.go, pkg/runner/step/atmos.go, pkg/runner/step/shell.go
New OutputModeWriter (viewport/raw/log/none), StreamingOutputWriter, helpers GetOutputMode/GetViewportConfig/FormatStepLabel/RenderCommand; Shell and Atmos handlers integrate output modes and viewport.
Workflow UX & Executor Integration
internal/exec/workflow_utils.go, pkg/workflow/executor.go, pkg/workflow/progress.go, pkg/workflow/show_renderer.go
Initialize/reset step executor state, wire ShowRenderer and ProgressRenderer, render workflow header/flags and per-step command labels, support extended-step dispatch and progress lifecycle.
Runner & CLI
pkg/runner/runner.go, cmd/cmd_utils.go
Runner and custom command dispatch integrated with step registry and StepExecutor; reuse executor to preserve step outputs; StepVars propagation added.
Errors, Config & Theming
errors/errors.go, pkg/config/load.go, pkg/ui/theme/*.go, pkg/ui/markdown/styles.go, pkg/auth/cloud/aws/env.go
Add sentinel errors for step workflow control (e.g., ErrUnknownStepType, ErrWorkflowExit), switch to errors.As for viper not-found checks, theme constant DefaultThemeName, H1 spacing tweak, and env map capacity guard.
Pager & Viewport
pkg/pager/*
Add viewport maxHeight/maxWidth constraints, pager constructor NewWithViewport, tests for dimension constraints and propagation.
Tests & Snapshots
pkg/runner/step/*_test.go, pkg/runner/step/*_test.go (many), tests/snapshots/*
50+ new/updated tests covering handlers, registry, variables, output modes, templates, pager and workflow snapshots updated (hints → explanation).
Docs, PRD & Examples
docs/prd/*, website/docs/*, website/blog/*, examples/*
Add PRD, test-coverage plan, website docs/blog updates, step-types partial, numerous example workflows and interactive custom-command examples.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Workflow as Workflow Executor
    participant Registry as Step Registry
    participant Handler as Step Handler
    participant Vars as Variables Store
    participant UI as UI/Pager/Terminal

    User->>Workflow: run workflow
    Workflow->>Vars: NewVariables / SetWorkflow
    Workflow->>Workflow: Init Progress & Show renderers
    loop for each step
        Workflow->>Registry: Get(step.Type)
        Registry-->>Workflow: Handler
        Workflow->>Handler: Validate(step)
        Handler-->>Workflow: ok / error
        Workflow->>Handler: Execute(ctx, step, Vars)
        alt interactive step
            Handler->>UI: render prompt/form
            UI-->>Handler: user input
        else command step
            Handler->>Handler: determine OutputMode/Viewport
            Handler->>UI: stream or capture output
        end
        Handler->>Vars: Set(stepName, StepResult)
        Handler-->>Workflow: StepResult
        Workflow->>Workflow: Update Progress & Render
    end
    Workflow->>User: completion / exit
Loading
sequenceDiagram
    participant Handler
    participant Output as OutputModeWriter
    participant Cmd as OS Process
    participant Pager as Pager/UI
    participant Log as Logger

    Handler->>Output: NewOutputModeWriter(mode, stepName, viewport)
    Handler->>Output: Execute(cmd)
    Output->>Cmd: Run process
    alt viewport
        Cmd-->>Output: capture stdout/stderr
        Output->>Pager: render captured output
    else raw
        Cmd-->>Output: stream stdout/stderr to terminal
    else log
        Output->>Log: print header
        Cmd-->>Output: run and collect output
        Output->>Log: print footer
    else none
        Cmd-->>Output: run silently
    end
    Output-->>Handler: stdout, stderr, err
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • aknysh
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.27% 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 title clearly summarizes the main change: implementing workflow step types with a registry pattern, directly addressing the primary objective of adding 20+ interactive and output step types.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/dev-263-add-input-type-to-atmos-workflows

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
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: 15

🧹 Nitpick comments (26)
pkg/schema/workflow.go (1)

15-53: WorkflowStep schema extension looks solid.

Fields are well-organized by category with clear inline comments. The Timeout field as a string is a reasonable choice for YAML/JSON configs (e.g., "30s", "5m").

One note: consider documenting valid Output mode values ("viewport", "raw", "log", "none") in the comment on line 46, or referencing a constants file if one exists.

pkg/workflow/step/executor_test.go (1)

224-242: ListTypes test verifies registry categories.

Confirms all categories are present with sample type checks. Consider adding a count assertion to catch regressions if types are accidentally removed.

pkg/workflow/step/pager.go (1)

27-49: Execute implementation is well-structured.

  • Content resolution with error handling.
  • Conditional title resolution.
  • Error messages include step name for traceability.

The ctx parameter is unused. If pager.Run doesn't support context cancellation, this is acceptable. Otherwise, passing it through would enable timeout/cancellation support.

pkg/workflow/step/error.go (1)

26-38: Consider wrapping the ui.Error failure with step context.

Line 33-35: if ui.Error fails, the error is returned without step name context. Other handlers (e.g., pager.go line 45) wrap errors with step name for traceability.

🔎 Suggested improvement
-	if err := ui.Error(content); err != nil {
-		return nil, err
-	}
+	if err := ui.Error(content); err != nil {
+		return nil, fmt.Errorf("step '%s': failed to display error: %w", step.Name, err)
+	}

This would require adding "fmt" to imports.

pkg/workflow/step/info.go (1)

26-38: Same error wrapping consideration as ErrorHandler.

Line 33-35: the ui.Info error lacks step context. For consistency with pager.go and better debugging, consider wrapping.

🔎 Suggested improvement
-	if err := ui.Info(content); err != nil {
-		return nil, err
-	}
+	if err := ui.Info(content); err != nil {
+		return nil, fmt.Errorf("step '%s': failed to display info: %w", step.Name, err)
+	}

This would require adding "fmt" to imports.

pkg/workflow/step/file.go (2)

53-53: Consider using static errors from errors/errors.go for error wrapping.

Per the coding guidelines, errors should be wrapped using static errors defined in errors/errors.go rather than dynamic fmt.Errorf strings. For example, lines 53, 60, 90, 101, and 131 use dynamic error messages. Consider defining static errors like ErrFailedToResolvePath, ErrFailedToScanDirectory, ErrNoFilesFound, and ErrFileSelectionFailed in the errors package and wrapping them with context.

As per coding guidelines, all errors must be wrapped using static errors defined in errors/errors.go.

Also applies to: 60-60, 90-90, 101-101, 131-131


65-99: filepath.Walk error handling skips inaccessible files silently.

Lines 65-99 use filepath.Walk and return nil when encountering access errors (line 67), which silently skips files the user can't access. While this prevents the walk from failing entirely, users might be confused if expected files don't appear. Consider logging skipped files or accumulating warnings to surface later.

pkg/workflow/step/table.go (3)

51-56: Potential index out of bounds if Data is empty.

Line 53 accesses step.Data[0] without verifying that len(step.Data) > 0 in the condition. While line 51 checks len(step.Data) > 0, if Data passes validation as empty (when Content is present), this code path shouldn't be reached. Consider adding an explicit guard or comment clarifying the invariant.


83-83: TODO: Integration with existing table formatter.

The TODO on line 83 notes plans to integrate with pkg/list/format/table.go for lipgloss styling. The current tab-separated approach is functional but basic.

Would you like me to create an issue to track this integration work?


27-27: Use static errors for error wrapping.

Lines 27 and 90 use dynamic error messages with fmt.Errorf. Per coding guidelines, consider defining static errors in errors/errors.go (e.g., ErrTableDataRequired, ErrFailedToResolveTitle) and wrapping them with context.

As per coding guidelines, all errors must be wrapped using static errors defined in errors/errors.go.

Also applies to: 90-90

pkg/workflow/step/confirm.go (1)

75-75: Use static error for confirmation failure.

Line 75 uses fmt.Errorf with a dynamic message. Per coding guidelines, define a static error like ErrConfirmationFailed in errors/errors.go and wrap it with the step name context.

As per coding guidelines, all errors must be wrapped using static errors defined in errors/errors.go.

pkg/workflow/step/output_mode_test.go (1)

71-89: Concurrent write test assumes deterministic byte accumulation.

The test on line 88 asserts an exact length of 40 bytes (10 goroutines × 4 bytes each). While this validates accumulation, concurrent writes may have interleaving or buffering behaviors that could affect the exact output. Consider whether this test should focus on the presence of all data rather than exact byte count, or add a comment documenting the assumption.

pkg/workflow/step/spin.go (1)

68-72: Consider addressing the TODO for shell parsing.

strings.Fields will break on quoted arguments (e.g., echo "hello world"). The TODO is noted, but this could cause subtle bugs. A library like github.com/kballard/go-shellquote or github.com/google/shlex would handle this properly.

pkg/workflow/step/join.go (1)

31-56: Consider making the separator configurable.

Currently hardcoded to newline. A configurable separator (via a new field or reusing an existing one) would add flexibility. Not blocking—the current default is reasonable.

pkg/workflow/step/style.go (1)

3-9: Organize imports into three groups with blank lines.

Per coding guidelines, imports should be in three groups separated by blank lines: 1) Go stdlib, 2) 3rd-party, 3) Atmos packages.

Suggested import organization
 import (
 	"context"
+
 	"github.com/cloudposse/atmos/pkg/data"
 	"github.com/cloudposse/atmos/pkg/schema"
 	"github.com/cloudposse/atmos/pkg/ui/theme"
 )

Based on coding guidelines requiring three-group import organization.

pkg/workflow/step/markdown.go (1)

3-8: Organize imports into three groups with blank lines.

Imports should be separated into groups per coding guidelines: 1) Go stdlib, 2) 3rd-party, 3) Atmos packages.

Suggested import organization
 import (
 	"context"
+
 	"github.com/cloudposse/atmos/pkg/schema"
 	"github.com/cloudposse/atmos/pkg/ui"
 )

Based on coding guidelines.

pkg/workflow/step/atmos.go (1)

3-11: Organize imports with blank lines between groups.

Suggested organization
 import (
 	"context"
 	"fmt"
 	"os"
 	"os/exec"
 	"strings"
+
 	"github.com/cloudposse/atmos/pkg/schema"
 )

Based on coding guidelines.

pkg/workflow/step/warn.go (1)

3-8: Organize imports with blank lines between groups.

Suggested organization
 import (
 	"context"
+
 	"github.com/cloudposse/atmos/pkg/schema"
 	"github.com/cloudposse/atmos/pkg/ui"
 )

Based on coding guidelines.

pkg/workflow/step/filter.go (1)

3-14: Organize imports with blank lines between groups.

Suggested organization
 import (
 	"context"
 	"errors"
 	"fmt"
+
 	"github.com/charmbracelet/bubbles/key"
 	"github.com/charmbracelet/huh"
+
 	errUtils "github.com/cloudposse/atmos/errors"
 	uiutils "github.com/cloudposse/atmos/internal/tui/utils"
 	"github.com/cloudposse/atmos/pkg/schema"
 )

Based on coding guidelines.

pkg/workflow/step/input.go (1)

3-14: Organize imports with blank lines between groups.

Suggested organization
 import (
 	"context"
 	"errors"
 	"fmt"
+
 	"github.com/charmbracelet/bubbles/key"
 	"github.com/charmbracelet/huh"
+
 	errUtils "github.com/cloudposse/atmos/errors"
 	uiutils "github.com/cloudposse/atmos/internal/tui/utils"
 	"github.com/cloudposse/atmos/pkg/schema"
 )

Based on coding guidelines.

pkg/workflow/step/output_handlers_test.go (1)

3-10: Organize imports with blank lines between groups.

Suggested organization
 import (
 	"testing"
+
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+
 	"github.com/cloudposse/atmos/pkg/schema"
 )

Based on coding guidelines.

pkg/workflow/step/shell.go (1)

3-12: Organize imports with blank lines between groups.

Suggested organization
 import (
 	"context"
 	"errors"
 	"fmt"
 	"os"
 	"os/exec"
 	"strings"
+
 	"github.com/cloudposse/atmos/pkg/schema"
 )

Based on coding guidelines.

pkg/workflow/step/interactive_handlers_test.go (2)

90-91: Malformed nolint directive.

The nolintlint comment on line 90 isn't a valid directive format. It should either be removed or properly formatted.

🔎 Proposed fix
-// nolintlint: dupl - Similar test patterns for different handlers.
 // nolint: dupl
 func TestConfirmHandlerValidation(t *testing.T) {

203-204: Same malformed nolint directive here.

Same issue as line 90 — the nolintlint comment isn't doing anything useful.

🔎 Proposed fix
-// nolintlint: dupl - Similar test patterns for different handlers.
 // nolint: dupl
 func TestWriteHandlerValidation(t *testing.T) {
pkg/workflow/step/registry.go (1)

42-46: Consider nil handler guard.

If a nil handler is passed, handler.GetName() will panic. Since this runs in init(), a panic is acceptable for fail-fast, but an explicit check could provide a clearer error message.

🔎 Optional defensive check
 func Register(handler StepHandler) {
+	if handler == nil {
+		panic("cannot register nil step handler")
+	}
 	registry.mu.Lock()
 	defer registry.mu.Unlock()
 	registry.handlers[handler.GetName()] = handler
 }
pkg/workflow/step/handler_base.go (1)

73-107: Unused context parameter.

The ctx parameter is passed but unused in all three Resolve* methods. This is fine for forward compatibility (future cancellation support), but you could add a _ = ctx or brief comment to silence linters if they flag it.

🔎 Optional: silence unused parameter warnings
 func (h BaseHandler) ResolveContent(ctx context.Context, step *schema.WorkflowStep, vars *Variables) (string, error) {
+	_ = ctx // Reserved for future cancellation support.
 	if step.Content == "" {
 		return "", nil
 	}

@osterman osterman added the minor New features that do not break anything label Dec 20, 2025
@github-actions
Copy link

Warning

Changelog Entry Required

This PR is labeled minor or major but doesn't include a changelog entry.

Action needed: Add a new blog post in website/blog/ to announce this change.

Example filename: website/blog/2025-12-20-feature-name.mdx

Alternatively: If this change doesn't require a changelog entry, remove the minor or major label.

Copy link
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: 1

♻️ Duplicate comments (6)
pkg/workflow/step/atmos.go (3)

30-42: Add performance tracking to public Execute method.

Missing the required defer perf.Track() call per coding guidelines.

Add performance tracking
 func (h *AtmosHandler) Execute(ctx context.Context, step *schema.WorkflowStep, vars *Variables) (*StepResult, error) {
+	defer perf.Track(nil, "step.AtmosHandler.Execute")()
+
 	opts, err := h.prepareExecution(ctx, step, vars)

Based on coding guidelines.


165-176: Add performance tracking to public ExecuteWithWorkflow method.

Missing the required defer perf.Track() call per coding guidelines.

Add performance tracking
 func (h *AtmosHandler) ExecuteWithWorkflow(ctx context.Context, step *schema.WorkflowStep, vars *Variables, workflow *schema.WorkflowDefinition) (*StepResult, error) {
+	defer perf.Track(nil, "step.AtmosHandler.ExecuteWithWorkflow")()
+
 	opts, err := h.prepareExecution(ctx, step, vars)

Based on coding guidelines.


179-194: Remove unreachable dead code.

Lines 189-191 are unreachable because line 181 already handles arg == "-s" and returns true. The final check for a following value is never reached.

Remove dead code
 func containsStackFlag(args []string) bool {
 	for i, arg := range args {
 		if arg == "-s" || arg == "--stack" {
 			return true
 		}
 		// Check for -s=value or --stack=value.
 		if strings.HasPrefix(arg, "-s=") || strings.HasPrefix(arg, "--stack=") {
 			return true
 		}
-		// Check if next arg would be stack value for -s.
-		if arg == "-s" && i+1 < len(args) {
-			return true
-		}
 	}
 	return false
 }
pkg/workflow/step/filter.go (1)

42-68: Add performance tracking to public Execute method.

Missing the required defer perf.Track() call per coding guidelines.

Add performance tracking
 func (h *FilterHandler) Execute(ctx context.Context, step *schema.WorkflowStep, vars *Variables) (*StepResult, error) {
+	defer perf.Track(nil, "step.FilterHandler.Execute")()
+
 	if err := h.CheckTTY(step); err != nil {

Based on coding guidelines.

pkg/workflow/step/choose.go (1)

100-109: Default selection not applied.

The call to opt.Selected(true) returns a new option with the selected state but doesn't mutate the existing option. Since the returned value isn't assigned back, the default selection is lost.

Proposed fix
 func (h *ChooseHandler) runSelectForm(stepName, prompt string, options []string, defaultVal string) (*StepResult, error) {
 	var choice string
-	huhOptions := huh.NewOptions(options...)
-	for _, opt := range huhOptions {
+	huhOptions := make([]huh.Option[string], len(options))
+	for i, optVal := range options {
+		opt := huh.NewOption(optVal, optVal)
-		if opt.Value == defaultVal {
-			opt.Selected(true)
-			break
+		if optVal == defaultVal {
+			opt = opt.Selected(true)
 		}
+		huhOptions[i] = opt
 	}
pkg/workflow/step/executor.go (1)

103-120: RunAll still uses dynamic error for step failure.

Line 115 creates fmt.Errorf("step '%s' failed: %w", ...). Per past review and coding guidelines, this should wrap a static error like ErrWorkflowStepFailed.

Recommended fix
-			return fmt.Errorf("step '%s' failed: %w", step.Name, err)
+			return errUtils.Build(errUtils.ErrWorkflowStepFailed).
+				WithContext("step", step.Name).
+				WithCause(err).
+				Err()

This assumes ErrWorkflowStepFailed exists in errors/errors.go (per learnings, it should).

🧹 Nitpick comments (8)
examples/demo-interactive-workflows/README.md (1)

145-145: Add closing punctuation.

The final line should end with a period for consistency with documentation standards.

🔎 Proposed fix
-- Use environment variables instead of prompts
+- Use environment variables instead of prompts.
pkg/workflow/step/log.go (1)

64-71: Consider logging template resolution failures.

When vars.Resolve(value) fails, the error is silently discarded and the original value is used. While this provides a fallback, it could mask configuration issues where template variables are misspelled or undefined.

Consider logging the error at debug level so issues are discoverable:

🔎 Proposed enhancement
 	for key, value := range step.Fields {
 		// Resolve template variables in field values.
 		resolvedValue, err := vars.Resolve(value)
 		if err != nil {
-			// On error, use the original value.
+			// On error, use the original value but log for debugging.
+			log.Debug("Failed to resolve template in log field", "key", key, "value", value, "error", err.Error())
 			resolvedValue = value
 		}
 		keyvals = append(keyvals, key, resolvedValue)
 	}
pkg/workflow/step/linebreak.go (1)

29-41: Add performance tracking to public Execute method.

The Execute method is missing the required defer perf.Track() call per coding guidelines.

Add performance tracking
 func (h *LinebreakHandler) Execute(ctx context.Context, step *schema.WorkflowStep, vars *Variables) (*StepResult, error) {
+	defer perf.Track(nil, "step.LinebreakHandler.Execute")()
+
 	count := step.Count

Based on coding guidelines.

pkg/workflow/step/choose.go (1)

42-63: Add performance tracking to public Execute method.

Missing the required defer perf.Track() call per coding guidelines.

Add performance tracking
 func (h *ChooseHandler) Execute(ctx context.Context, step *schema.WorkflowStep, vars *Variables) (*StepResult, error) {
+	defer perf.Track(nil, "step.ChooseHandler.Execute")()
+
 	if err := h.CheckTTY(step); err != nil {

Based on coding guidelines.

pkg/workflow/step/pager.go (1)

34-70: Add performance tracking to public Execute method.

Missing the required defer perf.Track() call per coding guidelines.

Add performance tracking
 func (h *PagerHandler) Execute(ctx context.Context, step *schema.WorkflowStep, vars *Variables) (*StepResult, error) {
+	defer perf.Track(nil, "step.PagerHandler.Execute")()
+
 	var content string

Based on coding guidelines.

pkg/workflow/step/file.go (1)

36-73: Add performance tracking to public Execute method.

Missing the required defer perf.Track() call per coding guidelines.

Add performance tracking
 func (h *FileHandler) Execute(ctx context.Context, step *schema.WorkflowStep, vars *Variables) (*StepResult, error) {
+	defer perf.Track(nil, "step.FileHandler.Execute")()
+
 	if err := h.CheckTTY(step); err != nil {

Based on coding guidelines.

pkg/workflow/step/handler_base.go (1)

76-110: Unused context parameter in Resolve methods.

The ctx parameter is unused in ResolveContent, ResolvePrompt, and ResolveCommand. If it's for future cancellation support or interface consistency, consider adding a brief comment. Otherwise, these could be simplified.

Option: Document intent or remove unused param

If ctx is for future use:

// ResolveContent resolves Go templates in the content field.
// The context is included for future cancellation support.
func (h BaseHandler) ResolveContent(ctx context.Context, step *schema.WorkflowStep, vars *Variables) (string, error) {

Or if not needed, remove it from the signature (would require updating callers).

pkg/workflow/step/output_mode.go (1)

222-236: StreamingOutputWriter silently ignores write errors.

Line 232 discards the error from fmt.Fprintf. If the target is unavailable, this fails silently. Consider at minimum logging a warning or returning the error.

Option: Handle or log write errors
 	// Write with prefix to target.
 	if w.target != nil {
-		_, _ = fmt.Fprintf(w.target, "%s %s", w.prefix, string(p))
+		if _, writeErr := fmt.Fprintf(w.target, "%s %s", w.prefix, string(p)); writeErr != nil {
+			// Log at debug level or accumulate for later inspection.
+		}
 	}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a82ae17 and 5a30c93.

📒 Files selected for processing (34)
  • errors/errors.go (1 hunks)
  • examples/demo-interactive-workflows/README.md (1 hunks)
  • examples/demo-interactive-workflows/atmos.yaml (1 hunks)
  • examples/demo-interactive-workflows/stacks/deploy/dev.yaml (1 hunks)
  • examples/demo-interactive-workflows/stacks/deploy/prod.yaml (1 hunks)
  • examples/demo-interactive-workflows/stacks/deploy/staging.yaml (1 hunks)
  • examples/demo-interactive-workflows/stacks/workflows/interactive.yaml (1 hunks)
  • internal/exec/workflow_utils.go (5 hunks)
  • pkg/schema/workflow.go (1 hunks)
  • pkg/ui/markdown/styles.go (1 hunks)
  • pkg/ui/theme/converter.go (1 hunks)
  • pkg/ui/theme/styles.go (3 hunks)
  • pkg/workflow/step/atmos.go (1 hunks)
  • pkg/workflow/step/choose.go (1 hunks)
  • pkg/workflow/step/executor.go (1 hunks)
  • pkg/workflow/step/executor_test.go (1 hunks)
  • pkg/workflow/step/file.go (1 hunks)
  • pkg/workflow/step/filter.go (1 hunks)
  • pkg/workflow/step/handler_base.go (1 hunks)
  • pkg/workflow/step/interactive_handlers_test.go (1 hunks)
  • pkg/workflow/step/join.go (1 hunks)
  • pkg/workflow/step/linebreak.go (1 hunks)
  • pkg/workflow/step/log.go (1 hunks)
  • pkg/workflow/step/output_handlers_test.go (1 hunks)
  • pkg/workflow/step/output_mode.go (1 hunks)
  • pkg/workflow/step/pager.go (1 hunks)
  • pkg/workflow/step/shell.go (1 hunks)
  • pkg/workflow/step/spin.go (1 hunks)
  • pkg/workflow/step/style.go (1 hunks)
  • pkg/workflow/step/table.go (1 hunks)
  • pkg/workflow/step/variables.go (1 hunks)
  • pkg/workflow/step/variables_test.go (1 hunks)
  • website/docs/cli/commands/workflow.mdx (1 hunks)
  • website/docs/workflows/workflows.mdx (2 hunks)
✅ Files skipped from review due to trivial changes (4)
  • examples/demo-interactive-workflows/atmos.yaml
  • examples/demo-interactive-workflows/stacks/deploy/staging.yaml
  • website/docs/workflows/workflows.mdx
  • examples/demo-interactive-workflows/stacks/deploy/dev.yaml
🚧 Files skipped from review as they are similar to previous changes (7)
  • pkg/workflow/step/variables_test.go
  • pkg/workflow/step/join.go
  • pkg/workflow/step/interactive_handlers_test.go
  • pkg/workflow/step/shell.go
  • pkg/workflow/step/variables.go
  • pkg/workflow/step/table.go
  • pkg/workflow/step/style.go
🧰 Additional context used
📓 Path-based instructions (4)
**/*.go

📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)

**/*.go: Use Viper for managing configuration, environment variables, and flags in CLI commands
Use interfaces for external dependencies to facilitate mocking and consider using testify/mock for creating mock implementations
All code must pass golangci-lint checks
Follow Go's error handling idioms: use meaningful error messages, wrap errors with context using fmt.Errorf("context: %w", err), and consider using custom error types for domain-specific errors
Follow standard Go coding style: use gofmt and goimports to format code, prefer short descriptive variable names, use kebab-case for command-line flags, and snake_case for environment variables
Document all exported functions, types, and methods following Go's documentation conventions
Document complex logic with inline comments in Go code
Support configuration via files, environment variables, and flags following the precedence order: flags > environment variables > config file > defaults
Provide clear error messages to users, include troubleshooting hints when appropriate, and log detailed errors for debugging

**/*.go: NEVER use fmt.Fprintf(os.Stdout/Stderr) or fmt.Println(); use data.* or ui.* functions instead
All comments must end with periods (enforced by godot linter)
Organize imports in three groups separated by blank lines, sorted alphabetically: 1) Go stdlib, 2) 3rd-party (NOT cloudposse/atmos), 3) Atmos packages; maintain aliases: cfg, log, u, errUtils
Add defer perf.Track(atmosConfig, "pkg.FuncName")() + blank line to all public functions for performance tracking; use nil if no atmosConfig param
All errors MUST be wrapped using static errors defined in errors/errors.go; use errors.Join for combining multiple errors; use fmt.Errorf with %w for adding string context; use error builder for complex errors; use errors.Is() for error checking; NEVER use dynamic errors directly
Use go.uber.org/mock/mockgen with //go:generate directives for mock generation; never create manual mocks
Keep files small...

Files:

  • pkg/ui/theme/converter.go
  • pkg/ui/markdown/styles.go
  • pkg/workflow/step/spin.go
  • pkg/ui/theme/styles.go
  • pkg/workflow/step/executor_test.go
  • pkg/workflow/step/choose.go
  • pkg/workflow/step/linebreak.go
  • errors/errors.go
  • pkg/workflow/step/pager.go
  • pkg/workflow/step/file.go
  • pkg/workflow/step/filter.go
  • internal/exec/workflow_utils.go
  • pkg/workflow/step/atmos.go
  • pkg/workflow/step/log.go
  • pkg/workflow/step/output_mode.go
  • pkg/workflow/step/handler_base.go
  • pkg/workflow/step/output_handlers_test.go
  • pkg/workflow/step/executor.go
  • pkg/schema/workflow.go
website/**

📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)

website/**: Update website documentation in the website/ directory when adding new features, ensure consistency between CLI help text and website documentation, and follow the website's documentation structure and style
Keep website code in the website/ directory, follow the existing website architecture and style, and test website changes locally before committing
Keep CLI documentation and website documentation in sync and document new features on the website with examples and use cases

Files:

  • website/docs/cli/commands/workflow.mdx
website/docs/cli/commands/**/*.mdx

📄 CodeRabbit inference engine (CLAUDE.md)

All CLI commands/flags need Docusaurus documentation in website/docs/cli/commands/ with specific structure: frontmatter, Intro component, Screengrab component, Usage section, Arguments/Flags using

/
, and Examples section

Files:

  • website/docs/cli/commands/workflow.mdx
**/*_test.go

📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)

**/*_test.go: Every new feature must include comprehensive unit tests targeting >80% code coverage for all packages
Use table-driven tests for testing multiple scenarios in Go
Include integration tests for command flows and test CLI end-to-end when possible with test fixtures

**/*_test.go: Prefer unit tests with mocks over integration tests; use interfaces + dependency injection for testability; generate mocks with go.uber.org/mock/mockgen; use table-driven tests; target >80% coverage
Test behavior, not implementation; never test stub functions; avoid tautological tests; make code testable via DI; no coverage theater; remove always-skipped tests; use errors.Is() for error checking

Files:

  • pkg/workflow/step/executor_test.go
  • pkg/workflow/step/output_handlers_test.go
🧠 Learnings (56)
📓 Common learnings
Learnt from: milldr
Repo: cloudposse/atmos PR: 1229
File: internal/exec/workflow_test.go:0-0
Timestamp: 2025-06-02T14:12:02.710Z
Learning: In the atmos codebase, workflow error handling was refactored to use `PrintErrorMarkdown` followed by returning specific error variables (like `ErrWorkflowNoSteps`, `ErrInvalidFromStep`, `ErrInvalidWorkflowStepType`, `ErrWorkflowStepFailed`) instead of `PrintErrorMarkdownAndExit`. This pattern allows proper error testing without the function terminating the process with `os.Exit`, enabling unit tests to assert on error conditions while maintaining excellent user-facing error formatting.
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: internal/exec/workflow_utils.go:0-0
Timestamp: 2025-12-13T06:10:25.156Z
Learning: Atmos workflows: In internal/exec/workflow_utils.go ExecuteWorkflow, non-identity steps intentionally use baseWorkflowEnv, which is constructed from the parent environment with PATH modifications for the toolchain. Avoid appending os.Environ() again; prefer documenting this behavior and testing that standard environment variables are preserved.
📚 Learning: 2025-06-02T14:12:02.710Z
Learnt from: milldr
Repo: cloudposse/atmos PR: 1229
File: internal/exec/workflow_test.go:0-0
Timestamp: 2025-06-02T14:12:02.710Z
Learning: In the atmos codebase, workflow error handling was refactored to use `PrintErrorMarkdown` followed by returning specific error variables (like `ErrWorkflowNoSteps`, `ErrInvalidFromStep`, `ErrInvalidWorkflowStepType`, `ErrWorkflowStepFailed`) instead of `PrintErrorMarkdownAndExit`. This pattern allows proper error testing without the function terminating the process with `os.Exit`, enabling unit tests to assert on error conditions while maintaining excellent user-facing error formatting.

Applied to files:

  • examples/demo-interactive-workflows/stacks/workflows/interactive.yaml
  • pkg/workflow/step/executor_test.go
  • pkg/workflow/step/choose.go
  • pkg/workflow/step/linebreak.go
  • errors/errors.go
  • examples/demo-interactive-workflows/README.md
  • internal/exec/workflow_utils.go
  • pkg/workflow/step/atmos.go
  • pkg/workflow/step/log.go
  • pkg/workflow/step/output_mode.go
  • pkg/workflow/step/handler_base.go
  • pkg/workflow/step/output_handlers_test.go
  • pkg/workflow/step/executor.go
  • pkg/schema/workflow.go
📚 Learning: 2025-12-13T06:10:25.156Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: internal/exec/workflow_utils.go:0-0
Timestamp: 2025-12-13T06:10:25.156Z
Learning: Atmos workflows: In internal/exec/workflow_utils.go ExecuteWorkflow, non-identity steps intentionally use baseWorkflowEnv, which is constructed from the parent environment with PATH modifications for the toolchain. Avoid appending os.Environ() again; prefer documenting this behavior and testing that standard environment variables are preserved.

Applied to files:

  • examples/demo-interactive-workflows/stacks/workflows/interactive.yaml
  • pkg/workflow/step/spin.go
  • pkg/workflow/step/executor_test.go
  • errors/errors.go
  • examples/demo-interactive-workflows/README.md
  • internal/exec/workflow_utils.go
  • pkg/workflow/step/atmos.go
  • pkg/workflow/step/output_mode.go
  • pkg/workflow/step/handler_base.go
  • pkg/workflow/step/executor.go
  • pkg/schema/workflow.go
📚 Learning: 2025-10-11T19:12:38.832Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1599
File: tests/snapshots/TestCLICommands_atmos_workflow_invalid_step_type.stderr.golden:0-0
Timestamp: 2025-10-11T19:12:38.832Z
Learning: Usage Examples sections in error output are appropriate for command usage errors (incorrect syntax, missing arguments, invalid flags) but not for configuration validation errors (malformed workflow files, invalid settings in atmos.yaml). Configuration errors should focus on explaining what's wrong with the config, not command usage patterns.

Applied to files:

  • website/docs/cli/commands/workflow.mdx
  • examples/demo-interactive-workflows/README.md
📚 Learning: 2025-04-23T15:02:50.246Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1202
File: pkg/utils/yaml_func_exec.go:104-104
Timestamp: 2025-04-23T15:02:50.246Z
Learning: In the Atmos codebase, direct calls to `os.Getenv` should be avoided. Instead, use `viper.BindEnv` for environment variable access. This provides a consistent approach to configuration management across the codebase.

Applied to files:

  • pkg/workflow/step/spin.go
📚 Learning: 2025-12-13T04:37:25.223Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: cmd/root.go:0-0
Timestamp: 2025-12-13T04:37:25.223Z
Learning: In Atmos cmd/root.go Execute(), after cfg.InitCliConfig, we must call both toolchainCmd.SetAtmosConfig(&atmosConfig) and toolchain.SetAtmosConfig(&atmosConfig) so the CLI wrapper and the toolchain package receive configuration; missing either can cause nil-pointer panics in toolchain path resolution.

Applied to files:

  • pkg/workflow/step/spin.go
  • pkg/workflow/step/atmos.go
📚 Learning: 2025-11-11T03:47:45.878Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/add_test.go:67-77
Timestamp: 2025-11-11T03:47:45.878Z
Learning: In the cloudposse/atmos codebase, tests should prefer t.Setenv for environment variable setup/teardown instead of os.Setenv/Unsetenv to ensure test-scoped isolation.

Applied to files:

  • pkg/workflow/step/spin.go
📚 Learning: 2025-01-09T22:37:01.004Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 914
File: cmd/terraform_commands.go:260-265
Timestamp: 2025-01-09T22:37:01.004Z
Learning: In the terraform commands implementation (cmd/terraform_commands.go), the direct use of `os.Args[2:]` for argument handling is intentionally preserved to avoid extensive refactoring. While it could be improved to use cobra's argument parsing, such changes should be handled in a dedicated PR to maintain focus and minimize risk.

Applied to files:

  • pkg/workflow/step/spin.go
📚 Learning: 2025-08-29T20:57:35.423Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1433
File: cmd/theme_list.go:33-36
Timestamp: 2025-08-29T20:57:35.423Z
Learning: In the Atmos codebase, avoid using viper.SetEnvPrefix("ATMOS") with viper.AutomaticEnv() because canonical environment variable names are not exclusive to Atmos and could cause conflicts. Instead, use selective environment variable binding through the setEnv function in pkg/config/load.go with bindEnv(v, "config.key", "ENV_VAR_NAME") for specific environment variables.

Applied to files:

  • pkg/workflow/step/spin.go
📚 Learning: 2025-11-11T03:47:59.576Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/which_test.go:166-223
Timestamp: 2025-11-11T03:47:59.576Z
Learning: In the cloudposse/atmos repo, tests that manipulate environment variables should use testing.T.Setenv for automatic setup/teardown instead of os.Setenv/Unsetenv.

Applied to files:

  • pkg/workflow/step/spin.go
📚 Learning: 2025-12-16T18:20:55.630Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T18:20:55.630Z
Learning: Applies to **/*.go : Use colors from pkg/ui/theme/colors.go for all UI output

Applied to files:

  • pkg/ui/theme/styles.go
📚 Learning: 2024-10-31T19:25:41.298Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 727
File: internal/exec/terraform_clean.go:233-235
Timestamp: 2024-10-31T19:25:41.298Z
Learning: When specifying color values in functions like `confirmDeleteTerraformLocal` in `internal/exec/terraform_clean.go`, avoid hardcoding color values. Instead, use predefined color constants or allow customization through configuration settings to improve accessibility and user experience across different terminals and themes.

Applied to files:

  • pkg/ui/theme/styles.go
📚 Learning: 2025-11-08T19:56:18.660Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1697
File: internal/exec/oci_utils.go:0-0
Timestamp: 2025-11-08T19:56:18.660Z
Learning: In the Atmos codebase, when a function receives an `*schema.AtmosConfiguration` parameter, it should read configuration values from `atmosConfig.Settings` fields rather than using direct `os.Getenv()` or `viper.GetString()` calls. The Atmos pattern is: viper.BindEnv in cmd/root.go binds environment variables → Viper unmarshals into atmosConfig.Settings via mapstructure → business logic reads from the Settings struct. This provides centralized config management, respects precedence, and enables testability. Example: `atmosConfig.Settings.AtmosGithubToken` instead of `os.Getenv("ATMOS_GITHUB_TOKEN")` in functions like `getGHCRAuth` in internal/exec/oci_utils.go.

Applied to files:

  • pkg/ui/theme/styles.go
  • pkg/workflow/step/atmos.go
📚 Learning: 2025-07-05T20:59:02.914Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1363
File: internal/exec/template_utils.go:18-18
Timestamp: 2025-07-05T20:59:02.914Z
Learning: In the Atmos project, gomplate v4 is imported with a blank import (`_ "github.com/hairyhenderson/gomplate/v4"`) alongside v3 imports to resolve AWS SDK version conflicts. V3 uses older AWS SDK versions that conflict with newer AWS modules used by Atmos. A full migration to v4 requires extensive refactoring due to API changes and should be handled in a separate PR.

Applied to files:

  • pkg/ui/theme/styles.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*_test.go : Include integration tests for command flows and test CLI end-to-end when possible with test fixtures

Applied to files:

  • pkg/workflow/step/executor_test.go
  • pkg/workflow/step/output_handlers_test.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*_test.go : Every new feature must include comprehensive unit tests targeting >80% code coverage for all packages

Applied to files:

  • pkg/workflow/step/executor_test.go
  • pkg/workflow/step/output_handlers_test.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*_test.go : Use table-driven tests for testing multiple scenarios in Go

Applied to files:

  • pkg/workflow/step/executor_test.go
  • pkg/workflow/step/output_handlers_test.go
📚 Learning: 2025-12-16T18:20:55.630Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T18:20:55.630Z
Learning: Applies to **/*_test.go : Test behavior, not implementation; never test stub functions; avoid tautological tests; make code testable via DI; no coverage theater; remove always-skipped tests; use errors.Is() for error checking

Applied to files:

  • pkg/workflow/step/executor_test.go
  • pkg/workflow/step/output_handlers_test.go
📚 Learning: 2025-12-13T06:10:13.688Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: errors/errors.go:184-203
Timestamp: 2025-12-13T06:10:13.688Z
Learning: cloudposse/atmos: For toolchain work, duplicate/unused error sentinels in errors/errors.go should be cleaned up in a separate refactor PR and not block feature PRs; canonical toolchain sentinels live under toolchain/registry with re-exports in toolchain/errors.go.

Applied to files:

  • errors/errors.go
  • pkg/workflow/step/executor.go
📚 Learning: 2024-12-12T17:13:53.409Z
Learnt from: RoseSecurity
Repo: cloudposse/atmos PR: 848
File: pkg/utils/doc_utils.go:19-22
Timestamp: 2024-12-12T17:13:53.409Z
Learning: In `pkg/utils/doc_utils.go`, the `DisplayDocs` function uses the `PAGER` environment variable, which is intentionally user-configurable to allow users to specify custom pager commands that fit their workflow; adding validation to restrict it is not desired.

Applied to files:

  • pkg/workflow/step/pager.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to README.md : Update README.md with new commands and features

Applied to files:

  • examples/demo-interactive-workflows/README.md
📚 Learning: 2025-11-07T14:52:55.217Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1761
File: docs/prd/claude-agent-architecture.md:331-439
Timestamp: 2025-11-07T14:52:55.217Z
Learning: In the cloudposse/atmos repository, Claude agents are used as interactive tools, not in automated/headless CI/CD contexts. Agent documentation and patterns should assume synchronous human interaction.

Applied to files:

  • examples/demo-interactive-workflows/README.md
📚 Learning: 2025-10-11T19:11:58.965Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1599
File: internal/exec/terraform.go:0-0
Timestamp: 2025-10-11T19:11:58.965Z
Learning: For terraform apply interactivity checks in Atmos (internal/exec/terraform.go), use stdin TTY detection (e.g., `IsTTYSupportForStdin()` or checking `os.Stdin`) to determine if user prompts are possible. This is distinct from stdout/stderr TTY checks used for output display (like TUI rendering). User input requires stdin to be a TTY; output display requires stdout/stderr to be a TTY.

Applied to files:

  • examples/demo-interactive-workflows/README.md
📚 Learning: 2024-11-12T13:06:56.194Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 768
File: website/docs/cheatsheets/vendoring.mdx:70-70
Timestamp: 2024-11-12T13:06:56.194Z
Learning: In `atmos vendor pull --everything`, the `--everything` flag uses the TTY for TUI but is not interactive.

Applied to files:

  • examples/demo-interactive-workflows/README.md
📚 Learning: 2025-09-13T16:39:20.007Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1466
File: cmd/markdown/atmos_toolchain_aliases.md:2-4
Timestamp: 2025-09-13T16:39:20.007Z
Learning: In the cloudposse/atmos repository, CLI documentation files in cmd/markdown/ follow a specific format that uses " $ atmos command" (with leading space and dollar sign prompt) in code blocks. This is the established project convention and should not be changed to comply with standard markdownlint rules MD040 and MD014.

Applied to files:

  • examples/demo-interactive-workflows/README.md
📚 Learning: 2025-11-30T04:16:24.155Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1821
File: pkg/merge/deferred.go:34-48
Timestamp: 2025-11-30T04:16:24.155Z
Learning: In the cloudposse/atmos repository, the `defer perf.Track()` guideline applies to functions that perform meaningful work (I/O, computation, external calls), but explicitly excludes trivial accessors/mutators (e.g., simple getters, setters with single integer increments, string joins, or map appends) where the tracking overhead would exceed the actual method cost and provide no actionable performance data. Hot-path methods called in tight loops should especially avoid perf.Track() if they perform only trivial operations.

Applied to files:

  • pkg/workflow/step/filter.go
  • pkg/workflow/step/atmos.go
📚 Learning: 2025-11-30T04:16:01.899Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1821
File: pkg/merge/deferred.go:50-59
Timestamp: 2025-11-30T04:16:01.899Z
Learning: In the cloudposse/atmos repository, performance tracking with `defer perf.Track()` should NOT be added to trivial O(1) getter methods that only return field references or check map lengths (e.g., `GetDeferredValues()`, `HasDeferredValues()`). The guideline to add perf tracking to "all public functions" applies to functions that do meaningful work (I/O, computation, external calls), not to trivial accessors where the tracking overhead would exceed the operation time and pollute performance reports.

Applied to files:

  • pkg/workflow/step/filter.go
  • pkg/workflow/step/atmos.go
📚 Learning: 2025-12-16T18:20:55.630Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T18:20:55.630Z
Learning: Applies to **/*.go : Add `defer perf.Track(atmosConfig, "pkg.FuncName")()` + blank line to all public functions for performance tracking; use nil if no atmosConfig param

Applied to files:

  • pkg/workflow/step/filter.go
  • pkg/workflow/step/atmos.go
📚 Learning: 2025-11-09T19:06:58.470Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1752
File: pkg/profile/list/formatter_table.go:27-29
Timestamp: 2025-11-09T19:06:58.470Z
Learning: In the cloudposse/atmos repository, performance tracking with `defer perf.Track()` is enforced on all functions via linting, including high-frequency utility functions, formatters, and renderers. This is a repository-wide policy to maintain consistency and avoid making case-by-case judgment calls about which functions should have profiling.

Applied to files:

  • pkg/workflow/step/filter.go
  • pkg/workflow/step/atmos.go
📚 Learning: 2025-06-02T14:12:02.710Z
Learnt from: milldr
Repo: cloudposse/atmos PR: 1229
File: internal/exec/workflow_test.go:0-0
Timestamp: 2025-06-02T14:12:02.710Z
Learning: In the atmos codebase, workflow error handling was refactored to use `PrintErrorMarkdown` followed by returning the error instead of `PrintErrorMarkdownAndExit`. This pattern allows proper error testing without the function terminating the process with `os.Exit`, enabling unit tests to assert on error conditions.

Applied to files:

  • internal/exec/workflow_utils.go
  • pkg/workflow/step/executor.go
📚 Learning: 2024-12-07T16:16:13.038Z
Learnt from: Listener430
Repo: cloudposse/atmos PR: 825
File: internal/exec/helmfile_generate_varfile.go:28-31
Timestamp: 2024-12-07T16:16:13.038Z
Learning: In `internal/exec/helmfile_generate_varfile.go`, the `--help` command (`./atmos helmfile generate varfile --help`) works correctly without requiring stack configurations, and the only change needed was to make `ProcessCommandLineArgs` exportable by capitalizing its name.

Applied to files:

  • internal/exec/workflow_utils.go
  • pkg/workflow/step/atmos.go
📚 Learning: 2024-11-02T15:35:09.958Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 759
File: internal/exec/terraform.go:366-368
Timestamp: 2024-11-02T15:35:09.958Z
Learning: In `internal/exec/terraform.go`, the workspace cleaning code under both the general execution path and within the `case "init":` block is intentionally duplicated because the code execution paths are different. The `.terraform/environment` file should be deleted before executing `terraform init` in both scenarios to ensure a clean state.

Applied to files:

  • internal/exec/workflow_utils.go
📚 Learning: 2025-09-13T18:06:07.674Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1466
File: toolchain/list.go:39-42
Timestamp: 2025-09-13T18:06:07.674Z
Learning: In the cloudposse/atmos repository, for UI messages in the toolchain package, use utils.PrintfMessageToTUI instead of log.Error or fmt.Fprintln(os.Stderr, ...). Import pkg/utils with alias "u" to follow the established pattern.

Applied to files:

  • internal/exec/workflow_utils.go
📚 Learning: 2024-12-05T22:33:40.955Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 820
File: cmd/list_components.go:53-54
Timestamp: 2024-12-05T22:33:40.955Z
Learning: In the Atmos CLI Go codebase, using `u.LogErrorAndExit` within completion functions is acceptable because it logs the error and exits the command execution.

Applied to files:

  • internal/exec/workflow_utils.go
📚 Learning: 2025-02-03T06:00:11.419Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 959
File: cmd/describe_config.go:20-20
Timestamp: 2025-02-03T06:00:11.419Z
Learning: Commands should use `PrintErrorMarkdownAndExit` with empty title and suggestion (`"", err, ""`) for general error handling. Specific titles like "Invalid Usage" or "File Not Found" should only be used for validation or specific error scenarios.

Applied to files:

  • internal/exec/workflow_utils.go
📚 Learning: 2025-09-10T22:38:42.212Z
Learnt from: Benbentwo
Repo: cloudposse/atmos PR: 1475
File: pkg/auth/identities/aws/user.go:141-145
Timestamp: 2025-09-10T22:38:42.212Z
Learning: ErrWrappingFormat is correctly defined as "%w: %w" in the errors package and is used throughout the codebase to wrap two error types together. The usage fmt.Errorf(errUtils.ErrWrappingFormat, errUtils.ErrAuthAwsFileManagerFailed, err) is the correct pattern when both arguments are error types.

Applied to files:

  • internal/exec/workflow_utils.go
📚 Learning: 2025-09-10T22:38:42.212Z
Learnt from: Benbentwo
Repo: cloudposse/atmos PR: 1475
File: pkg/auth/identities/aws/user.go:141-145
Timestamp: 2025-09-10T22:38:42.212Z
Learning: The user confirmed that the errors package has an error string wrapping format, contradicting the previous learning about ErrWrappingFormat being invalid. The current usage of fmt.Errorf(errUtils.ErrWrappingFormat, errUtils.ErrAuthAwsFileManagerFailed, err) appears to be the correct pattern.

Applied to files:

  • internal/exec/workflow_utils.go
  • pkg/workflow/step/executor.go
📚 Learning: 2025-08-16T23:32:40.412Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1405
File: internal/exec/describe_dependents_test.go:455-456
Timestamp: 2025-08-16T23:32:40.412Z
Learning: In the cloudposse/atmos Go codebase, `InitCliConfig` returns a `schema.AtmosConfiguration` value (not a pointer), while `ExecuteDescribeDependents` expects a `*schema.AtmosConfiguration` pointer parameter. Therefore, when passing the result of `InitCliConfig` to `ExecuteDescribeDependents`, use `&atmosConfig` to pass the address of the value.

Applied to files:

  • pkg/workflow/step/atmos.go
📚 Learning: 2024-10-23T21:36:40.262Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 740
File: cmd/cmd_utils.go:340-359
Timestamp: 2024-10-23T21:36:40.262Z
Learning: In the Go codebase for Atmos, when reviewing functions like `checkAtmosConfig` in `cmd/cmd_utils.go`, avoid suggesting refactoring to return errors instead of calling `os.Exit` if such changes would significantly increase the scope due to the need to update multiple call sites.

Applied to files:

  • pkg/workflow/step/atmos.go
  • pkg/workflow/step/executor.go
📚 Learning: 2025-04-11T22:06:46.999Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1147
File: internal/exec/validate_schema.go:42-57
Timestamp: 2025-04-11T22:06:46.999Z
Learning: The "ExecuteAtmosValidateSchemaCmd" function in internal/exec/validate_schema.go has been reviewed and confirmed to have acceptable cognitive complexity despite static analysis warnings. The function uses a clean structure with only three if statements for error handling and delegates complex operations to helper methods.

Applied to files:

  • pkg/workflow/step/atmos.go
📚 Learning: 2024-11-13T21:37:07.852Z
Learnt from: Cerebrovinny
Repo: cloudposse/atmos PR: 764
File: internal/exec/describe_stacks.go:289-295
Timestamp: 2024-11-13T21:37:07.852Z
Learning: In the `internal/exec/describe_stacks.go` file of the `atmos` project written in Go, avoid extracting the stack name handling logic into a helper function within the `ExecuteDescribeStacks` method, even if the logic appears duplicated.

Applied to files:

  • pkg/workflow/step/atmos.go
📚 Learning: 2025-10-13T18:13:54.020Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1622
File: pkg/perf/perf.go:140-184
Timestamp: 2025-10-13T18:13:54.020Z
Learning: In pkg/perf/perf.go, the `trackWithSimpleStack` function intentionally skips ownership checks at call stack depth > 1 to avoid expensive `getGoroutineID()` calls on every nested function. This is a performance optimization for the common single-goroutine execution case (most Atmos commands), accepting the rare edge case of potential metric corruption if multi-goroutine execution occurs at depth > 1. The ~19× performance improvement justifies this trade-off.

Applied to files:

  • pkg/workflow/step/atmos.go
📚 Learning: 2024-12-07T16:19:01.683Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 825
File: internal/exec/terraform.go:30-30
Timestamp: 2024-12-07T16:19:01.683Z
Learning: In `internal/exec/terraform.go`, skipping stack validation when help flags are present is not necessary.

Applied to files:

  • pkg/workflow/step/atmos.go
📚 Learning: 2024-12-02T21:26:32.337Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 808
File: pkg/config/config.go:478-483
Timestamp: 2024-12-02T21:26:32.337Z
Learning: In the 'atmos' project, when reviewing Go code like `pkg/config/config.go`, avoid suggesting file size checks after downloading remote configs if such checks aren't implemented elsewhere in the codebase.

Applied to files:

  • pkg/workflow/step/atmos.go
📚 Learning: 2025-05-22T15:42:10.906Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1261
File: internal/exec/utils.go:639-640
Timestamp: 2025-05-22T15:42:10.906Z
Learning: In the Atmos codebase, when appending slices with `args := append(configAndStacksInfo.CliArgs, configAndStacksInfo.AdditionalArgsAndFlags...)`, it's intentional that the result is not stored back in the original slice. This pattern is used when the merged result serves a different purpose than the original slices, such as when creating a filtered version for component section assignments.

Applied to files:

  • pkg/workflow/step/atmos.go
📚 Learning: 2025-02-09T14:38:53.443Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 992
File: cmd/cmd_utils.go:0-0
Timestamp: 2025-02-09T14:38:53.443Z
Learning: Error handling for RegisterFlagCompletionFunc in AddStackCompletion is not required as the errors are non-critical for tab completion functionality.

Applied to files:

  • pkg/workflow/step/atmos.go
📚 Learning: 2025-10-10T23:51:36.597Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1599
File: internal/exec/terraform.go:394-402
Timestamp: 2025-10-10T23:51:36.597Z
Learning: In Atmos (internal/exec/terraform.go), when adding OpenTofu-specific flags like `--var-file` for `init`, do not gate them based on command name (e.g., checking if `info.Command == "tofu"` or `info.Command == "opentofu"`) because command names don't reliably indicate the actual binary being executed (symlinks, aliases). Instead, document the OpenTofu requirement in code comments and documentation, trusting users who enable the feature (e.g., `PassVars`) to ensure their terraform command points to an OpenTofu binary.

Applied to files:

  • pkg/workflow/step/atmos.go
📚 Learning: 2025-12-13T03:21:35.786Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1813
File: cmd/terraform/shell.go:28-73
Timestamp: 2025-12-13T03:21:35.786Z
Learning: In Atmos, when calling cfg.InitCliConfig, you must first populate the schema.ConfigAndStacksInfo struct with global flag values using flags.ParseGlobalFlags(cmd, v) rather than passing an empty struct. The LoadConfig function (pkg/config/load.go) reads config selection fields (AtmosConfigFilesFromArg, AtmosConfigDirsFromArg, BasePath, ProfilesFromArg) directly from the ConfigAndStacksInfo struct, NOT from Viper. Passing an empty struct causes config selection flags (--base-path, --config, --config-path, --profile) to be silently ignored. Correct pattern: parse flags → populate struct → call InitCliConfig. See cmd/terraform/plan_diff.go for reference implementation.

Applied to files:

  • pkg/workflow/step/atmos.go
📚 Learning: 2025-04-04T02:03:23.676Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1185
File: internal/exec/yaml_func_store.go:26-26
Timestamp: 2025-04-04T02:03:23.676Z
Learning: The Atmos codebase currently uses `log.Fatal` for error handling in multiple places. The maintainers are aware this isn't an ideal pattern (should only be used in main() or init() functions) and plan to address it comprehensively in a separate PR. CodeRabbit should not flag these issues or push for immediate changes until that refactoring is complete.

Applied to files:

  • pkg/workflow/step/atmos.go
  • pkg/workflow/step/executor.go
📚 Learning: 2025-01-09T22:27:25.538Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 914
File: cmd/validate_stacks.go:20-23
Timestamp: 2025-01-09T22:27:25.538Z
Learning: The validate commands in Atmos can have different help handling implementations. Specifically, validate_component.go and validate_stacks.go are designed to handle help requests differently, with validate_stacks.go including positional argument checks while validate_component.go does not.

Applied to files:

  • pkg/workflow/step/atmos.go
📚 Learning: 2025-02-06T13:38:07.216Z
Learnt from: Listener430
Repo: cloudposse/atmos PR: 984
File: internal/exec/copy_glob.go:0-0
Timestamp: 2025-02-06T13:38:07.216Z
Learning: The `u.LogTrace` function in the `cloudposse/atmos` repository accepts `atmosConfig` as its first parameter, followed by the message string.

Applied to files:

  • pkg/workflow/step/log.go
📚 Learning: 2025-12-16T18:20:55.630Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T18:20:55.630Z
Learning: Separate I/O (streams) from UI (formatting): use I/O Layer (pkg/io/) for stream access and UI Layer (pkg/ui/) for formatting; use data.Write/Writeln/WriteJSON/WriteYAML for pipeable output to stdout and ui.Write/Success/Error/Warning/Info for human messages to stderr

Applied to files:

  • pkg/workflow/step/output_mode.go
📚 Learning: 2025-12-16T18:20:55.630Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T18:20:55.630Z
Learning: Applies to **/*.go : All errors MUST be wrapped using static errors defined in errors/errors.go; use errors.Join for combining multiple errors; use fmt.Errorf with %w for adding string context; use error builder for complex errors; use errors.Is() for error checking; NEVER use dynamic errors directly

Applied to files:

  • pkg/workflow/step/executor.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*.go : Follow Go's error handling idioms: use meaningful error messages, wrap errors with context using `fmt.Errorf("context: %w", err)`, and consider using custom error types for domain-specific errors

Applied to files:

  • pkg/workflow/step/executor.go
📚 Learning: 2024-10-12T18:38:28.458Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 715
File: pkg/logger/logger.go:21-32
Timestamp: 2024-10-12T18:38:28.458Z
Learning: In Go code, avoid prefixing struct names with 'I' (e.g., `IError`) as it can be confusing, suggesting an interface. Use descriptive names for structs to improve code clarity.

Applied to files:

  • pkg/workflow/step/executor.go
📚 Learning: 2025-04-04T02:03:21.906Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1185
File: internal/exec/yaml_func_store.go:71-72
Timestamp: 2025-04-04T02:03:21.906Z
Learning: The codebase currently uses `log.Fatal` for error handling in library functions, which terminates the program. There is a plan to refactor this approach in a separate PR to improve API design by returning error messages instead of terminating execution.

Applied to files:

  • pkg/workflow/step/executor.go
🧬 Code graph analysis (10)
pkg/workflow/step/choose.go (4)
pkg/workflow/step/handler_base.go (2)
  • BaseHandler (13-17)
  • NewBaseHandler (20-26)
pkg/workflow/step/registry.go (1)
  • Register (42-46)
pkg/schema/workflow.go (1)
  • WorkflowStep (16-75)
errors/errors.go (2)
  • ErrStepOptionsRequired (448-448)
  • ErrUserAborted (555-555)
pkg/workflow/step/linebreak.go (4)
pkg/workflow/step/handler_base.go (2)
  • BaseHandler (13-17)
  • NewBaseHandler (20-26)
pkg/workflow/step/types.go (2)
  • CategoryUI (12-12)
  • NewStepResult (46-51)
pkg/schema/workflow.go (1)
  • WorkflowStep (16-75)
pkg/workflow/step/variables.go (1)
  • Variables (12-17)
pkg/workflow/step/file.go (5)
pkg/workflow/step/registry.go (1)
  • Register (42-46)
pkg/workflow/step/types.go (3)
  • CategoryInteractive (8-8)
  • StepResult (32-43)
  • NewStepResult (46-51)
pkg/schema/workflow.go (1)
  • WorkflowStep (16-75)
pkg/workflow/step/variables.go (1)
  • Variables (12-17)
errors/errors.go (2)
  • ErrStepNoFilesFound (452-452)
  • ErrUserAborted (555-555)
internal/exec/workflow_utils.go (4)
pkg/workflow/step/executor.go (3)
  • IsExtendedStepType (134-149)
  • StepExecutor (13-16)
  • NewStepExecutor (19-23)
errors/errors.go (1)
  • ErrInvalidWorkflowStepType (441-441)
errors/exit_code.go (1)
  • WithExitCode (40-48)
pkg/schema/workflow.go (2)
  • WorkflowStep (16-75)
  • WorkflowDefinition (78-88)
pkg/workflow/step/atmos.go (4)
pkg/workflow/step/handler_base.go (2)
  • BaseHandler (13-17)
  • NewBaseHandler (20-26)
pkg/workflow/step/registry.go (1)
  • Register (42-46)
pkg/workflow/step/variables.go (1)
  • Variables (12-17)
pkg/workflow/step/output_mode.go (2)
  • GetOutputMode (175-188)
  • GetViewportConfig (191-203)
pkg/workflow/step/log.go (5)
pkg/workflow/step/handler_base.go (2)
  • BaseHandler (13-17)
  • NewBaseHandler (20-26)
pkg/workflow/step/types.go (2)
  • CategoryOutput (10-10)
  • NewStepResult (46-51)
pkg/schema/workflow.go (1)
  • WorkflowStep (16-75)
pkg/workflow/step/variables.go (1)
  • Variables (12-17)
pkg/logger/log.go (9)
  • Level (203-203)
  • TraceLevel (211-211)
  • Trace (14-16)
  • DebugLevel (213-213)
  • Debug (24-26)
  • WarnLevel (217-217)
  • Warn (44-46)
  • ErrorLevel (219-219)
  • InfoLevel (215-215)
pkg/workflow/step/handler_base.go (5)
pkg/workflow/step/types.go (1)
  • StepCategory (4-4)
pkg/schema/workflow.go (1)
  • WorkflowStep (16-75)
errors/builder.go (1)
  • Build (24-37)
errors/errors.go (2)
  • ErrStepTTYRequired (454-454)
  • ErrStepFieldRequired (453-453)
pkg/workflow/step/variables.go (1)
  • Variables (12-17)
pkg/workflow/step/output_handlers_test.go (3)
pkg/workflow/step/types.go (2)
  • StepCategory (4-4)
  • CategoryOutput (10-10)
pkg/workflow/step/registry.go (2)
  • Get (49-54)
  • ListByCategory (68-77)
pkg/schema/workflow.go (1)
  • WorkflowStep (16-75)
pkg/workflow/step/executor.go (5)
pkg/workflow/step/variables.go (2)
  • Variables (12-17)
  • NewVariables (20-28)
pkg/schema/workflow.go (2)
  • WorkflowDefinition (78-88)
  • WorkflowStep (16-75)
pkg/workflow/step/types.go (2)
  • StepResult (32-43)
  • StepCategory (4-4)
pkg/workflow/step/registry.go (3)
  • Get (49-54)
  • StepHandler (13-28)
  • ListByCategory (68-77)
errors/errors.go (1)
  • ErrUnknownStepType (447-447)
pkg/schema/workflow.go (1)
pkg/schema/schema.go (1)
  • RetryConfig (784-792)
🪛 LanguageTool
examples/demo-interactive-workflows/README.md

[typographical] ~118-~118: To join two clauses or introduce examples, consider using an em dash.
Context: ...Variables - {{ .steps.<name>.value }} - Primary value from step - `{{ .steps.<na...

(DASH_RULE)


[grammar] ~145-~145: Please add a punctuation mark at the end of paragraph.
Context: ... - Use environment variables instead of prompts

(PUNCTUATION_PARAGRAPH_END)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Acceptance Tests (macos)
  • GitHub Check: Acceptance Tests (windows)
  • GitHub Check: Acceptance Tests (linux)
  • GitHub Check: Summary

Copy link
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 (3)
pkg/workflow/step/toast.go (1)

27-52: Solid implementation with clear level handling.

The case-insensitive level matching and sensible Info default provide good UX. The explicit handling of "warn" as an alias for "warning" is thoughtful.

Optional: Make empty string handling explicit

For clarity, you could explicitly handle the empty string case:

 	// Display based on level (default to info).
 	switch strings.ToLower(step.Level) {
 	case "success":
 		err = ui.Success(content)
 	case "warning", "warn":
 		err = ui.Warning(content)
 	case "error":
 		err = ui.Error(content)
-	default:
-		// Default to info for "", "info", or any other value.
+	case "", "info":
 		err = ui.Info(content)
+	default:
+		// Treat unknown levels as info.
+		err = ui.Info(content)
 	}

This makes the intent clearer that both empty and "info" are treated identically.

examples/demo-interactive-workflows/README.md (1)

125-145: Good coverage of output modes and CI considerations.

The output mode examples and CI guidance are practical.

Optional: Add missing period for consistency

Line 145 is missing a closing period:

 - Use `--dry-run` to preview workflows
 - Set default values in configuration
-- Use environment variables instead of prompts
+- Use environment variables instead of prompts.
pkg/workflow/step/executor_test.go (1)

33-41: Consider testing SetWorkflow behavior.

The test only verifies no panic occurs when calling SetWorkflow. Consider either testing that the workflow is actually stored/accessible or removing this test case if there's genuinely no observable behavior to verify.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5a30c93 and aa405ec.

📒 Files selected for processing (6)
  • examples/demo-interactive-workflows/README.md (1 hunks)
  • examples/demo-interactive-workflows/stacks/workflows/interactive.yaml (1 hunks)
  • pkg/workflow/step/executor_test.go (1 hunks)
  • pkg/workflow/step/pager.go (1 hunks)
  • pkg/workflow/step/toast.go (1 hunks)
  • pkg/workflow/step/ui_handlers_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/workflow/step/ui_handlers_test.go
  • examples/demo-interactive-workflows/stacks/workflows/interactive.yaml
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)

**/*.go: Use Viper for managing configuration, environment variables, and flags in CLI commands
Use interfaces for external dependencies to facilitate mocking and consider using testify/mock for creating mock implementations
All code must pass golangci-lint checks
Follow Go's error handling idioms: use meaningful error messages, wrap errors with context using fmt.Errorf("context: %w", err), and consider using custom error types for domain-specific errors
Follow standard Go coding style: use gofmt and goimports to format code, prefer short descriptive variable names, use kebab-case for command-line flags, and snake_case for environment variables
Document all exported functions, types, and methods following Go's documentation conventions
Document complex logic with inline comments in Go code
Support configuration via files, environment variables, and flags following the precedence order: flags > environment variables > config file > defaults
Provide clear error messages to users, include troubleshooting hints when appropriate, and log detailed errors for debugging

**/*.go: NEVER use fmt.Fprintf(os.Stdout/Stderr) or fmt.Println(); use data.* or ui.* functions instead
All comments must end with periods (enforced by godot linter)
Organize imports in three groups separated by blank lines, sorted alphabetically: 1) Go stdlib, 2) 3rd-party (NOT cloudposse/atmos), 3) Atmos packages; maintain aliases: cfg, log, u, errUtils
Add defer perf.Track(atmosConfig, "pkg.FuncName")() + blank line to all public functions for performance tracking; use nil if no atmosConfig param
All errors MUST be wrapped using static errors defined in errors/errors.go; use errors.Join for combining multiple errors; use fmt.Errorf with %w for adding string context; use error builder for complex errors; use errors.Is() for error checking; NEVER use dynamic errors directly
Use go.uber.org/mock/mockgen with //go:generate directives for mock generation; never create manual mocks
Keep files small...

Files:

  • pkg/workflow/step/executor_test.go
  • pkg/workflow/step/toast.go
  • pkg/workflow/step/pager.go
**/*_test.go

📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)

**/*_test.go: Every new feature must include comprehensive unit tests targeting >80% code coverage for all packages
Use table-driven tests for testing multiple scenarios in Go
Include integration tests for command flows and test CLI end-to-end when possible with test fixtures

**/*_test.go: Prefer unit tests with mocks over integration tests; use interfaces + dependency injection for testability; generate mocks with go.uber.org/mock/mockgen; use table-driven tests; target >80% coverage
Test behavior, not implementation; never test stub functions; avoid tautological tests; make code testable via DI; no coverage theater; remove always-skipped tests; use errors.Is() for error checking

Files:

  • pkg/workflow/step/executor_test.go
🧠 Learnings (13)
📓 Common learnings
Learnt from: milldr
Repo: cloudposse/atmos PR: 1229
File: internal/exec/workflow_test.go:0-0
Timestamp: 2025-06-02T14:12:02.710Z
Learning: In the atmos codebase, workflow error handling was refactored to use `PrintErrorMarkdown` followed by returning specific error variables (like `ErrWorkflowNoSteps`, `ErrInvalidFromStep`, `ErrInvalidWorkflowStepType`, `ErrWorkflowStepFailed`) instead of `PrintErrorMarkdownAndExit`. This pattern allows proper error testing without the function terminating the process with `os.Exit`, enabling unit tests to assert on error conditions while maintaining excellent user-facing error formatting.
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: internal/exec/workflow_utils.go:0-0
Timestamp: 2025-12-13T06:10:25.156Z
Learning: Atmos workflows: In internal/exec/workflow_utils.go ExecuteWorkflow, non-identity steps intentionally use baseWorkflowEnv, which is constructed from the parent environment with PATH modifications for the toolchain. Avoid appending os.Environ() again; prefer documenting this behavior and testing that standard environment variables are preserved.
📚 Learning: 2025-12-13T06:10:25.156Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: internal/exec/workflow_utils.go:0-0
Timestamp: 2025-12-13T06:10:25.156Z
Learning: Atmos workflows: In internal/exec/workflow_utils.go ExecuteWorkflow, non-identity steps intentionally use baseWorkflowEnv, which is constructed from the parent environment with PATH modifications for the toolchain. Avoid appending os.Environ() again; prefer documenting this behavior and testing that standard environment variables are preserved.

Applied to files:

  • pkg/workflow/step/executor_test.go
  • examples/demo-interactive-workflows/README.md
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*_test.go : Include integration tests for command flows and test CLI end-to-end when possible with test fixtures

Applied to files:

  • pkg/workflow/step/executor_test.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*_test.go : Every new feature must include comprehensive unit tests targeting >80% code coverage for all packages

Applied to files:

  • pkg/workflow/step/executor_test.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*_test.go : Use table-driven tests for testing multiple scenarios in Go

Applied to files:

  • pkg/workflow/step/executor_test.go
📚 Learning: 2025-12-16T18:20:55.630Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T18:20:55.630Z
Learning: Applies to **/*_test.go : Test behavior, not implementation; never test stub functions; avoid tautological tests; make code testable via DI; no coverage theater; remove always-skipped tests; use errors.Is() for error checking

Applied to files:

  • pkg/workflow/step/executor_test.go
📚 Learning: 2025-06-02T14:12:02.710Z
Learnt from: milldr
Repo: cloudposse/atmos PR: 1229
File: internal/exec/workflow_test.go:0-0
Timestamp: 2025-06-02T14:12:02.710Z
Learning: In the atmos codebase, workflow error handling was refactored to use `PrintErrorMarkdown` followed by returning specific error variables (like `ErrWorkflowNoSteps`, `ErrInvalidFromStep`, `ErrInvalidWorkflowStepType`, `ErrWorkflowStepFailed`) instead of `PrintErrorMarkdownAndExit`. This pattern allows proper error testing without the function terminating the process with `os.Exit`, enabling unit tests to assert on error conditions while maintaining excellent user-facing error formatting.

Applied to files:

  • pkg/workflow/step/executor_test.go
  • pkg/workflow/step/pager.go
  • examples/demo-interactive-workflows/README.md
📚 Learning: 2024-12-12T17:13:53.409Z
Learnt from: RoseSecurity
Repo: cloudposse/atmos PR: 848
File: pkg/utils/doc_utils.go:19-22
Timestamp: 2024-12-12T17:13:53.409Z
Learning: In `pkg/utils/doc_utils.go`, the `DisplayDocs` function uses the `PAGER` environment variable, which is intentionally user-configurable to allow users to specify custom pager commands that fit their workflow; adding validation to restrict it is not desired.

Applied to files:

  • pkg/workflow/step/pager.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to README.md : Update README.md with new commands and features

Applied to files:

  • examples/demo-interactive-workflows/README.md
📚 Learning: 2025-11-07T14:52:55.217Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1761
File: docs/prd/claude-agent-architecture.md:331-439
Timestamp: 2025-11-07T14:52:55.217Z
Learning: In the cloudposse/atmos repository, Claude agents are used as interactive tools, not in automated/headless CI/CD contexts. Agent documentation and patterns should assume synchronous human interaction.

Applied to files:

  • examples/demo-interactive-workflows/README.md
📚 Learning: 2025-10-11T19:11:58.965Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1599
File: internal/exec/terraform.go:0-0
Timestamp: 2025-10-11T19:11:58.965Z
Learning: For terraform apply interactivity checks in Atmos (internal/exec/terraform.go), use stdin TTY detection (e.g., `IsTTYSupportForStdin()` or checking `os.Stdin`) to determine if user prompts are possible. This is distinct from stdout/stderr TTY checks used for output display (like TUI rendering). User input requires stdin to be a TTY; output display requires stdout/stderr to be a TTY.

Applied to files:

  • examples/demo-interactive-workflows/README.md
📚 Learning: 2024-11-12T13:06:56.194Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 768
File: website/docs/cheatsheets/vendoring.mdx:70-70
Timestamp: 2024-11-12T13:06:56.194Z
Learning: In `atmos vendor pull --everything`, the `--everything` flag uses the TTY for TUI but is not interactive.

Applied to files:

  • examples/demo-interactive-workflows/README.md
📚 Learning: 2025-09-13T16:39:20.007Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1466
File: cmd/markdown/atmos_toolchain_aliases.md:2-4
Timestamp: 2025-09-13T16:39:20.007Z
Learning: In the cloudposse/atmos repository, CLI documentation files in cmd/markdown/ follow a specific format that uses " $ atmos command" (with leading space and dollar sign prompt) in code blocks. This is the established project convention and should not be changed to comply with standard markdownlint rules MD040 and MD014.

Applied to files:

  • examples/demo-interactive-workflows/README.md
🧬 Code graph analysis (3)
pkg/workflow/step/executor_test.go (5)
pkg/workflow/step/executor.go (6)
  • NewStepExecutor (19-23)
  • NewStepExecutorWithVars (26-30)
  • IsExtendedStepType (134-149)
  • ValidateStep (152-167)
  • ValidateWorkflow (186-204)
  • ListTypes (170-183)
pkg/workflow/step/variables.go (2)
  • Variables (12-17)
  • NewVariables (20-28)
pkg/workflow/step/types.go (1)
  • NewStepResult (46-51)
pkg/schema/workflow.go (2)
  • WorkflowDefinition (78-88)
  • WorkflowStep (16-75)
pkg/workflow/step/registry.go (1)
  • Get (49-54)
pkg/workflow/step/toast.go (5)
pkg/workflow/step/handler_base.go (2)
  • BaseHandler (13-17)
  • NewBaseHandler (20-26)
pkg/workflow/step/types.go (1)
  • CategoryUI (12-12)
pkg/schema/workflow.go (1)
  • WorkflowStep (16-75)
pkg/workflow/step/variables.go (1)
  • Variables (12-17)
pkg/ui/formatter.go (2)
  • Success (193-200)
  • Warning (237-244)
pkg/workflow/step/pager.go (6)
pkg/workflow/step/registry.go (1)
  • Register (42-46)
pkg/workflow/step/types.go (2)
  • StepResult (32-43)
  • NewStepResult (46-51)
pkg/schema/workflow.go (1)
  • WorkflowStep (16-75)
pkg/pager/pager.go (1)
  • NewWithAtmosConfig (27-31)
pkg/ui/theme/converter.go (1)
  • GetGlamourStyleForTheme (255-263)
pkg/ui/theme/styles.go (1)
  • DefaultThemeName (11-11)
🪛 LanguageTool
examples/demo-interactive-workflows/README.md

[typographical] ~118-~118: To join two clauses or introduce examples, consider using an em dash.
Context: ...Variables - {{ .steps.<name>.value }} - Primary value from step - `{{ .steps.<na...

(DASH_RULE)


[grammar] ~145-~145: Please add a punctuation mark at the end of paragraph.
Context: ... - Use environment variables instead of prompts

(PUNCTUATION_PARAGRAPH_END)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Acceptance Tests (linux)
  • GitHub Check: Acceptance Tests (macos)
  • GitHub Check: Acceptance Tests (windows)
  • GitHub Check: Summary
🔇 Additional comments (15)
pkg/workflow/step/toast.go (2)

11-20: LGTM—clean handler registration.

The ToastHandler correctly registers as CategoryUI with requiresTTY=false, making toast notifications available in both interactive and non-interactive contexts. The pattern aligns well with other UI step handlers.


22-25: Validation is appropriate.

Correctly enforces the content requirement using the base validation helper.

pkg/workflow/step/pager.go (5)

17-26: Proper registration as output category.

Correctly positioned under CategoryOutput with appropriate TTY requirements for a pager step.


28-35: Flexible validation for content sources.

Correctly allows either inline content or file path, covering both use cases.


37-63: Clean execution flow with graceful degradation.

The pipeline (load → render → display) is well-structured. Silently falling back to unrendered content when markdown rendering fails (line 46) provides good UX—the pager still works even if styling fails.


65-83: Well-structured content loading.

Cleanly handles both inline and file-based content sources. Returning the resolved path enables accurate markdown detection downstream.


101-140: Helper methods are well-implemented.

The markdown detection logic (explicit flag → extension auto-detect) provides good flexibility. Theme-aware rendering integrates nicely with Atmos styling. File reading has appropriate error context.

examples/demo-interactive-workflows/README.md (3)

1-23: Strong introduction with clear prerequisites.

The TTY requirement callout is helpful for users. Quick start commands provide immediate value.


25-80: Excellent workflow documentation.

The progression from simple (deploy) to complex (full-deploy) helps users learn incrementally. Each workflow's purpose is immediately clear.


81-124: Comprehensive reference for step types and variables.

The step types table provides quick lookup. The variable passing examples with Go templates are clear and practical. The available template variables list serves as a good reference.

pkg/workflow/step/executor_test.go (5)

89-125: Excellent table-driven test coverage.

Comprehensive coverage of legacy vs extended step types with clear test cases. Good use of table-driven pattern per coding guidelines.


172-219: Good workflow validation coverage.

The test cases cover validation scenarios comprehensively, including valid workflows, invalid steps, empty workflows, and generated step names. The approach of checking error count is appropriate for ValidateWorkflow's []error return type.


221-239: LGTM.

Test provides good coverage of the ListTypes functionality, verifying all categories and spot-checking expected types in each.


243-315: Strong integration test coverage for variable passing.

These tests effectively verify the variable passing mechanism between steps, covering value access, metadata, environment variables, multi-value scenarios, and state flags. The approach of simulating results is appropriate for testing variable resolution independently of handler execution.


330-346: LGTM.

Clean test of the fluent API pattern, verifying all chainable methods work correctly.

@mergify
Copy link

mergify bot commented Dec 22, 2025

💥 This pull request now has conflicts. Could you fix it @osterman? 🙏

@mergify mergify bot added conflict This PR has conflicts and removed conflict This PR has conflicts labels Dec 22, 2025
coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 22, 2025
osterman and others added 8 commits January 15, 2026 16:33
- Remove 2025-12-20-interactive-workflow-steps.mdx (superseded by
  2026-01-03-custom-commands-step-types.mdx which covers both
  workflows and custom commands)
- Add changelog references to workflow milestones in roadmap

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- log.go: Check fieldsLen >= maxCapacity/2 before multiplication
- spin.go: Extract safeEnvCapacity helper to clamp values before adding,
  reducing cyclomatic complexity while ensuring sum can't overflow

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Using Go's builtin min() makes the overflow protection clearer
and more analyzable by static analysis tools like CodeQL.

- log.go: fieldsLen := min(len(step.Fields), maxCapacity/2)
- spin.go: Use min() to clamp both inputs before addition

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tests in spin_test.go used Unix-specific commands (pwd, echo $VAR) that
fail on Windows. Added platform-specific helper functions that use
appropriate commands for each platform:
- pwd -> cd on Windows
- echo $VAR -> echo %VAR% on Windows
- /tmp -> C:\Windows\Temp on Windows

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CodeQL flagged that len(environ)+6 could overflow on 32-bit systems.
Applied the same min() clamping pattern used in other overflow fixes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1. Simplified overflow protection in env.go to avoid any arithmetic that
   CodeQL might flag - just clamp environLen directly without addition.

2. Removed duplicate containsStr function from spin_test.go and use
   strings.Contains from stdlib instead (CodeRabbit suggestion).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CodeQL couldn't infer that min(x, maxCapacity/2) * 2 is safe because it
doesn't trace the relationship between clamped values and subsequent
multiplication. Changed to an explicit if-guard pattern that CodeQL
recognizes:

  if fieldsLen > maxFields {
      fieldsLen = maxFields
  }

This pattern is explicitly documented in CodeQL's recommendation for
guarding against allocation size overflow.

Fixes: https://github.com/cloudposse/atmos/security/code-scanning/5211

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CodeQL couldn't trace that an if-guard on fieldsLen bounds the subsequent
multiplication. Moving the guarded multiplication into a helper function
with an explicit if-guard pattern allows CodeQL to recognize the safety:

  if fieldsLen > maxCapacity/2 {
      return maxCapacity
  }
  return fieldsLen * 2

This pattern explicitly shows that fieldsLen*2 only executes when
fieldsLen <= maxCapacity/2, which CodeQL can verify is safe.

Fixes: https://github.com/cloudposse/atmos/security/code-scanning/5211

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@mergify
Copy link

mergify bot commented Jan 16, 2026

💥 This pull request now has conflicts. Could you fix it @osterman? 🙏

osterman and others added 5 commits January 15, 2026 19:21
…flows

Resolved conflict in website/docs/workflows/workflows.mdx: kept both imports

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Rename examples/demo-interactive-workflows to examples/interactive-workflows
- Add EmbedExample for interactive-workflows to workflow documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Rename demo-custom-command to custom-commands
- Add .atmos.d/interactive.yaml with interactive step types (choose, confirm, input, etc.)
- Add .atmos.d/advanced.yaml with boolean flags, env vars, and working directory examples
- Update README.md with comprehensive documentation
- Add EmbedExample component to commands.mdx documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Rename demo-custom-command to custom-commands in TAGS_MAP and DOCS_MAP
- Add interactive-workflows mapping (renamed from demo-interactive-workflows)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The examples/demo-custom-command directory was renamed to
examples/custom-commands but test files were not updated,
causing CI failures on all platforms.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
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: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/runner/runner.go (1)

199-208: Step variables won’t persist across RunAll without initialization.

When opts.StepVars is nil, each extended step gets a fresh Variables instance, so .steps.<name> values won’t carry across tasks in RunAll. Consider initializing StepVars once in RunAll when absent so sequential steps can share outputs.

✅ Suggested fix
 func RunAll(ctx context.Context, tasks Tasks, runner CommandRunner, opts Options) error {
 	defer perf.Track(opts.AtmosConfig, "runner.RunAll")()
 
+	if opts.StepVars == nil {
+		opts.StepVars = step.NewVariables()
+	}
+
 	for i, task := range tasks {
 		if err := Run(ctx, &task, runner, opts); err != nil {
 			return fmt.Errorf("task %d (%s) failed: %w", i, taskName(&task, i), err)
 		}
 	}
 	return nil
 }
🤖 Fix all issues with AI agents
In `@cmd/cmd_utils.go`:
- Around line 804-816: The current code creates a fresh StepExecutor for each
extended custom command step (stepPkg.NewStepExecutor), which drops previously
set step outputs and prevents cross-step templating; instead instantiate a
single StepExecutor before the loop and reuse it for each step, calling
executor.SetEnv(...) to update env vars per step and then executor.Execute(...,
&workflowStep) so .steps.<name> variables persist across steps; ensure you do
not recreate the executor inside the per-step block and only update
environment/step-specific state before Execute.

In `@examples/interactive-workflows/README.md`:
- Around line 12-14: The README uses the wrong directory name in the quick-start
CD command; change the cd target from "examples/demo-interactive-workflows" to
"examples/interactive-workflows" so the instructions match the actual repository
path and the subsequent "Run the main deployment workflow" step will work.

In `@internal/exec/workflow_utils.go`:
- Around line 332-346: Call showRenderer.RenderHeaderIfNeeded(...) once before
iterating over steps instead of inside the for loop: move the invocation of
RenderHeaderIfNeeded(workflowDefinition, workflow, flags) out of the loop that
iterates "for stepIdx, step := range steps" so the header is rendered a single
time (also remove the in-loop call); keep all other step rendering logic
(showCfg, FormatStepLabel, progressRenderer.Update/RenderWithLabel, ui.Writeln)
unchanged — mirror the pattern used in the fix applied in
pkg/workflow/executor.go.

In `@pkg/runner/step/confirm.go`:
- Around line 76-81: The confirmation error handling should wrap failures with
the workflow-step sentinel so errors.Is works; replace the fmt.Errorf wrapper in
the form.Run() error branch with a wrapped sentinel (e.g.,
errUtils.ErrWorkflowStepFailed) joined with the original error and include the
step name for context—e.g., use errors.Join(errUtils.ErrWorkflowStepFailed, err)
and fmt.Errorf("step '%s': confirmation failed: %w", step.Name,
errors.Join(errUtils.ErrWorkflowStepFailed, err)); keep the huh.ErrUserAborted /
errUtils.ErrUserAborted branch unchanged.

In `@pkg/runner/step/filter.go`:
- Around line 62-70: The logic sets limit=1 before checking step.Multiple, which
forces multi-select into a single choice; change the branching so that when
step.Multiple is true and step.Limit <= 0 you set limit = len(options) (or
otherwise omit/capture an "unlimited" sentinel) and then call
h.executeMultiSelect(step.Name, prompt, options, limit); keep the single-select
default of limit=1 only in the non-multiple branch. Update the code paths around
step.Limit, step.Multiple, and the h.executeMultiSelect call accordingly.

In `@pkg/runner/step/format.go`:
- Around line 33-41: Wrap errors returned by ResolveContent and data.Writeln
with the corresponding static errors from errors/errors.go before returning;
replace the bare returns in the block handling content, e.g., when calling
h.ResolveContent(ctx, step, vars) return a wrapped error like fmt.Errorf("%w:
%v", errors.ErrResolveContent, err) (use the appropriate static error name from
errors/errors.go), and similarly wrap the error from data.Writeln with a static
error such as errors.ErrDataWrite using fmt.Errorf("%w: %v",
errors.ErrDataWrite, err) so consumers can classify errors consistently.

In `@pkg/runner/step/handler_base.go`:
- Around line 89-127: Replace the current fmt.Errorf wrappers in ResolveContent,
ResolvePrompt and ResolveCommand so they wrap with the static sentinel error
from errors/errors.go (e.g., ErrTemplateResolution) while still attaching the
original error and context; import the errors package if needed and change each
error return to something like fmt.Errorf("step '%s': failed to resolve
<content|prompt|command> template: %v: %w", step.Name, err,
errors.ErrTemplateResolution) so errors.Is(...) will match the sentinel and the
underlying error message is preserved.
- Around line 52-70: CheckTTY currently only checks stdout TTY; update
BaseHandler.CheckTTY to also verify stdin is a TTY when h.requiresTTY is true by
calling terminal.New().IsTTY(terminal.Stdin) (and optionally combine with
terminal.Stdout check for rendering). If stdin is not a TTY, return the same
errUtils.Build(errUtils.ErrStepTTYRequired) enriched with step context and an
explanation/hints indicating interactive prompts require a TTY on stdin (suggest
--dry-run, defaults, or env vars), so the error prevents CI from passing the
check but hanging on input.

In `@pkg/runner/step/join.go`:
- Around line 44-59: The error returns in the join step should be wrapped with
the static workflow step error: replace the raw fmt.Errorf and direct return of
err in the options/content branches so they wrap the underlying error with
errors.ErrWorkflowStepFailed (e.g., return nil, fmt.Errorf("%w: step '%s' failed
to resolve option %d: %v", errors.ErrWorkflowStepFailed, step.Name, i, err) for
the vars.Resolve error and similarly wrap the h.ResolveContent error when
resolving step.Content); update the branches that call vars.Resolve and
h.ResolveContent to return wrapped errors using ErrWorkflowStepFailed while
preserving original context.

In `@pkg/runner/step/log_test.go`:
- Around line 16-41: Update TestLogHandler_GetLogLevel to assert the actual
mapped level equals the test case's expected string instead of only checking
non-nil; call getLogLevel(tt.level), convert its output to the comparable string
(e.g., via level.String() or the appropriate method on the returned type) and
use assert.Equal(t, tt.expected, actual) inside the t.Run subtest for each case
(reference: TestLogHandler_GetLogLevel and getLogLevel).

In `@pkg/runner/step/output_mode.go`:
- Around line 282-295: StreamingOutputWriter.Write currently prefixes the entire
incoming chunk once, causing multi-line chunks to miss per-line prefixes and
causing double prefixes when partial lines span writes; change Write (in type
StreamingOutputWriter) to perform line-buffering: append p to an internal buffer
(w.output or a dedicated leftover buffer), scan for newline-terminated lines,
for each complete line write to w.target using fmt.Fprintf(w.target, "%s %s",
w.prefix, line) (preserving newline), and keep any trailing partial line in the
buffer for the next Write; ensure this logic runs under the existing w.mu lock
and still writes to w.output as needed, and handle the case where w.target is
nil by only buffering without prefixing.

In `@pkg/runner/step/sleep.go`:
- Around line 35-55: Wrap the raw errors returned in this block so callers can
errors.Is against the static errors in errors/errors.go: when
vars.Resolve(step.Timeout) returns err, return a wrapped error combining the
appropriate static error from errors/errors.go and the original err (use
fmt.Errorf("%w: %v", errors.SomeStaticErr, err) or similar), do the same for the
time.ParseDuration(resolved) error, and when returning ctx.Err() wrap it with
the appropriate static cancellation error before returning; update the error
returns in the vars.Resolve, time.ParseDuration, and ctx.Done() branches
accordingly so callers can match via errors.Is.

In `@pkg/runner/step/spin.go`:
- Around line 113-125: The createExecContext method currently treats invalid or
non-positive step.Timeout values as “no timeout”; change it to validate and
surface invalid timeout errors instead of silently ignoring them by returning a
(context.Context, context.CancelFunc, error) from createExecContext on
SpinHandler and propagate that error up to Execute (or perform validation in
Validate). Introduce a package-level sentinel error (e.g., ErrInvalidTimeout)
and use it when time.ParseDuration fails or timeout <= 0, returning the sentinel
(wrapped with context) so callers can handle/log it; update Execute (or
Validate) to check for and return that error to surface configuration problems.
Ensure existing callers are updated to handle the new error return and preserve
context/CancelFunc semantics when a valid timeout is returned.

In `@pkg/runner/step/style.go`:
- Around line 134-143: The comments above the Padding and Margin handling
incorrectly list supported formats as only 1, 2, or 4 values; update both
comments to state that parseSpacing also supports the 3-value form (top,
horizontal, bottom) so they read something like: supports "1", "1 2", "1 2 1",
"1 2 1 2" formats; this applies to the blocks that reference
step.Padding/parseSpacing and step.Margin/parseSpacing (also update the similar
comment at the later occurrence around the code that handles spacing at lines
212-214).
- Around line 39-72: Wrap the raw errors returned by ResolveContent and
ui.Writeln with the static sentinel errors defined in errors/errors.go: replace
the direct returns of err after calling h.ResolveContent(...) and
ui.Writeln(output) with wrapped errors using fmt.Errorf and %w (e.g.,
fmt.Errorf("%w: %v", errors.ErrResolveContent, err) for the ResolveContent error
and fmt.Errorf("%w: %v", errors.ErrWriteOutput, err) for the ui.Writeln error),
ensuring you import fmt and the errors package and keep the original error as
the wrapped cause.

In `@pkg/runner/step/variables_test.go`:
- Around line 324-337: The test TestVariablesLoadOSEnv relies on OS envs and can
fail on Windows; make it deterministic by setting required env values with
t.Setenv before calling NewVariables() (e.g., set "HOME" and optionally "PATH"
to known non-empty strings), then call NewVariables() and assert those entries
exist; update references in the test to use t.Setenv so env changes are scoped
to the test and avoid OS-dependent failures in NewVariables()/LoadOSEnv.
🧹 Nitpick comments (11)
examples/custom-commands/.atmos.d/interactive.yaml (1)

40-43: Consider: Warning displays regardless of selected environment.

The prod_warning toast fires for every environment, not just prod. If the workflow system supports conditional steps, you might want to gate this on {{ .steps.env.value }} equaling prod. If this is intentional for demo purposes, no action needed.

pkg/runner/step/shell_test.go (1)

210-245: Consider removing or adjusting the nil error test case.

The test case for nil error (lines 216-220) is skipped at runtime (lines 237-239). Either remove this case since it's not being tested, or actually test the behavior when getExitCode receives nil (if that's a valid scenario to handle).

Option 1: Remove the skipped case
 func TestGetExitCode(t *testing.T) {
 	tests := []struct {
 		name     string
 		err      error
 		expected int
 	}{
-		{
-			name:     "nil error",
-			err:      nil,
-			expected: 1, // Default for non-exit errors.
-		},
 		{
 			name: "exec.ExitError with code 2",
Option 2: Actually test it
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			if tt.err == nil {
-				// Test that nil is handled - though in practice getExitCode is only called on error.
-				return
-			}
 			result := getExitCode(tt.err)
 			assert.Equal(t, tt.expected, result)
 		})
 	}
pkg/runner/step/env_test.go (1)

16-124: Prefer table-driven subtests for EnvHandler scenarios.

Multiple subtests repeat the same setup/assertion patterns; a table-driven structure would reduce duplication and align with the repo’s test conventions.
As per coding guidelines, consider converting the subtests to a table-driven format.

pkg/schema/task.go (1)

250-256: Silent timeout parse failure could mask configuration errors.

When time.ParseDuration fails, the code silently continues with a zero timeout. This hides malformed duration strings like "30x" or "invalid" from users, potentially causing unexpected behavior where no timeout is applied.

Consider logging a warning or returning an error to surface invalid configurations.

🔧 Proposed approach
 func TaskFromWorkflowStep(step *WorkflowStep) Task {
 	// Parse timeout string to time.Duration.
 	var timeout time.Duration
 	if step.Timeout != "" {
-		if parsed, err := time.ParseDuration(step.Timeout); err == nil {
+		parsed, err := time.ParseDuration(step.Timeout)
+		if err != nil {
+			// Log warning for invalid timeout format.
+			// Consider: log.Warn("invalid timeout format", "step", step.Name, "timeout", step.Timeout, "error", err)
+		} else {
 			timeout = parsed
 		}
 	}
pkg/runner/step/executor.go (1)

150-168: Consider making legacyTypes a package-level variable.

The legacyTypes map is recreated on every call to IsExtendedStepType. Moving it to package level would avoid repeated allocations, though the current approach is fine for clarity and the function isn't on a hot path.

🔧 Optional optimization
+// legacyTypes identifies step types handled by the legacy executor.
+var legacyTypes = map[string]bool{
+	"atmos": true,
+	"shell": true,
+	"":      true,
+}
+
 func IsExtendedStepType(stepType string) bool {
 	defer perf.Track(nil, "step.IsExtendedStepType")()
 
-	// Legacy types that should be handled by existing executor.
-	legacyTypes := map[string]bool{
-		"atmos": true,
-		"shell": true,
-		"":      true, // Empty defaults to shell in legacy.
-	}
-
 	if legacyTypes[stepType] {
 		return false
 	}
pkg/workflow/progress.go (1)

36-64: Good defensive initialization with nil return.

Returning nil when progress is disabled or TTY unavailable is clean. All methods handle nil receivers gracefully. Consider adding perf.Track to NewProgressRenderer per coding guidelines.

♻️ Optional: Add performance tracking
 func NewProgressRenderer(workflow *schema.WorkflowDefinition, totalSteps int) *ProgressRenderer {
+	defer perf.Track(nil, "workflow.NewProgressRenderer")()
+
 	// Check if progress is enabled at workflow level.
 	showCfg := step.GetShowConfig(nil, workflow)
internal/exec/workflow_utils.go (1)

522-538: Duplicated flags map builder.

This is similar to buildFlagsMap in pkg/workflow/executor.go. Consider extracting to a shared location if this pattern grows, but acceptable for now given the different call sites and parameter styles.

pkg/runner/step/clear_test.go (1)

37-62: Consider consolidating redundant subtests.

Both "executes successfully" (line 37) and "returns empty result value" (line 51) verify the same behavior: result.Value is empty. They could be a single test, or the second could test something distinct like metadata or a different scenario.

💡 Suggested consolidation
 	t.Run("executes successfully", func(t *testing.T) {
 		step := &schema.WorkflowStep{
 			Name: "test",
 			Type: "clear",
 		}
 		vars := NewVariables()
 		ctx := context.Background()

 		result, err := handler.Execute(ctx, step, vars)
 		require.NoError(t, err)
 		assert.NotNil(t, result)
-		assert.Empty(t, result.Value)
-	})
-
-	t.Run("returns empty result value", func(t *testing.T) {
-		step := &schema.WorkflowStep{
-			Name: "test",
-			Type: "clear",
-		}
-		vars := NewVariables()
-		ctx := context.Background()
-
-		result, err := handler.Execute(ctx, step, vars)
-		require.NoError(t, err)
 		assert.Equal(t, "", result.Value)
 	})
pkg/runner/step/alert.go (1)

35-38: Consider terminal instance reuse.

Creating terminal.New() on each Execute call may be fine, but if this step runs frequently in a workflow, you might consider caching or reusing the terminal instance. Low priority since Alert steps are typically infrequent.

pkg/runner/step/pager.go (1)

125-140: Consider adding word wrap option for terminal width.

The glamour renderer could benefit from glamour.WithWordWrap(width) for better terminal display, but this is optional.

Optional: Add word wrap support
 func (h *PagerHandler) renderMarkdown(content string) (string, error) {
 	var opts []glamour.TermRendererOption
 
 	// Use theme-aware styles.
 	if glamourStyle, err := theme.GetGlamourStyleForTheme(theme.DefaultThemeName); err == nil {
 		opts = append(opts, glamour.WithStylesFromJSONBytes(glamourStyle))
 	}
+	opts = append(opts, glamour.WithWordWrap(80))
 
 	renderer, err := glamour.NewTermRenderer(opts...)
pkg/runner/step/atmos.go (1)

154-167: Consider extracting exit code metadata key.

"exit_code" is used here and in shell.go. The shell.go file defines exitCodeMetadata constant—consider reusing it here for consistency.

Optional: Use shared constant
 func (h *AtmosHandler) buildAtmosResult(stdout, stderr string, err error) *StepResult {
 	if err != nil {
 		return NewStepResult(stdout).
 			WithError(stderr).
 			WithMetadata("stdout", stdout).
 			WithMetadata("stderr", stderr).
-			WithMetadata("exit_code", getExitCode(err))
+			WithMetadata(exitCodeMetadata, getExitCode(err))
 	}
 	return NewStepResult(strings.TrimSpace(stdout)).
 		WithMetadata("stdout", stdout).
 		WithMetadata("stderr", stderr).
-		WithMetadata("exit_code", 0)
+		WithMetadata(exitCodeMetadata, 0)
 }

osterman and others added 4 commits January 16, 2026 21:27
- Reuse single StepExecutor across custom command steps for variable persistence
- Fix directory path in interactive-workflows README
- Move header rendering before step loop in workflow_utils.go
- Wrap confirmation errors with static ErrWorkflowStepFailed sentinel
- Fix multi-select limit to allow unlimited when Multiple=true and Limit<=0
- Wrap format.go errors with static error sentinels
- Validate stdin TTY (not just stdout) for interactive steps
- Use static error sentinels for template resolution failures
- Wrap join.go errors with static error sentinels
- Assert actual log level values in log_test.go
- Implement per-line prefixing in StreamingOutputWriter
- Wrap sleep.go duration/context errors with static sentinels
- Surface invalid timeout values instead of silently ignoring
- Wrap style.go errors with static error sentinels
- Update spacing docs to include 3-value form
- Make LoadOSEnv test portable with t.Setenv

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@mergify
Copy link

mergify bot commented Jan 17, 2026

💥 This pull request now has conflicts. Could you fix it @osterman? 🙏

Copy link
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: 3

🤖 Fix all issues with AI agents
In `@cmd/cmd_utils.go`:
- Around line 801-816: Extended steps aren't inheriting the current working
directory; before calling executor.Execute with workflowStep, set its working
directory when empty (e.g., if workflowStep.WorkingDirectory == "" then
workflowStep.WorkingDirectory = workDir) so that extended-step handlers (e.g.,
spin) run from the same directory as shell/atmos branches; add this assignment
right after updating workflowStep.Command and before calling executor.Execute.

In `@internal/exec/workflow_utils.go`:
- Around line 335-346: The current branch only writes the formatted label when
stepPkg.ShowCount(showCfg) is true, which hides labels when show.count is false
even though FormatStepLabel already includes the count handling; change the
logic so that when progressRenderer.IsEnabled() is false you always print the
label (use ui.Writeln(label)) regardless of ShowCount. Update the block around
progressRenderer.Update, RenderWithLabel, and the else branch that currently
checks stepPkg.ShowCount(showCfg) so it simply calls ui.Writeln(label) when
progress is disabled; keep GetShowConfig, FormatStepLabel, RenderWithLabel, and
Update calls unchanged.

In `@pkg/runner/step/output_mode.go`:
- Around line 58-91: The executeViewport function currently returns immediately
on cmd.Run() error and drops stdout/stderr; change the error path to preserve
and emit output by invoking the existing fallback path: call
w.fallbackToLog(stdout.String(), stderr.String(), err) (or otherwise write the
captured stdout/stderr and return the original error) instead of returning
directly, so failures always surface diagnostics; update the error branch in
executeViewport to use that fallback and return its results.
♻️ Duplicate comments (1)
pkg/runner/step/variables_test.go (1)

324-340: Portability issue resolved.

Using t.Setenv ensures deterministic behavior across platforms. Good fix.

🧹 Nitpick comments (8)
pkg/runner/step/style.go (2)

73-77: Consider handling ui.Writeln error.

The error from ui.Writeln(output) is discarded. If writing fails (e.g., broken pipe), the step silently succeeds. A minor edge case, but worth noting.

Optional: propagate write error
-	ui.Writeln(output)
-
-	return NewStepResult(content), nil
+	if err := ui.Writeln(output); err != nil {
+		return nil, errUtils.Build(errUtils.ErrWriteToStream).
+			WithCause(err).
+			WithContext("step", step.Name).
+			Err()
+	}
+	return NewStepResult(content), nil

88-91: Hardcoded theme may ignore user configuration.

GetGlamourStyleForTheme(theme.DefaultThemeName) always uses "atmos" theme for markdown rendering, even if the user configured a different theme. Consider using the active theme.

Use active theme instead
-	if glamourStyle, err := theme.GetGlamourStyleForTheme(theme.DefaultThemeName); err == nil {
+	// Use the currently configured theme for markdown rendering.
+	themeName := theme.DefaultThemeName
+	if activeStyles := theme.GetCurrentStyles(); activeStyles != nil {
+		// GetCurrentStyles loads the active theme, so it's now cached.
+		// We could expose the theme name, but for now fall through to default.
+	}
+	if glamourStyle, err := theme.GetGlamourStyleForTheme(themeName); err == nil {

Alternatively, expose getActiveThemeName() as public or pass theme context.

pkg/workflow/show_renderer.go (1)

27-58: Add performance tracking to RenderHeaderIfNeeded.

Per coding guidelines, public methods doing meaningful work should include defer perf.Track(). This method performs UI rendering and configuration lookups.

Add perf tracking
+import (
+	"github.com/cloudposse/atmos/pkg/perf"
+	// ... existing imports
+)

 func (r *ShowRenderer) RenderHeaderIfNeeded(
 	workflow *schema.WorkflowDefinition,
 	workflowName string,
 	flags map[string]string,
 ) {
+	defer perf.Track(nil, "workflow.ShowRenderer.RenderHeaderIfNeeded")()
+
 	if r.headerDone {
 		return
 	}

Based on coding guidelines requiring perf tracking for all public functions.

pkg/runner/step/sleep.go (1)

58-68: Avoid timer leak on context cancellation.
time.After allocates a timer that can linger if ctx.Done() fires first. Prefer time.NewTimer with Stop() and drain.

♻️ Proposed fix
-	// Create a timer that respects context cancellation.
-	select {
-	case <-time.After(duration):
-		return NewStepResult(duration.String()), nil
-	case <-ctx.Done():
-		return nil, errUtils.Build(errUtils.ErrUserAborted).
-			WithCause(ctx.Err()).
-			WithContext("step", step.Name).
-			WithExplanation("Sleep interrupted by context cancellation").
-			Err()
-	}
+	// Create a timer that respects context cancellation.
+	timer := time.NewTimer(duration)
+	defer func() {
+		if !timer.Stop() {
+			select {
+			case <-timer.C:
+			default:
+			}
+		}
+	}()
+	select {
+	case <-timer.C:
+		return NewStepResult(duration.String()), nil
+	case <-ctx.Done():
+		return nil, errUtils.Build(errUtils.ErrUserAborted).
+			WithCause(ctx.Err()).
+			WithContext("step", step.Name).
+			WithExplanation("Sleep interrupted by context cancellation").
+			Err()
+	}
pkg/runner/step/ui_handlers_test.go (2)

16-40: Test covers core UI handlers but missing linebreak and sleep.

The registration test validates 7 handlers but linebreak and sleep are tested separately. Consider adding them to this table for completeness.

 		{"clear", CategoryUI},
 		{"env", CategoryUI},
 		{"exit", CategoryUI},
+		{"linebreak", CategoryUI},
+		{"sleep", CategoryUI},
 	}

277-286: Silent skip on execution failure may mask test issues.

When Execute fails, the test passes without verifying anything. Consider using t.Skip() to make it explicit that verification was skipped.

 			if err == nil {
 				assert.Equal(t, tt.expectedContent, result.Value)
+			} else {
+				t.Skipf("Skipping verification - execution failed in test environment: %v", err)
 			}
pkg/runner/step/alert.go (1)

35-37: Consider caching terminal instance if called frequently in this handler.

terminal.New() performs system calls to detect TTY and determine color support. While this pattern is consistent throughout the codebase and already performance tracked, you could optimize by caching the instance if Execute is called repeatedly (similar to the pattern in pkg/list/extract/metadata.go using sync.OnceValue). However, this is optional since the current approach ensures fresh terminal state detection on each call.

pkg/runner/step/spin_test.go (1)

356-457: Consider table-driven cases for RunCommand.
This block has many similar scenarios; a table-driven structure would reduce repetition and make additions easier. As per coding guidelines.

- Propagate working_directory to extended steps so handlers like spin
  run from the correct directory
- Always print step labels when progress is disabled, not just when
  show.count is true
- Fall back to log output when viewport commands fail to ensure
  diagnostic output is visible

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
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: 1

🤖 Fix all issues with AI agents
In `@pkg/runner/step/output_mode.go`:
- Around line 58-85: executeViewport currently ignores the per-step viewport
stored on OutputModeWriter (w.viewport) and always calls
pager.NewWithAtmosConfig(true), so per-step viewport settings don't apply;
update executeViewport to wire w.viewport into pager creation (e.g., replace
pager.NewWithAtmosConfig(true) with a pager constructor that consumes w.viewport
or pass relevant fields from w.viewport into the existing pager.New... call),
handle nil/default viewport safely, and keep the existing fallback behavior if
pager creation or Run fails; alternatively, if per-step viewport is not
intended, remove the viewport field from OutputModeWriter and any references to
it to avoid a misleading API.
🧹 Nitpick comments (1)
internal/exec/workflow_utils.go (1)

505-509: Missing perf.Track on public function.

Per coding guidelines, public functions in pkg, internal, and cmd should include performance tracking. Since this is a simple state reset, it qualifies as a trivial setter exception, but worth confirming intent.

Optional: Add perf.Track if desired
 // ResetStepExecutorState resets the step executor state.
 // This should be called at the start of a new workflow execution.
 func ResetStepExecutorState() {
+	defer perf.Track(nil, "exec.ResetStepExecutorState")()
+
 	stepExecutorState = nil
 }

Comment on lines +58 to +85
// executeViewport captures output and displays in pager.
func (w *OutputModeWriter) executeViewport(cmd *exec.Cmd) (string, string, error) {
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr

err := cmd.Run()
if err != nil {
// Fall back to log mode on error to ensure output is visible.
return w.fallbackToLog(stdout.String(), stderr.String(), err)
}

// Check if terminal is available for viewport.
term := terminal.New()
if !term.IsTTY(terminal.Stdout) {
// Fall back to log mode.
return w.fallbackToLog(stdout.String(), stderr.String(), nil)
}

// Display in pager.
content := stdout.String()
if stderr.Len() > 0 {
content += "\n--- stderr ---\n" + stderr.String()
}

p := pager.NewWithAtmosConfig(true)
if pagerErr := p.Run(w.stepName, content); pagerErr != nil {
// Pager failed, fall back to raw output.
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Viewport config is unused in viewport mode.

OutputModeWriter stores viewport *schema.ViewportConfig, but executeViewport always uses pager.NewWithAtmosConfig(true) and never references w.viewport, so per-step viewport settings won’t take effect. Please wire w.viewport into pager creation (or remove the field to avoid a misleading API).

🤖 Prompt for AI Agents
In `@pkg/runner/step/output_mode.go` around lines 58 - 85, executeViewport
currently ignores the per-step viewport stored on OutputModeWriter (w.viewport)
and always calls pager.NewWithAtmosConfig(true), so per-step viewport settings
don't apply; update executeViewport to wire w.viewport into pager creation
(e.g., replace pager.NewWithAtmosConfig(true) with a pager constructor that
consumes w.viewport or pass relevant fields from w.viewport into the existing
pager.New... call), handle nil/default viewport safely, and keep the existing
fallback behavior if pager creation or Run fails; alternatively, if per-step
viewport is not intended, remove the viewport field from OutputModeWriter and
any references to it to avoid a misleading API.

@mergify
Copy link

mergify bot commented Jan 21, 2026

💥 This pull request now has conflicts. Could you fix it @osterman? 🙏

osterman and others added 2 commits January 23, 2026 21:57
…flows

Resolved conflicts:
- pkg/ui/markdown/styles.go: Keep non-breaking space for H1 padding
- pkg/ui/theme/converter.go: Keep non-breaking space for H1 padding
- website/docs/cli/configuration/commands.mdx: Keep all imports
- website/plugins/file-browser/index.js: Merge all new example entries
Allow the pager to respect configurable max height/width instead of
always using full terminal dimensions. This enables workflow viewport
settings to control the pager display area.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mergify
Copy link

mergify bot commented Feb 27, 2026

💥 This pull request now has conflicts. Could you fix it @osterman? 🙏

…flows

Resolve merge conflicts in workflow_utils.go, workflow.go, and executor.go:
- Combine base env construction (main) with show/progress rendering (feature)
- Keep all interactive step fields (feature) with Command tag fix (main)
- Add Env fields to WorkflowStep and WorkflowDefinition (main) alongside
  Output, Viewport, and Show fields (feature)
- Keep renderStepCommand (feature) with env-aware prepareStepEnvironment (main)
- Update custom-gcl version to v2.10.1 for Go 1.26 compatibility
- Fix deprecated comment format in progress.go

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

minor New features that do not break anything size/xl Extra large size PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant