Skip to content

feat(cast): auto-install animated renderers - #2763

Merged
Andriy Knysh (aknysh) merged 15 commits into
mainfrom
osterman/auto-install-cast-renderers
Jul 19, 2026
Merged

Andriy Knysh (aknysh) merged 15 commits into
mainfrom
osterman/auto-install-cast-renderers

Conversation

@osterman

@osterman Erik Osterman (Cloud Posse) (osterman) commented Jul 18, 2026 •

Copy link
Copy Markdown
Member

what

  • Auto-install pinned agg and FFmpeg renderers for animated cast outputs
  • Resolve managed binaries to absolute paths while keeping static formats native
  • Add renderer coverage and document automatic installation and MP4 palette constraints

why

  • Remove the manual renderer setup requirement for GIF and MP4 cast rendering

references

  • N/A

Summary by CodeRabbit

  • New Features
    • Animated GIF and MP4 rendering now automatically manages and installs the needed tools on first use.
  • Bug Fixes
    • Rendering now reports clearer failures and prevents overwriting existing animated outputs.
    • Cached tool discovery on Windows is fixed (improves test/tooling reliability).
    • Terminal-width fallback is more consistent in non-interactive environments by honoring COLUMNS.
  • Documentation
    • Updated cast rendering and workflow docs to reflect managed installation and MP4-from-GIF behavior.

@atmos-pro

atmos-pro Bot commented Jul 18, 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 github-actions Bot added the size/m Medium size PR label Jul 18, 2026
@github-actions

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues found.

Scanned Files

None

@osterman
Erik Osterman (Cloud Posse) (osterman) marked this pull request as ready for review July 18, 2026 13:00
@osterman Erik Osterman (Cloud Posse) (osterman) added the minor New features that do not break anything label Jul 18, 2026
@github-actions

Copy link
Copy Markdown

Warning

Release Documentation Required

This PR is labeled minor or major and requires documentation updates:

  • Changelog entry - Add a blog post in website/blog/YYYY-MM-DD-feature-name.mdx
  • Roadmap update - Update website/src/data/roadmap.js with the new milestone

Alternatively: If this change doesn't require release documentation, remove the minor or major label.

@osterman Erik Osterman (Cloud Posse) (osterman) added patch A minor, backward compatible change and removed minor New features that do not break anything labels Jul 18, 2026
@github-actions

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"

@coderabbitai

coderabbitai Bot commented Jul 18, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 661b785f-621b-4fff-bd9d-cb90d2528fd6

📥 Commits

Reviewing files that changed from the base of the PR and between 3b1c3ee and 65e7f7b.

📒 Files selected for processing (2)
  • internal/exec/atlantis_generate_repo_config_test.go
  • lychee.toml

📝 Walkthrough

Walkthrough

Atmos now manages GIF/MP4 renderers, centralizes terminal-width fallback behavior, and updates related tests, documentation, maintenance guidance, portability handling, and CI configuration.

Changes

Managed cast rendering

Layer / File(s) Summary
Toolchain resolution and execution
pkg/asciicast/render_toolchain.go, errors/errors.go
Managed agg and FFmpeg tools are installed, resolved to absolute paths, executed through Atmos IO, and reported with sentinel errors.
Render pipeline integration
pkg/asciicast/render.go
Render targets identify required tools once per render operation and pass resolved paths to GIF and MP4 rendering.
Renderer and cast failure coverage
pkg/asciicast/*_test.go, pkg/runner/step/cast_test.go, cmd/cast/recording_test.go
Tests cover injected tool resolution, execution failures, output collisions, caching, IO masking, and deterministic finalization failures.
Managed renderer documentation
website/docs/cli/commands/cast/render.mdx, website/docs/cli/global-flags.mdx, website/docs/workflows/workflows/workflow/steps/type/cast.mdx
Cast documentation describes automatic renderer installation and GIF-derived MP4 generation.

Terminal width resolution

Layer / File(s) Summary
Shared terminal width behavior
pkg/terminal/terminal.go, internal/tui/templates/templater.go, cmd/root.go
Terminal width resolution now honors COLUMNS, preserves recording-width precedence, and applies shared fallback behavior.
Terminal width test coverage
pkg/terminal/*_test.go, internal/tui/templates/templater_test.go, cmd/root_helpers_test.go, pkg/ui/formatter_test.go, tests/cli_test.go
Tests verify COLUMNS, TTY precedence, template width calculation, formatter behavior, and isolated CLI environments.

Test maintenance policy

Layer / File(s) Summary
Failure handling and timeout guidance
.claude/agents/test-coverage-fix.md, .claude/skills/test-coverage/SKILL.md, .claude/skills/fix-all/SKILL.md, docs/fixes/2026-07-18-pr-maintenance-loop-pre-existing-test-timeout.md
Maintenance guidance now permits one safe fix attempt for each failing test per cycle and documents extended patch-scoped test timeouts.
Maintenance validation and portability
tests/test-cases/toolchain.yaml, tests/preconditions.go, tests/precondition_cached_tools_test.go, .github/workflows/codeql.yml, docs/fixes/*, internal/exec/atlantis_generate_repo_config_test.go, lychee.toml
Toolchain snapshots, Windows cached executable naming, lint verification, repository test setup, maintenance notes, and link-check exclusions are updated.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested labels: minor

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.14% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: automatic installation of animated renderers for cast outputs.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch osterman/auto-install-cast-renderers

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.

@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: 3

🤖 Prompt for all review comments with 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.

Inline comments:
In `@pkg/asciicast/render_toolchain.go`:
- Around line 39-57: Replace the mutable package-level hooks in
pkg/asciicast/render_toolchain.go:39-57 with an injected interface covering
dependency installation and render-tool binary resolution, and update the
relevant renderer flow to consume that interface. In
pkg/asciicast/render_test.go:452-469, remove global hook mutation and use
generated mockgen mocks for the interface; preserve the existing test behavior
through dependency injection.
- Line 129: Update the renderer execution return in the relevant toolchain
function to detect cmd.Run() failures and wrap them with the appropriate static
sentinel from errors/errors.go using %w, preserving the original error and
identifying the managed renderer that failed.
- Around line 126-128: Update the subprocess stream assignments in the command
execution flow to use the Atmos IO helpers instead of direct os.Stdout and
os.Stderr. Route command data through the data helper’s stdout stream and
renderer/UI messages through the ui helper’s stderr stream, preserving the CLI’s
data/UI separation and test capture.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7286a0f8-ca3b-41ad-bee0-4a4ab235ea34

📥 Commits

Reviewing files that changed from the base of the PR and between e6ad3a3 and 520dd52.

📒 Files selected for processing (8)
  • cmd/cast/recording_test.go
  • pkg/asciicast/render.go
  • pkg/asciicast/render_test.go
  • pkg/asciicast/render_toolchain.go
  • pkg/runner/step/cast_test.go
  • website/docs/cli/commands/cast/render.mdx
  • website/docs/cli/global-flags.mdx
  • website/docs/workflows/workflows/workflow/steps/type/cast.mdx

Comment thread pkg/asciicast/render_toolchain.go
Comment thread pkg/asciicast/render_toolchain.go Outdated
Comment thread pkg/asciicast/render_toolchain.go Outdated
@mergify

mergify Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Important

Cloud Posse Engineering Team Review Required

This pull request modifies files that require Cloud Posse's review. Please be patient, and a core maintainer will review your changes.

To expedite this process, reach out to us on Slack in the #pr-reviews channel.

Address CodeRabbit review feedback on render_toolchain.go: wire the
managed renderer subprocess (agg/ffmpeg) stdout/stderr through
iolib.GetContext() instead of os.Stdout/os.Stderr directly, and wrap
cmd.Run() failures with a static ErrRenderToolExecFailed sentinel so
callers can identify which renderer failed.

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

codecov Bot commented Jul 18, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.79245% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.77%. Comparing base (19e71dc) to head (65e7f7b).

Files with missing lines Patch % Lines
pkg/asciicast/render_toolchain.go 77.41% 13 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #2763      +/-   ##
==========================================
- Coverage   81.78%   81.77%   -0.01%     
==========================================
  Files        1750     1751       +1     
  Lines      167186   167259      +73     
==========================================
+ Hits       136729   136775      +46     
- Misses      22952    22974      +22     
- Partials     7505     7510       +5     
Flag Coverage Δ
unittests 81.77% <86.79%> (-0.01%) ⬇️

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

Files with missing lines Coverage Δ
cmd/root.go 77.77% <ø> (ø)
errors/errors.go 100.00% <ø> (ø)
internal/tui/templates/templater.go 68.42% <100.00%> (-0.33%) ⬇️
pkg/asciicast/render.go 98.40% <100.00%> (+0.05%) ⬆️
pkg/terminal/terminal.go 90.24% <100.00%> (+0.63%) ⬆️
tests/preconditions.go 64.95% <100.00%> (+0.68%) ⬆️
pkg/asciicast/render_toolchain.go 77.41% <77.41%> (ø)

... and 8 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Raise the patch-scoped test-coverage go test timeout to 40m (matching
.atmos.d/test.yaml) since the untouched-package ./tests binary was
tripping Go's 10m default under load, not actually failing. Also drop
the "pre-existing means don't touch" carve-out in test-coverage-fix,
test-coverage, and fix-all: the loop now attempts a confident fix on
any failing test in a scoped package, whether or not this patch broke
it, and only reports rather than fixes when it can't do so safely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions github-actions Bot added size/l Large size PR and removed size/m Medium size PR labels Jul 18, 2026
Re-ran atmos fix coverage against a patch touching tests/ after the
timeout fix landed: STATUS: OK, full ./tests package completed
cleanly with no false-positive panic. Updates the fix-log's
Follow-ups section from deferred to validated, per CodeRabbit's
review-thread request not to leave it open-ended.

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

🧹 Nitpick comments (2)
pkg/asciicast/render_toolchain_test.go (2)

77-129: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use testify/require for cleaner assertions.

Nice work on the I/O masking coverage. We can drop the manual if and t.Fatal blocks in favor of testify/require to make the assertions pop and fail loudly. As per coding guidelines, "Safety checks ... must fail loudly with require.Positive or an equivalent assertion".

♻️ Proposed refactor
 func TestRunRendererRoutesOutputThroughIOMasking(t *testing.T) {
 	exe, err := os.Executable()
-	if err != nil {
-		t.Fatal(err)
-	}
+	require.NoError(t, err)
 	t.Setenv(asciicastExecHelperEnv, "ok")
 
 	iolib.RegisterSecret("stdout line")
 	iolib.RegisterSecret("stderr line")
 
 	stdoutR, stdoutW, err := os.Pipe()
-	if err != nil {
-		t.Fatal(err)
-	}
+	require.NoError(t, err)
 	stderrR, stderrW, err := os.Pipe()
-	if err != nil {
-		t.Fatal(err)
-	}
+	require.NoError(t, err)
 	origStdout, origStderr := os.Stdout, os.Stderr
 	os.Stdout, os.Stderr = stdoutW, stderrW
 	t.Cleanup(func() {
 		os.Stdout, os.Stderr = origStdout, origStderr
 	})
 
 	runErr := runRenderer(exe)
 
 	_ = stdoutW.Close()
 	_ = stderrW.Close()
 	stdoutBytes, readErr := io.ReadAll(stdoutR)
-	if readErr != nil {
-		t.Fatal(readErr)
-	}
+	require.NoError(t, readErr)
 	stderrBytes, readErr := io.ReadAll(stderrR)
-	if readErr != nil {
-		t.Fatal(readErr)
-	}
+	require.NoError(t, readErr)
 
-	if runErr != nil {
-		t.Fatalf("runRenderer: %v", runErr)
-	}
+	require.NoError(t, runErr)
-	if strings.Contains(string(stdoutBytes), "stdout line") {
-		t.Fatalf("expected masked stdout (raw secret leaked), got %q", stdoutBytes)
-	}
+	require.NotContains(t, string(stdoutBytes), "stdout line", "expected masked stdout (raw secret leaked)")
-	if !strings.Contains(string(stdoutBytes), iolib.MaskReplacement) {
-		t.Fatalf("expected stdout to contain the mask replacement, got %q", stdoutBytes)
-	}
+	require.Contains(t, string(stdoutBytes), iolib.MaskReplacement, "expected stdout to contain the mask replacement")
-	if strings.Contains(string(stderrBytes), "stderr line") {
-		t.Fatalf("expected masked stderr (raw secret leaked), got %q", stderrBytes)
-	}
+	require.NotContains(t, string(stderrBytes), "stderr line", "expected masked stderr (raw secret leaked)")
-	if !strings.Contains(string(stderrBytes), iolib.MaskReplacement) {
-		t.Fatalf("expected stderr to contain the mask replacement, got %q", stderrBytes)
-	}
+	require.Contains(t, string(stderrBytes), iolib.MaskReplacement, "expected stderr to contain the mask replacement")
 }
🤖 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/asciicast/render_toolchain_test.go` around lines 77 - 129, Update
TestRunRendererRoutesOutputThroughIOMasking to use testify/require assertions
instead of manual t.Fatal, if checks, and string conditions. Replace error
checks with require.NoError and validate masked output with require.NotContains
and require.Contains, preserving the existing assertion messages where
practical.

Source: Coding guidelines


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

Use table-driven tests and testify/require.

Good test coverage here. To perfectly align with the project guidelines, let's roll these three related execution scenarios into a single table-driven test and trim the manual error checks using testify/require. As per coding guidelines, "Use table-driven tests for testing multiple scenarios" and "Safety checks ... must fail loudly with require.Positive or an equivalent assertion".

(Remember to run goimports or manually pull in "github.com/stretchr/testify/require" if you apply this.)

♻️ Proposed refactor to a table-driven test
-// TestRunRendererRejectsEmptyBinary covers the guard clause in runRenderer
-// that refuses to exec.Command with an empty path. This protects against a
-// resolveRenderTools bug that returns a renderTools with an unset field
-// (e.g. tools.ffmpeg left empty) reaching exec.Command, which would otherwise
-// surface as a confusing "file not found" error instead of a clear signal
-// that the managed renderer path was never resolved.
-func TestRunRendererRejectsEmptyBinary(t *testing.T) {
-	err := runRenderer("")
-	if !errors.Is(err, errUtils.ErrToolInstall) {
-		t.Fatalf("expected wrapped ErrToolInstall for empty binary, got %v", err)
-	}
-}
-
-// TestRunRendererWrapsExecutionFailure asserts that a failing renderer
-// process surfaces as the dedicated errUtils.ErrRenderToolExecFailed
-// sentinel, with the binary path folded into the message, so callers can use
-// errors.Is to distinguish "renderer process failed" from other failure
-// modes (e.g. errUtils.ErrToolInstall for a renderer that couldn't be
-// resolved/installed at all).
-func TestRunRendererWrapsExecutionFailure(t *testing.T) {
-	exe, err := os.Executable()
-	if err != nil {
-		t.Fatal(err)
-	}
-	t.Setenv(asciicastExecHelperEnv, "fail")
-
-	runErr := runRenderer(exe)
-	if runErr == nil {
-		t.Fatal("expected error from failing renderer process")
-	}
-	if !errors.Is(runErr, errUtils.ErrRenderToolExecFailed) {
-		t.Fatalf("expected ErrRenderToolExecFailed, got %v", runErr)
-	}
-	if !strings.Contains(runErr.Error(), exe) {
-		t.Fatalf("expected error to name the failed binary %q, got %v", exe, runErr)
-	}
-}
-
-// TestRunRendererSucceedsWithoutError confirms the happy path returns nil
-// once the subprocess exits cleanly, exercising the fall-through after
-// cmd.Run() succeeds (the counterpart to the failure-wrapping test above).
-func TestRunRendererSucceedsWithoutError(t *testing.T) {
-	exe, err := os.Executable()
-	if err != nil {
-		t.Fatal(err)
-	}
-	t.Setenv(asciicastExecHelperEnv, "ok")
-
-	if err := runRenderer(exe); err != nil {
-		t.Fatalf("expected success, got %v", err)
-	}
-}
+func TestRunRendererExecution(t *testing.T) {
+	exe, err := os.Executable()
+	require.NoError(t, err)
+
+	tests := []struct {
+		name    string
+		binary  string
+		env     string
+		wantErr error
+		wantMsg string
+	}{
+		{
+			// Covers the guard clause in runRenderer that refuses to exec.Command with an empty path.
+			name:    "empty binary",
+			binary:  "",
+			wantErr: errUtils.ErrToolInstall,
+		},
+		{
+			// Asserts that a failing renderer process surfaces as the dedicated ErrRenderToolExecFailed sentinel.
+			name:    "execution failure",
+			binary:  exe,
+			env:     "fail",
+			wantErr: errUtils.ErrRenderToolExecFailed,
+			wantMsg: exe,
+		},
+		{
+			// Confirms the happy path returns nil once the subprocess exits cleanly.
+			name:   "success",
+			binary: exe,
+			env:    "ok",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if tt.env != "" {
+				t.Setenv(asciicastExecHelperEnv, tt.env)
+			}
+			err := runRenderer(tt.binary)
+			if tt.wantErr != nil {
+				require.ErrorIs(t, err, tt.wantErr)
+				if tt.wantMsg != "" {
+					require.ErrorContains(t, err, tt.wantMsg)
+				}
+			} else {
+				require.NoError(t, 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 `@pkg/asciicast/render_toolchain_test.go` around lines 14 - 65, Consolidate
TestRunRendererRejectsEmptyBinary, TestRunRendererWrapsExecutionFailure, and
TestRunRendererSucceedsWithoutError into one table-driven test covering the
three scenarios. Use testify/require for setup and assertions, including
executable lookup, expected errors, and message checks, while preserving each
case’s environment, input, and expected outcome. Add the require import and keep
the existing helper symbols and sentinel validations.

Source: Coding guidelines

🤖 Prompt for all review comments with 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.

Inline comments:
In @.claude/agents/test-coverage-fix.md:
- Around line 47-54: Update the Step 2 scope in the test-failure workflow so it
applies to every attempted failure, including pre-existing failures, rather than
only “in-scope” failures. Preserve the required reproduce, diagnosis, fix, and
full-package verification steps for all failures, while retaining the existing
stop-and-report path for issues that cannot be safely fixed.

In @.claude/skills/test-coverage/SKILL.md:
- Around line 59-70: Require the test-coverage workflow to enter coverage-gap
analysis only after all scoped tests pass; if any failure remains after one fix
attempt, stop and invoke the human-attention path. Update the failure-handling
guidance in .claude/skills/test-coverage/SKILL.md lines 59-70 and the Section B
transition in .claude/agents/test-coverage-fix.md lines 78-81, preserving the
existing reporting and say-skill requirements.

---

Nitpick comments:
In `@pkg/asciicast/render_toolchain_test.go`:
- Around line 77-129: Update TestRunRendererRoutesOutputThroughIOMasking to use
testify/require assertions instead of manual t.Fatal, if checks, and string
conditions. Replace error checks with require.NoError and validate masked output
with require.NotContains and require.Contains, preserving the existing assertion
messages where practical.
- Around line 14-65: Consolidate TestRunRendererRejectsEmptyBinary,
TestRunRendererWrapsExecutionFailure, and TestRunRendererSucceedsWithoutError
into one table-driven test covering the three scenarios. Use testify/require for
setup and assertions, including executable lookup, expected errors, and message
checks, while preserving each case’s environment, input, and expected outcome.
Add the require import and keep the existing helper symbols and sentinel
validations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e7070701-aace-4963-a56a-a650e65f0a86

📥 Commits

Reviewing files that changed from the base of the PR and between d2eb7ef and 3fe8c2f.

📒 Files selected for processing (12)
  • .claude/agents/test-coverage-fix.md
  • .claude/skills/fix-all/SKILL.md
  • .claude/skills/test-coverage/SKILL.md
  • .claude/skills/test-coverage/scripts/patch-test-coverage.sh
  • docs/fixes/2026-07-18-pr-maintenance-loop-pre-existing-test-timeout.md
  • docs/fixes/2026-07-18-windows-cached-test-tool-exe-suffix.md
  • lychee.toml
  • pkg/asciicast/render_test.go
  • pkg/asciicast/render_toolchain_test.go
  • tests/cli_remote_imports_test.go
  • tests/precondition_cached_tools_test.go
  • tests/preconditions.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/asciicast/render_test.go

Comment thread .claude/agents/test-coverage-fix.md
Comment thread .claude/skills/test-coverage/SKILL.md Outdated
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 19, 2026
@mergify

mergify Bot commented Jul 19, 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 Jul 19, 2026
…-cast-renderers

# Conflicts:
#	.claude/skills/test-coverage/scripts/patch-test-coverage.sh
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 19, 2026
@aknysh
Andriy Knysh (aknysh) merged commit 6845ae3 into main Jul 19, 2026
88 checks passed
@aknysh
Andriy Knysh (aknysh) deleted the osterman/auto-install-cast-renderers branch July 19, 2026 21:57
@atmos-pro

atmos-pro Bot commented Jul 19, 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.

@mergify mergify Bot removed the needs-cloudposse Needs Cloud Posse assistance label Jul 19, 2026
@github-actions

Copy link
Copy Markdown

These changes were released in v1.223.1-rc.2.

This branch was successfully deployed

1 active deployment
preview — 65e7f7b1 Deployed Jul 19, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

patch A minor, backward compatible change size/l Large size PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants