Skip to content

Add task-runner dependencies, freshness checks, and preconditions to custom commands and workflows - #2882

Merged
Andriy Knysh (aknysh) merged 28 commits into
mainfrom
osterman/task-runner-first-class-support
Aug 11, 2026
Merged

Andriy Knysh (aknysh) merged 28 commits into
mainfrom
osterman/task-runner-first-class-support

Conversation

@osterman

@osterman Erik Osterman (Cloud Posse) (osterman) commented Aug 5, 2026 •

Copy link
Copy Markdown
Member

what

  • Adds dependencies.commands/dependencies.workflows to custom commands and workflows: named, parameterized, concurrent-by-default dependency ordering across units, with automatic dedup of identical invocations.
  • Adds inputs/artifacts step fields: skip a step when its declared sources haven't changed since the last successful run (implicit when: checksum.changed), exposing checksum.changed/timestamp.changed/sources/artifacts as when: CEL facts.
  • Adds preconditions step field: skip a step when a required tool is already on PATH (implicit when: "!preconditions.success"), resolved via exec.LookPath — no shell involved. Pluralized (precondition → preconditions) to match the block-of-checks convention already used by inputs/artifacts/dependencies.
  • Adds continue: always step field, mirroring GitHub Actions' continue-on-error: a step's own failure is forgiven, later steps still run, overall exit status unaffected.
  • Fixes type: parallel/type: matrix steps silently failing in custom commands (only workflows supported them before).
  • Adds platforms via when: CEL facts (os/arch/platform), native per-command aliases:/internal:, and a values: constraint on flags/arguments with an interactive picker.
  • Fixes Windows-specific bugs in the new step types: shell child-process argument quoting for parallel/matrix, and verbatim CmdLine construction for cmd.exe /C.
  • Fixes a cluster of concurrency and correctness bugs surfaced during implementation and review: dependency-scheduling and freshness-check race conditions, hashfile collisions, non-atomic multi-line step output, diamond-dependency de-duplication, wrong-binary resolution in workflow command dependencies, and a UnitDependencies string-shorthand schema gap.
  • Remediates 3 Dependabot security alerts surfaced while this branch was open: js-yaml, mermaid, and a nanoid infinite-loop DoS (GHSA-2v37-7h3g-55p8 / CVE-2026-67213).
  • Relocates cmd/custom_command_dependency_adapter.go and cmd/custom_command_values.go into pkg/taskgraph/adapters and pkg/flags respectively, so this logic is unit-testable in isolation instead of coupled to cmd's live command registry.
  • Adds Docusaurus docs for every new field/fact and updates the JSON Schema (atmos/manifest, config/global, stacks/stack-config) accordingly.

why

Atmos workflows and custom commands already covered most of what a task runner needs, but a handful of real gaps kept teams running go-task alongside Atmos: no dependency ordering between named commands/workflows, no up-to-date checking, no continue-on-error, no precondition shortcut, and custom commands couldn't even use parallel/matrix steps — the exact recipe the project's own go-task migration guide recommends for concurrent dependents. This closes those gaps using the existing when:/CEL condition engine and scheduler rather than inventing a second mechanism.

references

  • Blog post: website/blog/2026-08-05-taskfile-convergence.mdx

…unner replacement

Closes the remaining gaps that kept teams running go-task alongside Atmos:

- Named cross-unit dependencies: dependencies.commands/dependencies.workflows on
  custom commands and workflows, with automatic dedup of identical invocations,
  parameterized invocations as distinct graph nodes, and concurrent-by-default
  execution via the existing scheduler.
- Freshness-based step skipping: inputs.sources/artifacts.paths skip a step when
  nothing has changed since its last successful run (implicit when: checksum.changed);
  precondition.tools skips a step when a required tool is already on PATH (implicit
  when: "!precondition.success"). Exposes checksum.changed/timestamp.changed/
  precondition.success as when: CEL facts, plus structured per-file records for
  custom comparisons.
- continue: always step field, mirroring GitHub Actions' continue-on-error: a
  step's own failure is forgiven, later steps still run, overall exit status
  unaffected.
- Fixed type: parallel/type: matrix steps silently failing in custom commands
  (only workflows supported them) -- the exact recipe the go-task migration
  guide recommended for concurrent dependents.
- platforms via when: CEL facts (os/arch/platform), native per-command
  aliases:/internal:, and values: constraint on flags/arguments with an
  interactive picker.

Relocates cmd/custom_command_dependency_adapter.go and cmd/custom_command_values.go
into pkg/taskgraph/adapters and pkg/flags respectively, so this logic is
unit-testable in isolation instead of coupled to cmd's live command registry.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@atmos-pro

atmos-pro Bot commented Aug 5, 2026 •

Copy link
Copy Markdown
Contributor

Tip

Atmos Pro  

No affected stacks workflow was detected for this pull request.
If this is expected, no action is needed.
Learn More. Ask AI.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

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
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

Custom commands and workflows now support dependency DAGs, freshness-aware conditions, failure continuation, parallel and matrix steps, aliases, internal visibility, platform facts, constrained values, and expanded validation.

Changes

Task-runner contracts and execution

Layer / File(s) Summary
Configuration and condition contracts
pkg/schema/..., pkg/condition/..., pkg/datafetcher/schema/..., errors/errors.go
Adds dependency, freshness, precondition, continuation, alias, platform, and constrained-value contracts.
Freshness computation and persistence
pkg/runner/freshness/..., pkg/hashfile/...
Adds glob resolution, checksum and timestamp facts, tool checks, structured file facts, hashing, and atomic state persistence.
Dependency graph execution
pkg/taskgraph/..., pkg/taskgraph/adapters/..., internal/exec/workflow_dependency_adapter.go
Adds graph construction, deduplication, concurrency, failure modes, command dispatch, workflow lookup, and subprocess execution.
Custom-command and workflow execution
cmd/cmd_utils.go, internal/exec/workflow_utils.go, internal/exec/custom_command_control_adapter.go
Integrates dependencies, freshness checks, continuation handling, parallel and matrix steps, dependency-aware errors, and platform metadata.
Validation and supporting changes
cmd/*_test.go, internal/exec/*_test.go, examples/task-runner-dependencies/..., pkg/process/..., website/docs/..., docs/fixes/...
Adds integration tests, fixtures, Windows shell handling, workflow loading tests, and documentation.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CustomCommand
  participant Taskgraph
  participant DependencyRunner
  participant WorkflowExecutor
  CustomCommand->>Taskgraph: Resolve command and workflow dependencies
  Taskgraph->>DependencyRunner: Execute deduplicated dependency nodes
  DependencyRunner->>WorkflowExecutor: Run workflow dependency steps
  WorkflowExecutor-->>Taskgraph: Return dependency result
  Taskgraph-->>CustomCommand: Apply failure mode
Loading

Possibly related PRs

Suggested reviewers: aknysh

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.48% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the PR's primary task-runner dependency, freshness, and precondition changes for custom commands and workflows.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch osterman/task-runner-first-class-support

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.

@osterman Erik Osterman (Cloud Posse) (osterman) added the minor New features that do not break anything label Aug 5, 2026
@github-actions github-actions Bot added the size/xl Extra large size PR label Aug 5, 2026
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@mergify

mergify Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

💥 This pull request now has conflicts. Could you fix it Erik Osterman (Cloud Posse) (@osterman)? 🙏

@mergify mergify Bot added the conflict This PR has conflicts label Aug 5, 2026
…first-class-support

# Conflicts:
#	cmd/cmd_utils.go
#	internal/exec/workflow_utils.go
#	pkg/condition/cel.go
#	pkg/condition/condition.go
#	pkg/datafetcher/schema/atmos/manifest/1.0.json
#	pkg/datafetcher/schema/config/global/1.0.json
#	pkg/runner/runner.go
#	pkg/workflow/executor.go
#	website/src/data/roadmap.js
@github-actions

github-actions Bot commented Aug 5, 2026 •

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues found.

Scanned Files

None

Comment thread pkg/taskgraph/schema.go Fixed
Comment thread pkg/taskgraph/schema.go Fixed
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

Resource Changes Found for bucket in test

Atmos CI

create

Plan: 4 to add, 0 to change, 0 to destroy.
To reproduce this locally, run:

atmos terraform plan bucket -s test

Create

+ aws_s3_bucket.checkov_target
+ aws_s3_bucket.this
+ aws_s3_bucket.trivy_target
+ aws_s3_bucket_public_access_block.trivy_target
Terraform Plan Summary
  # aws_s3_bucket.checkov_target will be created
  + resource "aws_s3_bucket" "checkov_target" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = "atmos-native-ci-e2e-checkov-test"
      + bucket_domain_name          = (known after apply)
      + bucket_prefix               = (known after apply)
      + bucket_regional_domain_name = (known after apply)
      + force_destroy               = false
      + hosted_zone_id              = (known after apply)
      + id                          = (known after apply)
      + object_lock_enabled         = (known after apply)
      + policy                      = (known after apply)
      + region                      = (known after apply)
      + request_payer               = (known after apply)
      + tags_all                    = (known after apply)
      + website_domain              = (known after apply)
      + website_endpoint            = (known after apply)

      + cors_rule (known after apply)

      + grant (known after apply)

      + lifecycle_rule (known after apply)

      + logging (known after apply)

      + object_lock_configuration (known after apply)

      + replication_configuration (known after apply)

      + server_side_encryption_configuration (known after apply)

      + versioning (known after apply)

      + website (known after apply)
    }

  # aws_s3_bucket.this will be created
  + resource "aws_s3_bucket" "this" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = "atmos-native-ci-e2e-test"
      + bucket_domain_name          = (known after apply)
      + bucket_prefix               = (known after apply)
      + bucket_regional_domain_name = (known after apply)
      + force_destroy               = false
      + hosted_zone_id              = (known after apply)
      + id                          = (known after apply)
      + object_lock_enabled         = (known after apply)
      + policy                      = (known after apply)
      + region                      = (known after apply)
      + request_payer               = (known after apply)
      + tags                        = {
          + "AtmosFixture" = "native-ci-e2e"
          + "Stage"        = "test"
        }
      + tags_all                    = {
          + "AtmosFixture" = "native-ci-e2e"
          + "Stage"        = "test"
        }
      + website_domain              = (known after apply)
      + website_endpoint            = (known after apply)

      + cors_rule (known after apply)

      + grant (known after apply)

      + lifecycle_rule (known after apply)

      + logging (known after apply)

      + object_lock_configuration (known after apply)

      + replication_configuration (known after apply)

      + server_side_encryption_configuration (known after apply)

      + versioning (known after apply)

      + website (known after apply)
    }

  # aws_s3_bucket.trivy_target will be created
  + resource "aws_s3_bucket" "trivy_target" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = "atmos-native-ci-e2e-trivy-test"
      + bucket_domain_name          = (known after apply)
      + bucket_prefix               = (known after apply)
      + bucket_regional_domain_name = (known after apply)
      + force_destroy               = false
      + hosted_zone_id              = (known after apply)
      + id                          = (known after apply)
      + object_lock_enabled         = (known after apply)
      + policy                      = (known after apply)
      + region                      = (known after apply)
      + request_payer               = (known after apply)
      + tags_all                    = (known after apply)
      + website_domain              = (known after apply)
      + website_endpoint            = (known after apply)

      + cors_rule (known after apply)

      + grant (known after apply)

      + lifecycle_rule (known after apply)

      + logging (known after apply)

      + object_lock_configuration (known after apply)

      + replication_configuration (known after apply)

      + server_side_encryption_configuration (known after apply)

      + versioning (known after apply)

      + website (known after apply)
    }

  # aws_s3_bucket_public_access_block.trivy_target will be created
  + resource "aws_s3_bucket_public_access_block" "trivy_target" {
      + block_public_acls       = true
      + block_public_policy     = true
      + bucket                  = (known after apply)
      + id                      = (known after apply)
      + ignore_public_acls      = true
      + restrict_public_buckets = true
    }

Plan: 4 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + bucket_name = "atmos-native-ci-e2e-test"

…erflow

Windows Acceptance Tests: unquoted backslash paths embedded in shell Command
strings get corrupted by mvdan/sh (pkg/utils/shell_utils.go parses commands
with bash syntax, which consumes unquoted backslashes as escapes). Apply
filepath.ToSlash() to every path used inside a shell Command string across
the freshness/dependency/precondition test suites; forward slashes are valid
path separators on Windows too. Also give freshness state Save() a uniquely
named temp file per write (os.CreateTemp) since pkg/cache.FileLock is a
documented no-op on Windows, so a fixed temp filename let concurrent writers
collide.

CodeQL: pkg/taskgraph.RefsFromDependencies allocated with
len(a)+len(b), which go/allocation-size-overflow flags as a potentially
overflowing sum; size the capacity hint to a single len() instead.
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 6, 2026
…ertions

TestCustomCommandIntegration_ParallelStepWithNeeds failed on Windows CI with
an exact-match assertion against shell-redirected file content: mvdan/sh's
`echo`+`>>` produced "first \r\nsecond \r\n" there instead of "first\nsecond\n".
Reproduced locally that the redirect itself correctly isolates fd1 from the
live-display writer (no leaked/duplicated output), so this is a shell/OS text
formatting difference Atmos doesn't control, not a functional bug.

Strengthen the shared splitNonEmptyLines test helper to trim each line
(handles \r and trailing whitespace) and switch this test's assertion to use
it, matching how sibling dependency tests already tolerate line content.
…encies and freshness checking

Found via field-testing the task-runner dependency/freshness feature; each is fixed with a
failing-first regression test:

- pkg/taskgraph/adapters/cobra_command.go: same-name dependency dispatches (e.g. the same
  command depended on twice with different flags) resolve to one shared *cobra.Command and now
  serialize per-target instead of racing on its mutable flags/context, and reset every
  non-overridden flag to its declared default before each dispatch instead of silently
  inheriting a prior dispatch's leftover value.
- cmd/cmd_utils.go + cobra_command.go: a step failure inside a dependency's own execution no
  longer hard-exits the whole process before taskgraph.Run's fail: mode handling
  (wait_all/fail_fast/best_effort) can see it -- failures now report through a dependency error
  sink instead.
- cmd/cmd_utils.go + internal/exec/workflow_utils.go: a step's freshness-referencing `when:`
  (timestamp.changed, structured sources/artifacts) no longer gets silently evaluated against an
  empty pre-check context, which always read false and skipped the whole command/workflow.
- internal/exec/workflow_dependency_adapter.go: workflow-depends-on-command subprocess dispatch
  now resolves its own binary path via os.Executable() instead of the bare "atmos" (PATH lookup),
  which could silently run an unrelated installed version instead of the active build.

examples/task-runner-dependencies/ is a new, durable fixture exercising all of the above end to
end.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…burst

writeLine locked/unlocked the shared writeMu per individual line rather than per flush. When one
Write() resolved into multiple lines (e.g. a \r-separated progress update followed later by its
completion), releasing the lock between them let a concurrently writing sibling node's entire
output interleave in the gap. Write/Flush now hold writeMu across every line one call flushes.

Fixes the CI failure in TestExecuteTerraformConcurrentHooksUseNodeWriters
(pkg/scheduler/adapters/terraform_test.go), the only real failure in the macOS acceptance-test
job's log. Also fixes a numbered-list indentation lint finding in the previous commit's fix doc.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (20)
cmd/list/aliases.go-28-35 (1)

28-35: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the AliasInfo.Type documentation.

Line 28 adds "custom", but the exported field comment at Line 48 lists only "built-in" and "configured". Include "custom" so the public contract matches the emitted values.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/list/aliases.go` around lines 28 - 35, The AliasInfo.Type documentation
currently omits the newly supported "custom" value; update its exported field
comment to list "custom" alongside "built-in" and "configured", without changing
the type behavior.

Source: Coding guidelines

pkg/flags/constrained.go-39-41 (1)

39-41: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Wrap picker errors with field context.

PromptForValue errors return unchanged. Wrap each error with the affected argument or flag name and the applicable static error so users can identify the failed prompt.

Also applies to: 81-83

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/flags/constrained.go` around lines 39 - 41, Update the error handling
around PromptForValue in the argument and flag prompt paths to wrap returned
errors with the affected arg.Name or flag name and the applicable static error
context. Preserve the existing early-return behavior while ensuring both
occurrences provide field-specific context.

Source: Coding guidelines

pkg/flags/constrained.go-46-46 (1)

46-46: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use an argument-specific validation error.

ValidateValue reports invalid input as flag --<name>. This call validates a positional argument, so an invalid env value is reported as an invalid flag. Return an argument-specific error that identifies the positional argument and its allowed values.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/flags/constrained.go` at line 46, Update the validation error handling in
the constrained argument path around ValidateValue so positional-argument
failures use an argument-specific error instead of the flag-oriented message.
Identify the argument by arg.Name and include arg.Values in the error, while
preserving the existing successful validation flow.
pkg/datafetcher/schema/atmos/config/1.0.json-6138-6170 (1)

6138-6170: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

One misplaced Go doc comment produced two wrong schema descriptions. The Inputs description opens with "Task represents a unit of work that can be executed. This type unifies workflow steps and custom command steps...", and the Task definition now has no description at all. Both follow from the same cause: in pkg/schema/task.go the Task doc comment sits directly above the Inputs declaration inside the grouped type (...) block, so the generator attached it to Inputs. Editors reading this schema show unrelated prose for inputs: and nothing for a task.

  • pkg/datafetcher/schema/atmos/config/1.0.json#L6138-L6170: no direct edit here. Move the Task doc comment in pkg/schema/task.go back above the Task declaration, then regenerate so this description contains only the Inputs prose starting at "Inputs declares a step's freshness inputs".
  • pkg/datafetcher/schema/atmos/config/1.0.json#L10925-L10925: after the same source fix and regeneration, confirm the Task definition regains its description.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/datafetcher/schema/atmos/config/1.0.json` around lines 6138 - 6170, Move
the Task doc comment in the grouped type declaration in pkg/schema/task.go
directly above Task rather than Inputs, then regenerate the schema. At
pkg/datafetcher/schema/atmos/config/1.0.json lines 6138-6170, verify Inputs
retains only its own description and requires no direct edit; at line 10925,
verify the regenerated Task definition has the Task description restored.
pkg/datafetcher/schema/atmos/manifest/1.0.json-2473-2475 (1)

2473-2475: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the duplicate dependencies key in workflow_manifest.

workflow_manifest.properties already declares dependencies with the same $ref at lines 2494-2496. Duplicate object members are not valid JSON hygiene, and most parsers keep only the last occurrence. Drop the new block and keep the existing one.

🧹 Proposed fix
             "stack": {
               "type": "string"
             },
-            "dependencies": {
-              "$ref": "`#/definitions/dependencies`"
-            },
             "steps": {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/datafetcher/schema/atmos/manifest/1.0.json` around lines 2473 - 2475,
Remove the duplicate dependencies property block from
workflow_manifest.properties, keeping the existing declaration that references
`#/definitions/dependencies` unchanged.

Source: Linters/SAST tools

pkg/datafetcher/schema/stacks/stack-config/1.0.json-2137-2139 (1)

2137-2139: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Constrain continue to the condition schema.

The new property has no type or $ref. The schema accepts invalid values such as continue: 42 or continue: {unexpected: true}.

Reference #/definitions/condition so editor validation and manifest validation match the runtime contract.

Proposed fix
 "continue": {
-  "description": "Condition that forgives this step's own failure so later steps still run and the overall status is unaffected (GitHub Actions' continue-on-error semantics). Evaluated after the step's own execution, against its own outcome, unlike 'when' which is evaluated before the step runs."
+  "description": "Condition that forgives this step's own failure so later steps still run and the overall status is unaffected (GitHub Actions' continue-on-error semantics). Evaluated after the step's own execution, against its own outcome, unlike 'when' which is evaluated before the step runs.",
+  "allOf": [
+    { "$ref": "`#/definitions/condition`" }
+  ]
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/datafetcher/schema/stacks/stack-config/1.0.json` around lines 2137 -
2139, Update the continue property definition in the stack configuration schema
to reference `#/definitions/condition`, replacing the unconstrained definition
while preserving its existing description.
pkg/schema/dependencies.go-171-179 (1)

171-179: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Reject non-string YAML scalar dependencies.

yaml.ScalarNode includes booleans, numbers, and null values. For example, commands: [true] becomes a dependency named "true" during direct workflow decoding. The Viper path rejects the same value because decodeUnitDependencyItem accepts only string.

Check node.Tag before assigning node.Value. Add tests for boolean, numeric, and null entries.

Proposed fix
 case yaml.ScalarNode:
+	if node.Tag != "!!str" {
+		return fmt.Errorf("%w at index %d: got %s scalar (expected string or mapping)", ErrTaskUnexpectedNodeKind, i, node.Tag)
+	}
 	dep.Name = node.Value
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/schema/dependencies.go` around lines 171 - 179, Update the ScalarNode
branch in the dependency decoding logic to verify node.Tag denotes a YAML string
before assigning node.Value to dep.Name; return the existing unexpected-node
error for booleans, numbers, null, and other non-string scalars. Add coverage
for boolean, numeric, and null dependency entries while preserving valid string
and mapping decoding.
internal/exec/workflow_utils.go-1034-1041 (1)

1034-1041: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

An artifacts:-only step never skips.

RecordSuccess runs only when step.Inputs != nil. For a step that declares artifacts: without inputs:, no record is ever saved. Checker.checksumChanged then finds no record and returns true on every run, so the implicit checksum.changed condition always matches.

pkg/runner/freshness/checker.go lines 136-139 document that declaring artifacts: alone is enough to skip a step whose work is already done. Please align the two: either record state for artifacts-only steps, or treat "artifacts all exist and no sources declared" as unchanged in checksumChanged.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/exec/workflow_utils.go` around lines 1034 - 1041, The freshness
state is recorded only when step.Inputs is non-nil, so artifacts-only steps
never become skippable. Update the post-success logic around
freshnessChecker.RecordSuccess to also persist freshness state for steps
declaring artifacts without inputs, or update Checker.checksumChanged to treat
existing artifacts with no sources as unchanged, preserving the documented
artifacts-only skip behavior.
internal/exec/custom_command_control_adapter.go-53-68 (1)

53-68: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Propagate dry-run into custom command child steps.

ExecuteCustomCommandControlStep hard-codes false for ExecuteShellCommand[5], while executeWorkflowControlStep[2] passes control.dryRun. Add DryRun to CustomCommandControlContext and use it here if custom commands are expected to honor dry-run.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/exec/custom_command_control_adapter.go` around lines 53 - 68, Update
CustomCommandControlContext to include a DryRun field, then pass that value
instead of the hard-coded false argument in the RunCommand callback used by
ExecuteCustomCommandControlStep. Ensure custom command child steps receive and
honor the same dry-run state propagated by executeWorkflowControlStep.
pkg/runner/freshness/checker.go-433-448 (1)

433-448: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

baseDir is documented as absolute, but callers can pass a relative path.

The doc comment states baseDir is absolute so state does not cross-contaminate between checkouts. internal/exec/workflow_utils.go line 564-567 falls back to "." when CalculateWorkingDirectory returns an empty string. Two different worktrees then produce the same key. Please resolve the path before hashing, or relax the comment.

🛡️ Proposed fix
 func (c *Checker) stateKey(scope, stepName, baseDir string, sourcesPatterns []string) string {
 	sorted := make([]string, len(sourcesPatterns))
 	copy(sorted, sourcesPatterns)
 	sort.Strings(sorted)
 
+	if abs, err := filepath.Abs(baseDir); err == nil {
+		baseDir = abs
+	}
+
 	h := sha256.New()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/runner/freshness/checker.go` around lines 433 - 448, Update
Checker.stateKey to resolve baseDir to an absolute, cleaned path before
incorporating it into the hash, preserving the documented per-checkout isolation
even when callers provide "." or another relative path. Keep the existing scope,
stepName, and sorted sourcesPatterns hashing behavior unchanged; handle
path-resolution errors according to the surrounding package’s established
conventions.
pkg/taskgraph/adapters/cobra_command.go-184-195 (1)

184-195: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Check ctx after the lock is acquired so fail_fast cancellation stops queued dispatches.

Same-name dispatches serialize on targetLock. A dispatch can wait there while the scheduler cancels ctx because of fail_fast. When the lock is released, this code still runs the command in full. Add a cancellation check before dispatch.

🛡️ Proposed guard
 		targetLock := locks.lockFor(target)
 		targetLock.Lock()
 		defer targetLock.Unlock()
 
+		// The scheduler may have cancelled ctx (fail_fast) while this dispatch waited on the lock.
+		if err := ctx.Err(); err != nil {
+			return err
+		}
+
 		return dispatchCustomCommand(ctx, target, &ref)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/taskgraph/adapters/cobra_command.go` around lines 184 - 195, In the
returned dispatch function, check ctx cancellation immediately after targetLock
is acquired and before calling dispatchCustomCommand. Return the context
cancellation error when ctx is done, while preserving the existing lock/unlock
and command lookup behavior.
cmd/custom_command_inputs_test.go-186-218 (1)

186-218: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Assert that go is on PATH before relying on it.

The comment on lines 189-191 treats go as guaranteed present. A prebuilt test binary can run without the Go toolchain on PATH. In that environment the precondition is unmet, the step runs, runLog is created, and line 241 fails with "step must be skipped when the precondition tool is already on PATH" — a message that points at the feature rather than at the environment. Add an explicit exec.LookPath check so the misconfiguration fails loudly and legibly.

💚 Proposed addition
 	tmpDir := t.TempDir()
 	atmosConfig.BasePath = tmpDir
 	runLog := filepath.Join(tmpDir, "run.txt")
 
+	// This test's whole premise is that the declared tool resolves. Fail loudly, not with a
+	// misleading "step must be skipped" assertion failure, if the environment lacks it.
+	_, lookErr := exec.LookPath("go")
+	require.NoError(t, lookErr, "this test requires the 'go' binary on PATH")
+

Add "os/exec" to the imports.

As per coding guidelines: "Safety precondition and fixture-count checks must fail loudly with require.Positive or an equivalent assertion; do not silently skip on misconfiguration."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/custom_command_inputs_test.go` around lines 186 - 218, In
TestCustomCommandIntegration_PreconditionSkipsWhenToolAlreadyOnPath, import
os/exec and explicitly verify that exec.LookPath("go") succeeds before
configuring the scenario. Use a require assertion with a clear
environment-focused failure message rather than skipping, then retain the
existing precondition test flow.

Source: Coding guidelines

pkg/taskgraph/taskgraph_test.go-114-128 (1)

114-128: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the specific cycle error.

Line 127 accepts any error. A future regression that makes Run fail earlier, for example with ErrMissingRunner or ErrUnknownDependency, would keep this test green while the cycle check silently stops working. buildGraph documents that dependency.GraphBuilder.Build returns dependency.ErrCircularDependency, so assert it.

💚 Proposed fix
 	require.Error(t, err)
+	assert.ErrorIs(t, err, dependency.ErrCircularDependency, "a -> b -> a must be reported as a circular dependency")
 }

Add "github.com/cloudposse/atmos/pkg/dependency" to the imports.

As per coding guidelines: "avoid tautological, stub, always-skipped, or coverage-only tests".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/taskgraph/taskgraph_test.go` around lines 114 - 128, Update
TestRun_CycleErrors to assert that Run returns dependency.ErrCircularDependency,
importing the dependency package for the expected sentinel error. Replace the
broad require.Error assertion while preserving the existing cyclic dependency
setup.

Source: Coding guidelines

pkg/taskgraph/adapters/cobra_command.go-231-240 (1)

231-240: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Set workflow dependency execution state from the custom command context.

CustomCommandDependencyOptions always passes dryRun=false and commandLineIdentity="". Later, custom commands still read the inherited --identity value at step execution. If a caller can invoke the custom tree with --dry-run or --identity, workflow dependencies can run in production and lose the caller-selected identity. Thread both values into this constructor and pass them through.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/taskgraph/adapters/cobra_command.go` around lines 231 - 240, Update
CustomCommandDependencyOptions to accept the custom command context’s dry-run
and command-line identity values, then pass both through to e.WorkflowRunner
instead of hardcoding false and an empty identity. Update all callers to supply
the inherited --dry-run and --identity values so workflow dependencies preserve
the caller’s execution state.
pkg/taskgraph/taskgraph.go-100-116 (1)

100-116: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

best_effort also swallows configuration errors, not just task failures.

Lines 112-115 discard aggregate.Err entirely. The aggregate can carry non-task errors produced by newDispatcher, for example ErrMissingRunner (Line 239) or the "no ref metadata" error (Line 232). Under fail: best_effort, a misconfigured graph then reports success with no signal at all.

Consider logging the swallowed aggregate at warn/debug level so operators still see the cause.

♻️ Suggested adjustment
 	aggregate := scheduler.New(graph, dispatcher, schedOpts...).Run(ctx)
 	if failMode == FailBestEffort {
+		if aggregate.Err != nil {
+			log.Debug("dependency failures ignored due to fail: best_effort", "error", aggregate.Err)
+		}
 		return nil
 	}
 	return aggregate.Err

Add the log "github.com/charmbracelet/log" import alias already used across the repo.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/taskgraph/taskgraph.go` around lines 100 - 116, Update the FailBestEffort
branch in the taskgraph execution flow to log aggregate.Err at warn or debug
level before returning nil, preserving successful best-effort task handling
while surfacing configuration errors such as missing runners or ref metadata.
Add the repository’s existing charmbracelet/log import alias and use it for this
diagnostic.
docs/fixes/2026-08-06-line-prefix-writer-multiline-atomicity.md-19-19 (1)

19-19: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language identifier to the CI-log fence.

The opening fence at Line 19 has no language identifier. Add text after the backticks to satisfy markdownlint MD040.

The supplied markdownlint result identifies MD040 at Line 19.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/fixes/2026-08-06-line-prefix-writer-multiline-atomicity.md` at line 19,
Update the opening fenced code block at the indicated location in the markdown
document to use the text language identifier, changing the fence from an untyped
fence to a text fence while preserving its contents and closing fence.

Source: Linters/SAST tools

pkg/hashfile/hashfile_test.go-65-68 (1)

65-68: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use a platform-neutral missing-file path.

"/no/such/file" is a Unix-style absolute path. Build the missing path below t.TempDir() with filepath.Join.

Suggested fix
-	_, err := HashFiles([]string{"/no/such/file"})
+	missing := filepath.Join(t.TempDir(), "missing.txt")
+	_, err := HashFiles([]string{missing})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/hashfile/hashfile_test.go` around lines 65 - 68, Update
TestHashFiles_MissingFileErrors to construct the nonexistent path beneath
t.TempDir() using filepath.Join instead of the hard-coded Unix absolute path,
while preserving the existing error assertion.

Source: Coding guidelines

docs/fixes/2026-08-05-custom-command-dependency-shared-cobra-state.md-1-1 (1)

1-1: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Align the fix-document summaries with their stated scope.

Both fix notes use broader headlines than their explicit scope statements. Readers may assume that all dependency failures are recoverable and that freshness checks can skip an entire workflow.

  • docs/fixes/2026-08-05-custom-command-dependency-shared-cobra-state.md#L1-L1: Qualify the “hard-exit” claim to the covered step-failure paths. The document states that earlier flag, working-directory, and identity-resolution failures still hard-exit.
  • docs/fixes/2026-08-05-custom-command-freshness-when-precheck.md#L1-L16: Separate the custom-command whole-run fix from the workflow needsAuth fix. The document states that the workflow change only controls auth-manager setup.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/fixes/2026-08-05-custom-command-dependency-shared-cobra-state.md` at
line 1, Update the summaries in
docs/fixes/2026-08-05-custom-command-dependency-shared-cobra-state.md:1 to
qualify the “hard-exit” claim as applying only to the covered step-failure
paths, while acknowledging that flag, working-directory, and identity-resolution
failures still hard-exit. Update
docs/fixes/2026-08-05-custom-command-freshness-when-precheck.md:1-16 to
distinguish the custom-command whole-run behavior from the workflow needsAuth
change, which only controls auth-manager setup.
website/docs/workflows/workflows/workflow/steps/continue.mdx-29-37 (1)

29-37: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Describe the default failure behavior precisely.

Line 31 says a failed step “stops the workflow.” Later structured steps still evaluate after a failure. This permits when: failure and when: always steps to run. State that an omitted continue keeps the workflow failed and skips success-only steps.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@website/docs/workflows/workflows/workflow/steps/continue.mdx` around lines 29
- 37, Update the “Omitted” entry in the continue behavior documentation to state
that a step failure keeps the workflow failed, skips success-only steps, and
still allows subsequent steps using when: failure or when: always to evaluate
and run.
website/docs/workflows/workflows/workflow/steps/continue.mdx-29-40 (1)

29-40: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use the YAML !cel tag in the quoted example.

When !cel is inside quotes, YAML passes it as a literal scalar to CEL instead of applying it as a tag. Use the explicit tag for a copy-pasteable CEL example.

Proposed documentation fix
-  <dd>Any CEL expression that evaluates to a boolean, for finer control — for example, `continue: "!cel env.CI == \'true\'"` to tolerate a failure only in CI.</dd>
+  <dd>Any CEL expression that evaluates to a boolean, for finer control — for example, `continue: !cel 'env.CI == "true"'` to tolerate a failure only in CI.</dd>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@website/docs/workflows/workflows/workflow/steps/continue.mdx` around lines 29
- 40, Update the CEL expression example in the workflow step documentation to
use YAML’s explicit !cel tag rather than placing !cel inside the quoted scalar.
Keep the example’s CI-based boolean expression and ensure it remains
copy-pasteable YAML.
🧹 Nitpick comments (19)
pkg/taskgraph/adapters/cobra_command_test.go (1)

176-180: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert what the options wire, not just how many there are.

assert.Len(t, opts, 4, ...) passes even if one option is duplicated and another is dropped, which is exactly the regression the message claims to guard against. Apply the options to a taskgraph.Options value and assert that the command runner, command lookup, workflow runner, and workflow lookup are each non-nil.

As per coding guidelines: "for slice results assert element values rather than only length".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/taskgraph/adapters/cobra_command_test.go` around lines 176 - 180, Update
TestCustomCommandDependencyOptions_ReturnsAllFourOptions to apply opts to a
taskgraph.Options value, then assert that the command runner, command lookup,
workflow runner, and workflow lookup fields are each non-nil. Replace the
length-only assertion while preserving the test’s coverage of all four
dependencies.

Source: Coding guidelines

pkg/condition/condition_test.go (1)

300-323: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer EvaluateE for the negative assertions.

Evaluate swallows evaluation errors and returns false (see pkg/condition/evaluate.go lines 20-23). So assert.False(t, mismatchOS.Evaluate(ctx)) and assert.False(t, notStale.Evaluate(ctx)) pass both when the expression correctly evaluates to false and when it fails at runtime. EvaluateE with require.NoError separates the two outcomes.

♻️ Proposed change for the negative cases
 	mismatchOS, err := New("!cel os == 'not-a-real-os'")
 	require.NoError(t, err)
-	assert.False(t, mismatchOS.Evaluate(ctx))
+	got, evalErr := mismatchOS.EvaluateE(ctx)
+	require.NoError(t, evalErr)
+	assert.False(t, got)
 	notStale, err := New("!cel sources.exists(s, artifacts.all(a, s.mtime < a.mtime))")
 	require.NoError(t, err)
-	assert.False(t, notStale.Evaluate(ctx))
+	got, evalErr := notStale.EvaluateE(ctx)
+	require.NoError(t, evalErr)
+	assert.False(t, got)

Also applies to: 337-358

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/condition/condition_test.go` around lines 300 - 323, Update the negative
assertions in TestConditionEvaluate_PlatformFacts and the additional notStale
cases to call EvaluateE instead of Evaluate. Require no evaluation error, then
assert the returned boolean is false so runtime failures cannot satisfy the
negative checks.
pkg/schema/dependencies_test.go (1)

14-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider covering args and fail in the round-trip assertions.

The tests exercise name, flags, and file, but not args or fail. Both fields carry real behavior: per the UnitDependency doc in pkg/schema/dependencies.go, args participates in the DAG dedup key, and fail selects the failure-propagation mode. Adding them here locks the full decode contract.

♻️ Proposed addition
 commands:
   - build
   - name: test
     flags:
       env: dev
+    args: [--verbose]
+    fail: fail_fast
 workflows:
 	assert.Equal(t, map[string]string{"env": "dev"}, deps.Commands[1].Flags)
+	assert.Equal(t, []string{"--verbose"}, deps.Commands[1].Args)
+	assert.Equal(t, "fail_fast", deps.Commands[1].Fail)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/schema/dependencies_test.go` around lines 14 - 38, Extend
TestUnitDependencies_UnmarshalYAML to include args and fail values in the YAML
input for relevant UnitDependency entries, then assert the decoded Args and Fail
fields. Preserve the existing name, flags, file, and collection assertions while
covering both fields’ unmarshalling behavior.
pkg/datafetcher/schema/atmos/config/1.0.json (1)

12616-12626: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider constraining fail to its three valid values.

The description names wait_all, fail_fast, and best_effort, but the schema accepts any string. A typo such as failfast passes schema validation and only surfaces at runtime. ParallelFailConfig.mode in pkg/datafetcher/schema/atmos/manifest/1.0.json already uses an enum for the same vocabulary, so this would align the two. The schema is generated, so the change belongs on the Go field's jsonschema tag in pkg/schema/dependencies.go.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/datafetcher/schema/atmos/config/1.0.json` around lines 12616 - 12626, The
fail field schema is too permissive because it accepts arbitrary strings despite
documenting three valid values. Update the Go field in dependencies.go that
generates this schema, adding an enum constraint for wait_all, fail_fast, and
best_effort while preserving its nullable behavior and regenerating the affected
schema.
examples/task-runner-dependencies/atmos.yaml (2)

19-23: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

GNU-only shell utilities in the cross-platform fixtures. Both fixtures build log lines with date +%s%N. %N is a GNU coreutils extension, so macOS prints a literal N and Windows has no date binary. The shared root cause is the use of GNU-specific utilities in example fixtures that must run on Linux, macOS, and Windows.

  • examples/task-runner-dependencies/atmos.yaml#L19-L23: replace date +%s%N with a portable marker, and replace sleep 2 in the step-c-slow command with a portable delay.
  • examples/task-runner-dependencies/workflows/task-runner.yaml#L8-L11: replace date +%s%N in the smoke step with the same portable marker.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/task-runner-dependencies/atmos.yaml` around lines 19 - 23, Replace
the GNU-specific date expressions with one portable marker in
examples/task-runner-dependencies/atmos.yaml lines 19-23 and
examples/task-runner-dependencies/workflows/task-runner.yaml lines 8-11,
preserving the log format and using the same marker in both fixtures. In
atmos.yaml, also replace the step-c-slow command’s sleep 2 with a delay
mechanism that works on Linux, macOS, and Windows.

102-103: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

These two steps write into logs/ without creating it.

Every other step runs mkdir -p logs first. release-failfast and release-besteffort depend on that side effect from their dependencies. If a dependency is skipped or fails early, the redirect fails.

♻️ Suggested fix
     steps:
       - type: shell
-        command: echo "release-failfast ran" >> logs/order.log
+        command: |
+          mkdir -p logs
+          echo "release-failfast ran" >> logs/order.log

Also applies to: 113-115

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/task-runner-dependencies/atmos.yaml` around lines 102 - 103, Update
the shell commands for the release-failfast and release-besteffort steps to
create the logs directory with mkdir -p before appending to logs/order.log, so
each step works independently without relying on dependency side effects.
internal/exec/workflow_utils_test.go (1)

1541-1569: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The test depends on the go binary being on PATH.

go test runs a compiled binary. The toolchain directory is usually on PATH, but that is not guaranteed in every CI image or when the test binary runs standalone. If exec.LookPath("go") fails, the step runs and the assertion fails for the wrong reason.

Create a temporary executable and prepend its directory to PATH with t.Setenv, then declare that name in Precondition.Tools. That keeps the test self-contained and cross-platform.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/exec/workflow_utils_test.go` around lines 1541 - 1569, Update
TestExecuteWorkflow_PreconditionSkipsWhenToolAlreadyOnPath to create a temporary
executable in a temporary directory, prepend that directory to PATH with
t.Setenv, and use the executable’s name in Precondition.Tools instead of relying
on “go”. Ensure the fixture is executable across supported platforms so the
precondition is deterministically satisfied and the workflow step remains
skipped.
pkg/runner/freshness/checker_test.go (1)

499-507: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Assert the matched path, not only the slice length.

The test proves one match exists but not that it is the created file.

💚 Proposed assertion
 	matches, err := g.Glob(tmpDir, "*.go")
 	require.NoError(t, err)
-	assert.Len(t, matches, 1)
+	require.Len(t, matches, 1)
+	assert.Equal(t, filepath.Join(tmpDir, "main.go"), matches[0])

As per coding guidelines: "for slice results assert element values rather than only length".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/runner/freshness/checker_test.go` around lines 499 - 507, Update
TestDefaultGlobber_ResolvesRealFiles to assert that the matched path equals the
expected path for the created main.go file, while retaining the existing error
check and match-count assertion.

Source: Coding guidelines

pkg/runner/freshness/checker.go (1)

228-250: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Artifact matches are not deduplicated, unlike source matches.

globAll deduplicates source matches. The artifact branch appends raw matches per pattern, so two overlapping artifacts.paths patterns produce duplicate entries. Duplicates then reach buildFileFacts, so the artifacts CEL list contains the same file twice, and each duplicate is hashed again.

♻️ Suggested dedup for artifact matches
 	if needs.artifactGlob() && len(artifactPatterns) > 0 {
+		seen := make(map[string]struct{})
 		for _, pattern := range artifactPatterns {
 			matches, globErr := c.globber.Glob(baseDir, pattern)
 			if globErr != nil {
 				return globResult{}, globErr
 			}
 			if len(matches) == 0 {
 				result.artifactsAllExist = false
 			}
-			result.artifactMatches = append(result.artifactMatches, matches...)
+			for _, m := range matches {
+				if _, ok := seen[m]; ok {
+					continue
+				}
+				seen[m] = struct{}{}
+				result.artifactMatches = append(result.artifactMatches, m)
+			}
 		}
 	}

The per-pattern artifactsAllExist check must stay inside the loop, as shown.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/runner/freshness/checker.go` around lines 228 - 250, Update
Checker.globSourcesAndArtifacts to deduplicate artifactMatches across
overlapping artifactPatterns, matching globAll’s source-match behavior. Preserve
the per-pattern artifactsAllExist check inside the loop, and ensure each
artifact is appended only once before results reach buildFileFacts.
pkg/runner/freshness/errors.go (1)

9-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the unused ErrGlobInvalid sentinel.

ErrGlobInvalid is only declared and documentation says defaultGlobber.Glob returns errors from filesystem.GetGlobMatches. Wire the sentinel into the glob path or remove this unused error to keep the exported surface clean.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/runner/freshness/errors.go` around lines 9 - 11, Remove the unused
exported ErrGlobInvalid sentinel and its associated comment from the freshness
errors definitions, since defaultGlobber.Glob continues to propagate
filesystem.GetGlobMatches errors directly.
cmd/custom_command_dependency_test.go (1)

362-378: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Capture the exit code, not only the fact of exiting.

The stub at line 366 discards code. Recording it lets the test pin the propagated failure status, so a regression that exits with 0 on a failed dependency is caught.

💚 Proposed addition
 	var mu sync.Mutex
 	exited := false
+	exitCode := 0
 	originalOsExit := errUtils.OsExit
 	t.Cleanup(func() { errUtils.OsExit = originalOsExit })
 	errUtils.OsExit = func(code int) {
 		mu.Lock()
 		exited = true
+		exitCode = code
 		mu.Unlock()
 	}
@@
 	assert.True(t, exited, "the default (wait_all) fail mode must still surface a dependency's failure via errUtils.OsExit")
+	assert.NotZero(t, exitCode, "a failed dependency must produce a non-zero exit code")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/custom_command_dependency_test.go` around lines 362 - 378, Update the
errUtils.OsExit stub in the parentCmd.Run test to capture the provided code in a
protected variable, then assert that the propagated exit status matches the
expected failure code in addition to asserting exited is true. Keep the existing
synchronization and ownStepLog assertion unchanged.
internal/exec/workflow.go (1)

206-214: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the manifest path to the read and parse errors.

Lines 208 and 213 return the raw os.ReadFile and YAML errors. LoadWorkflowConfig now serves three call sites: ExecuteWorkflowCmd, WorkflowLookup, and WorkflowRunner. A bare yaml: line 7: did not find expected key no longer tells the user which manifest failed, and dependency resolution can load several manifests in one run.

♻️ Proposed change
 	fileContent, err := os.ReadFile(workflowPath)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("failed to read workflow manifest %q: %w", filepath.ToSlash(workflowPath), err)
 	}
 
 	workflowManifest, err := u.UnmarshalYAML[schema.WorkflowManifest](string(fileContent))
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("failed to parse workflow manifest %q: %w", filepath.ToSlash(workflowPath), err)
 	}

As per coding guidelines: "Provide clear error messages to users, include troubleshooting hints when appropriate" and wrap errors "with context using fmt.Errorf(\"context: %w\", err)".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/exec/workflow.go` around lines 206 - 214, Update LoadWorkflowConfig
around os.ReadFile and UnmarshalYAML to wrap both errors with workflowPath
context using fmt.Errorf and %w, preserving the original errors for unwrapping
while identifying which manifest failed.

Source: Coding guidelines

pkg/taskgraph/taskgraph.go (1)

229-233: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reusing ErrUnknownDependencyKind for missing node metadata is misleading.

Line 232 reports an internal invariant violation ("no ref metadata") with the sentinel that means "unsupported Kind". A caller that uses errors.Is(err, ErrUnknownDependencyKind) cannot tell the two cases apart. Add a dedicated sentinel in pkg/taskgraph/errors.go.

♻️ Suggested change
-			return scheduler.Result{}, fmt.Errorf("%w: node %q has no ref metadata", ErrUnknownDependencyKind, node.ID)
+			return scheduler.Result{}, fmt.Errorf("%w: node %q", ErrMissingRefMetadata, node.ID)

Add to pkg/taskgraph/errors.go:

// ErrMissingRefMetadata is returned when a graph node lacks its "ref" metadata entry, which
// indicates an internal graph-construction bug rather than a user configuration error.
var ErrMissingRefMetadata = errors.New("graph node has no ref metadata")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/taskgraph/taskgraph.go` around lines 229 - 233, Add the dedicated
ErrMissingRefMetadata sentinel in errors.go and update the metadata validation
in the DispatcherFunc callback to wrap it instead of ErrUnknownDependencyKind
when node.Metadata lacks a valid "ref". Preserve the existing node ID context
and keep ErrUnknownDependencyKind for unsupported dependency kinds.
internal/exec/workflow_dependency_adapter.go (1)

109-122: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy lift

ctx is accepted but never reaches the subprocess.

ExecuteShellCommand takes no context.Context, so a fail_fast cancellation or a Ctrl-C leaves this dependency subprocess running until it exits on its own. The parent graph waits on it. Threading a context through ExecuteShellCommand is a cross-cutting change and belongs in its own PR, so record the gap here and track it.

I can open an issue for a context-aware ExecuteShellCommand variant if that helps.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/exec/workflow_dependency_adapter.go` around lines 109 - 122,
Document the missing context propagation in commandRunnerViaSubprocess, noting
that ExecuteShellCommand cannot currently receive ctx and that subprocess
cancellation remains unhandled. Add a TODO or issue-tracking reference at the
call site without attempting to modify ExecuteShellCommand or introduce broader
context changes.
pkg/taskgraph/taskgraph_test.go (1)

130-160: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for fail_fast and for WithMaxConcurrency.

The tests cover wait_all (Line 143) and best_effort (Line 159). The FailFast branch of effectiveFailMode and the scheduler.WithFailFast wiring in Run have no test, and WithMaxConcurrency is never exercised. A fail_fast case with two independent dependencies, one failing, would pin both the mode derivation and the sibling-cancellation behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/taskgraph/taskgraph_test.go` around lines 130 - 160, Extend the taskgraph
tests with a fail_fast case containing two independent dependencies, making one
fail and asserting the sibling is cancelled or not completed, to cover
effectiveFailMode and Run’s scheduler.WithFailFast wiring. Add a separate test
that invokes Run with WithMaxConcurrency and verifies execution is bounded by
the configured concurrency limit.
cmd/custom_command_control_test.go (1)

146-151: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert that errUtils.OsExit is not reached.

The doc comment on lines 89-91 states that the command "does not exit with an error", but nothing here checks that. customCmd.Run returns nothing, so a hard exit through errUtils.OsExit would leave these three assertions passing. cmd/custom_command_dependency_test.go already establishes the mutex-guarded override pattern for exactly this check; reuse it.

💚 Proposed addition
+	var mu sync.Mutex
+	exited := false
+	originalOsExit := errUtils.OsExit
+	t.Cleanup(func() { errUtils.OsExit = originalOsExit })
+	errUtils.OsExit = func(int) {
+		mu.Lock()
+		exited = true
+		mu.Unlock()
+	}
+
 	customCmd.Run(customCmd, []string{})
 
+	mu.Lock()
+	defer mu.Unlock()
+	assert.False(t, exited, "continue: always must not exit the process")
 	assert.FileExists(t, failFile)

Add the sync and errUtils "github.com/cloudposse/atmos/errors" imports.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/custom_command_control_test.go` around lines 146 - 151, Update the test
around customCmd.Run to use the mutex-guarded errUtils.OsExit override pattern
established in custom_command_dependency_test.go, adding the required sync and
errUtils imports. Capture whether OsExit is invoked, restore the override
safely, and assert it was not reached while preserving the existing file
assertions.
pkg/taskgraph/adapters/cobra_command.go (1)

65-78: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Export the error sink type or hide WithDependencyErrorSink.

WithDependencyErrorSink is exported, but it returns the unexported *errorSink, so external packages cannot name the value in variable declarations, fields, or signatures. Rename it to ErrorSink with an exported Err() accessor, or make the constructor unexported if this API stays package-local.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/taskgraph/adapters/cobra_command.go` around lines 65 - 78, Resolve the
exported API mismatch around WithDependencyErrorSink: either export errorSink as
ErrorSink and add an exported Err() accessor for reading the recorded error, or
make WithDependencyErrorSink unexported if it is strictly package-local. Update
all references consistently while preserving the sink’s existing behavior.

Source: Coding guidelines

pkg/hashfile/hashfile.go (1)

28-36: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Apply the repository error-wrapping policy.

HashFiles returns file and digest-write errors directly at Line 30, Line 33, and Line 36. Add operation and path context with %w, and route failures through the static errors defined in errors/errors.go.

As per coding guidelines, wrap all errors with static errors from errors/errors.go and use %w for string context.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/hashfile/hashfile.go` around lines 28 - 36, Update HashFiles to wrap each
os.ReadFile and hash.Write failure with the appropriate static error from
errors/errors.go, adding operation and path context via %w. Preserve the
existing immediate returns while ensuring errors at all three failure points
identify the relevant file path and operation.

Source: Coding guidelines

website/blog/2026-08-05-taskfile-convergence.mdx (1)

153-158: 📐 Maintainability & Code Quality | 🔵 Trivial

Verify the documentation routes and build the website.

The blog links to /cli/configuration/commands/dependencies, while the supplied repository path is website/docs/cli/configuration/commands/command/dependencies.mdx. Confirm that the page frontmatter publishes the flattened route. Then run cd website && npm run build to validate the new MDX and links.

Based on learnings, Docusaurus routes in this repository must use explicit frontmatter id or slug values, not inferred file paths. As per coding guidelines, documentation changes require website build and link/rendering verification.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@website/blog/2026-08-05-taskfile-convergence.mdx` around lines 153 - 158,
Verify the frontmatter in the dependencies documentation page resolves to the
linked flattened route /cli/configuration/commands/dependencies, adding or
correcting its explicit id or slug as needed. Then run the website build with cd
website && npm run build to validate the MDX and links.

Sources: Coding guidelines, Learnings


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 457c5637-575b-43e0-85f8-3e99da465109

📥 Commits

Reviewing files that changed from the base of the PR and between d2b8e81 and 038bf7e.

📒 Files selected for processing (74)
  • cmd/cmd_utils.go
  • cmd/custom_command_aliases_test.go
  • cmd/custom_command_control_test.go
  • cmd/custom_command_dependency_test.go
  • cmd/custom_command_inputs_test.go
  • cmd/custom_command_values_test.go
  • cmd/list/aliases.go
  • cmd/list/aliases_test.go
  • docs/fixes/2026-08-05-custom-command-dependency-shared-cobra-state.md
  • docs/fixes/2026-08-05-custom-command-freshness-when-precheck.md
  • docs/fixes/2026-08-05-workflow-command-dependency-wrong-atmos-binary.md
  • docs/fixes/2026-08-06-line-prefix-writer-multiline-atomicity.md
  • errors/errors.go
  • examples/task-runner-dependencies/atmos.yaml
  • examples/task-runner-dependencies/src/example.txt
  • examples/task-runner-dependencies/workflows/task-runner.yaml
  • internal/exec/custom_command_control_adapter.go
  • internal/exec/workflow.go
  • internal/exec/workflow_dependency_adapter.go
  • internal/exec/workflow_dependency_adapter_test.go
  • internal/exec/workflow_utils.go
  • internal/exec/workflow_utils_test.go
  • pkg/condition/cel.go
  • pkg/condition/condition.go
  • pkg/condition/condition_test.go
  • pkg/condition/evaluate.go
  • pkg/config/load.go
  • pkg/datafetcher/schema/atmos/config/1.0.json
  • pkg/datafetcher/schema/atmos/manifest/1.0.json
  • pkg/datafetcher/schema/stacks/stack-config/1.0.json
  • pkg/flags/constrained.go
  • pkg/flags/constrained_test.go
  • pkg/flags/standard.go
  • pkg/flags/standard_test.go
  • pkg/hashfile/hashfile.go
  • pkg/hashfile/hashfile_test.go
  • pkg/io/line_prefix_writer.go
  • pkg/process/exec_replace_windows.go
  • pkg/process/shell_command_unix.go
  • pkg/process/shell_command_windows.go
  • pkg/process/shell_session.go
  • pkg/runner/freshness/checker.go
  • pkg/runner/freshness/checker_test.go
  • pkg/runner/freshness/errors.go
  • pkg/runner/freshness/globber.go
  • pkg/runner/freshness/state.go
  • pkg/schema/command.go
  • pkg/schema/dependencies.go
  • pkg/schema/dependencies_test.go
  • pkg/schema/task.go
  • pkg/schema/task_test.go
  • pkg/schema/workflow.go
  • pkg/taskgraph/adapters/cobra_command.go
  • pkg/taskgraph/adapters/cobra_command_test.go
  • pkg/taskgraph/errors.go
  • pkg/taskgraph/ref.go
  • pkg/taskgraph/schema.go
  • pkg/taskgraph/taskgraph.go
  • pkg/taskgraph/taskgraph_test.go
  • pkg/workflow/condition_context.go
  • website/blog/2026-08-05-taskfile-convergence.mdx
  • website/docs/cli/configuration/aliases.mdx
  • website/docs/cli/configuration/commands/command/arguments.mdx
  • website/docs/cli/configuration/commands/command/dependencies.mdx
  • website/docs/cli/configuration/commands/command/flags.mdx
  • website/docs/cli/configuration/commands/command/index.mdx
  • website/docs/cli/configuration/commands/command/steps.mdx
  • website/docs/workflows/workflows/workflow/dependencies.mdx
  • website/docs/workflows/workflows/workflow/steps/artifacts.mdx
  • website/docs/workflows/workflows/workflow/steps/continue.mdx
  • website/docs/workflows/workflows/workflow/steps/index.mdx
  • website/docs/workflows/workflows/workflow/steps/inputs.mdx
  • website/docs/workflows/workflows/workflow/steps/precondition.mdx
  • website/src/data/roadmap.js

Comment thread cmd/cmd_utils.go Outdated
Comment thread cmd/cmd_utils.go
Comment thread cmd/custom_command_values_test.go Outdated
Comment thread internal/exec/workflow_dependency_adapter.go
Comment thread pkg/hashfile/hashfile.go Outdated
Comment thread pkg/hashfile/hashfile.go Outdated
- cmd/cmd_utils.go: propagate cmd.Context() to taskgraph.Run so Cobra cancellation
  reaches dependency execution instead of using context.Background(); generalize the
  dependency-error-sink helper and route every error path in executeCustomCommand
  (~28 sites: argument processing, dependency/tool resolution, working-directory
  resolution, validation, component_config, ENV var resolution, per-step auth) through
  it, not just step-execution failures.
- internal/exec/workflow_utils.go + workflow_dependency_adapter.go: fix
  workflow-depends-on-workflow redundantly re-resolving and re-running its own
  dependency graph (a diamond dependency shared by two parents ran 3x instead of
  once) by adding a dependencies-resolved marker for nested ExecuteWorkflow calls,
  mirroring the existing command-side mechanism.
- pkg/hashfile/hashfile.go: fix two real hash collisions (path/content concatenation
  ambiguity, and losing directory identity by hashing only the basename) with
  length-prefixed records and full-path hashing; stream file reads via os.Open +
  io.Copy instead of loading whole files into memory.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 10, 2026
@mergify

mergify Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

💥 This pull request now has conflicts. Could you fix it Erik Osterman (Cloud Posse) (@osterman)? 🙏

@mergify mergify Bot added the conflict This PR has conflicts label Aug 10, 2026
…first-class-support

# Conflicts:
#	pkg/flags/standard.go
#	pkg/flags/standard_test.go
…first-class-support

# Conflicts:
#	website/package.json
#	website/pnpm-lock.yaml
…first-class-support

# Conflicts:
#	internal/exec/workflow.go
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 11, 2026
@osterman Erik Osterman (Cloud Posse) (osterman) changed the title Add dependencies and freshness checks to custom commands and workflows Add task-runner dependencies, freshness checks, and preconditions to custom commands and workflows Aug 11, 2026
Verified each finding against current code, fixing what was still valid and
skipping what was already resolved or working as intended (documented inline
where relevant).

Fixed:
- cmd_utils.go: hoist argumentsData/flagsData construction and values:
  validation above the per-step loop, so interactive prompts fire once per
  command invocation instead of once per step.
- pkg/taskgraph: new ErrMissingRefMetadata sentinel instead of reusing
  ErrUnknownDependencyKind for a missing-graph-metadata condition.
- internal/exec: wrap raw file-read/YAML-parse/executable-resolution errors
  with static sentinels (ErrReadFile, ErrInvalidWorkflowManifest, new
  ErrResolveExecutablePath) for errors.Is() classification.
- pkg/flags: wrap PersistentFlags().Set failures with errUtils.ErrSetFlag;
  add a ValueKind (flag vs argument) parameter to ValidateValue so a
  values:-constrained positional argument is never reported as an invalid
  flag.
- pkg/hashfile: wrap file-operation errors with static sentinels; replace a
  hardcoded Unix path in a test with a cross-platform t.TempDir() path.
- pkg/runner/freshness: wire the previously-dead ErrGlobInvalid sentinel
  into Glob()'s real error path; harden recordPath against a path-traversing
  state key; clean up stale Save() *.tmp files left by a killed process.
- pkg/process: document that NewShellCommand requires trusted config input.
- pkg/schema/task.go: fix a doc-comment merge bug where Task's doc comment
  had been swallowed into Inputs' (no blank line between adjacent type
  declarations), leaving Task undocumented and Inputs mis-described in the
  generated JSON schema. Regenerated pkg/datafetcher/schema/atmos/config/1.0.json.
- Docs: add a language tag to a fenced code block in a fix log.
- Tests: doc comments on 6 exported test functions; migrate 3 test files'
  echo-based shell fixtures to existing Go-native helpers; strengthen the
  same-file/cross-file dependency tests to assert execution ORDER via a
  shared log, not just that both steps ran; switch the preconditions.tools
  regression test off the "go" binary onto the running test binary's own
  os.Executable() path.

Skipped as already fixed/working as intended (verified against current code):
custom-command fail-stop behavior (contradicted by an existing test
documenting the no-break loop as deliberate), matrix TemplateData's ignored
matrix arg (by design -- pkg/workflow/control.go injects .matrix after the
callback), FindCommandByName ambiguous-name handling, cobra_command.go
context restoration, taskgraph FailBestEffort logging + run-wide Fail docs,
RecordSuccess artifacts-only handling, schema unit_dependency required:
["name"], preconditions.mdx GOBIN/PATH note, and a claimed duplicate
`dependencies` schema property (schema has since regenerated/drifted).
The ci.cache.includes doc finding was factually wrong (the real field is
ci.cache.paths per pkg/schema/schema.go) and was left unchanged.

Skipped as real but out of scope for a minimal pass: the stepExecutorState
global-state race across concurrently-dispatched sibling workflows and
splitting the ~700-line ExecuteWorkflow into helpers (both already
documented in-code as known/deferred); two pure test-organization nitpicks
(table-driven consolidation, splitting checker_test.go into separate
files); one shell-fixture migration that couldn't be confidently
line-matched after drift.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@aknysh
Andriy Knysh (aknysh) added this pull request to the merge queue Aug 11, 2026
@atmos-pro

atmos-pro Bot commented Aug 11, 2026 •

Copy link
Copy Markdown
Contributor

Tip

Atmos Pro  

No affected stacks workflow was detected for this pull request.
If this is expected, no action is needed.
Learn More. Ask AI.

@atmos-pro

atmos-pro Bot commented Aug 11, 2026 •

Copy link
Copy Markdown
Contributor

Tip

Atmos Pro  

No affected stacks workflow was detected for this pull request.
If this is expected, no action is needed.
Learn More. Ask AI.

@github-actions

Copy link
Copy Markdown

These changes were released in v1.226.0-rc.5.

This branch was successfully deployed

1 active and 1 inactive deployments
preview — 498e886c Deployed Aug 11, 2026 by github-actions[bot]
screengrabs — 498e886c Deployed Aug 11, 2026 by aknysh via build #1304
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/xxl

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants