Skip to content

fix(ci): use terraform exit code as the source of truth for CI status - #2382

Merged
Erik Osterman (Cloud Posse) (osterman) merged 9 commits into
mainfrom
osterman/ci-summary-error-status
May 3, 2026
Merged

Erik Osterman (Cloud Posse) (osterman) merged 9 commits into
mainfrom
osterman/ci-summary-error-status

Conversation

@osterman

@osterman Erik Osterman (Cloud Posse) (osterman) commented May 1, 2026 •

Copy link
Copy Markdown
Member

what

  • Make the terraform exit code the authoritative signal for success/failure (and, for terraform plan with -detailed-exitcode, for change detection) in the CI summary path. Text parsing of stdout/stderr is downgraded to enrichment only — it still extracts resource counts, output values, and error message bodies, but no longer drives the binary HasErrors / HasChanges decisions.
  • Plumb the exit code through cmd/terraform/utils.go → pkg/hooks RunCIHooks → pkg/ci ExecuteOptions → plugin.HookContext so the plugin handler has a clean signal independent of output format.
  • Rewrite parseOutputWithError (pkg/ci/plugins/terraform/handlers.go) so that:
    • apply/deploy: HasErrors = (exitCode != 0)
    • plan: HasErrors = (exitCode == 1); exitCode == 2 also implies HasChanges
    • other commands: HasErrors = (exitCode != 0)
    • exit-code success discards spurious "Error:" matches from text; exit-code failure still falls back to CommandError.Error() for the body if text parsing didn't find one.
  • Wire the enriched *plugin.OutputResult from parseOutputWithError through writeSummary and buildTemplateContext (it had been silently dropped — writeSummary had _ *plugin.OutputResult as its second arg, and buildTemplateContext re-parsed ctx.Output from scratch). buildTemplateContext keeps a nil-fallback so legacy callers continue to work.
  • Refactor RunCIHooks to take a *RunCIHooksOptions struct (per the repo's options pattern) since the parameter list grew past the linter's max-args limit.
  • Add tests covering all the new branches: exit-code-only failure rendering, exit-code 2 → HasChanges for plan, apply exit 0 with stray Error: in output → no error, plus the original failure-summary tests for plan/apply/deploy.

why

  • Reported regression: atmos terraform deploy <component> -s <stack> --upload-status failing at the authentication step (before terraform itself ran, exit code 1) still produced a job summary that read ## No Changes Applied for eks/karpenter-node-pool in e98d-gov-use1-dss with a NO CHANGE badge. The check run was correctly marked failed, but the summary contradicted it.
  • Root cause was architectural: the CI summary path used text parsing as the primary source of truth for failure/change state. The auth-failure stderr did not match ExtractErrors's ^Error: regex (it's emitted as **Error:** in markdown form), and writeSummary silently dropped the already-enriched OutputResult, so the apply template fell through to the no-changes branch. Anything that fails before terraform runs — auth, OOM, signal kill, network — would have hit the same bug.
  • Terraform exit codes are well-defined and stable (apply: 0 = success / non-zero = error; plan -detailed-exitcode: 0/1/2). Using them as the authoritative signal makes the hook robust against output-format drift between Terraform and OpenTofu, and against any pre-terraform failure that produces no parseable output. errUtils.GetExitCode already unwraps exec.ExitError, ExecError, exitCoder, and WorkflowStepError, so the existing error chains carry it through without further plumbing.

references

  • Affected handlers: pkg/ci/plugins/terraform/handlers.go (parseOutputWithError, writeSummary).
  • Affected helper: pkg/ci/plugins/terraform/plugin.go (buildTemplateContext).
  • Plumbing: pkg/ci/internal/plugin/types.go, pkg/ci/executor.go, pkg/hooks/hooks.go, cmd/terraform/utils.go.
  • Templates (unchanged): pkg/ci/plugins/terraform/templates/{apply,plan}.md already had {{ if .Result.HasErrors }} branches; they just weren't being reached.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Improved error detection and failure reporting by treating command exit codes as the authoritative indicator of success/failure, fixing edge cases where errors occur before terraform produces output.
    • Enhanced CI/check-run status accuracy for plan and apply operations, properly handling plan changes and command execution failures.
  • Tests

    • Added comprehensive test coverage for exit code handling, error state reconciliation, and CI hook execution workflows.

…ore terraform runs

When `atmos terraform deploy` fails at authentication (or any pre-terraform step), the GitHub Actions job summary used to render "No Changes Applied" with a NO CHANGE badge instead of an APPLY-FAILED badge.

The handler enriched the OutputResult with the command error via parseOutputWithError, but writeSummary discarded that argument and buildTemplateContext re-parsed the raw output, so Result.HasErrors was false and the template fell through to the no-changes branch. Thread the enriched result through writeSummary into buildTemplateContext (with a nil fallback) so the failure branch is rendered correctly. Same fix applies to plan and apply.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@atmos-pro

atmos-pro Bot commented May 1, 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.

View pull request changes on Atmos Pro

@github-actions github-actions Bot added the size/m Medium size PR label May 1, 2026
@github-actions

github-actions Bot commented May 1, 2026 •

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues found.

Scanned Files

None

@coderabbitai

coderabbitai Bot commented May 1, 2026 •

Copy link
Copy Markdown
Contributor

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: 7076a742-995a-4587-9bbf-8d7e13a72e66

📥 Commits

Reviewing files that changed from the base of the PR and between b280f38 and 228608c.

📒 Files selected for processing (3)
  • cmd/terraform/utils_exit_wrapping_test.go
  • cmd/terraform/utils_hooks_test.go
  • pkg/hooks/hooks_test.go
✅ Files skipped from review due to trivial changes (2)
  • cmd/terraform/utils_hooks_test.go
  • pkg/hooks/hooks_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/terraform/utils_exit_wrapping_test.go

📝 Walkthrough

Walkthrough

Exit codes from Terraform commands are now extracted at the command execution boundary and propagated authoritatively through the CI hooks system. Terraform plan's exit code 2 (changes detected) is treated as success despite being non-zero, and error reconciliation happens before template rendering instead of during output parsing. Tests validate the complete flow from subprocess exit codes to template context.

Changes

Exit Code Authority & Propagation

Layer / File(s) Summary
Type System
pkg/ci/internal/plugin/types.go, pkg/ci/executor.go, pkg/hooks/hooks.go
HookContext gains ExitCode field. ExecuteOptions and RunCIHooksOptions add ExitCode to carry process termination status. RunCIHooks refactored from positional to options struct API.
Command Boundary
cmd/terraform/utils.go
Exit codes extracted via errUtils.GetExitCode(cmdErr) at command execution points and passed into RunCIHooksOptions for error and success paths.
Handler Reconciliation
pkg/ci/plugins/terraform/handlers.go
parseOutputWithError uses ctx.ExitCode as authoritative source: plan exit code 2 signals success (changes detected), exit code 1 signals errors, exit code 0 signals success. Non-nil ctx.CommandError treated as failure except for plan exit code 2. writeSummary passes enriched result to buildTemplateContext.
Template Context
pkg/ci/plugins/terraform/plugin.go
buildTemplateContext accepts optional result parameter; when provided, template derives error state from passed result instead of re-parsing output.
Subprocess Test Support
cmd/terraform/testmain_test.go
TestMain checks _ATMOS_TEST_EXIT_ONE env var to inject exit code 1 for subprocess testing without real logic execution.
Integration & Flow Tests
cmd/terraform/utils_exit_wrapping_test.go, cmd/terraform/utils_hooks_test.go, pkg/hooks/hooks_test.go
Tests verify exit code wrapping at command boundary, hook option construction, and options API integration.
Semantics & Reconciliation Tests
pkg/ci/plugins/terraform/handlers_test.go, pkg/ci/plugins/terraform/plugin_test.go
Tests enforce exit-code precedence for plan/apply, validate error state in check-run resolution and template rendering, and verify summary output under failure conditions.

Sequence Diagram

sequenceDiagram
    participant CLI as Terraform CLI Executor
    participant Boundary as Command Boundary<br/>(utils.go)
    participant Hooks as Hooks System<br/>(hooks.go)
    participant CI as CI Executor<br/>(executor.go)
    participant Plugin as Plugin Handler<br/>(handlers.go)
    participant Template as Template Renderer<br/>(plugin.go)

    CLI->>Boundary: Execute command, get output/error
    Boundary->>Boundary: Extract exit code from error
    Boundary->>Hooks: RunCIHooks(options with ExitCode)
    Hooks->>CI: ci.Execute(options with ExitCode)
    CI->>Plugin: buildHookContext with exit code
    Plugin->>Plugin: parseOutputWithError<br/>(uses ctx.ExitCode as authority)
    alt Plan with exit code 2
        Plugin->>Plugin: Treat as success (changes)
    else Exit code 1 or non-nil error
        Plugin->>Plugin: Mark errors/failure
    else Exit code 0
        Plugin->>Plugin: Success, clear errors
    end
    Plugin->>Template: writeSummary(enriched result)
    Template->>Template: buildTemplateContext<br/>(use result, not re-parse)
    Template->>Template: Render with reconciled error state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

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 60.00% 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 accurately summarizes the main objective: making Terraform exit codes the authoritative signal for CI status determination.
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.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch osterman/ci-summary-error-status

Tip

💬 Introducing Slack Agent: Turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get your free trial and get 200 agent minutes per Slack user (a $50 value).


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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

@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.

🧹 Nitpick comments (1)
pkg/ci/plugins/terraform/handlers_test.go (1)

1208-1265: ⚡ Quick win

Consider table-driving the three failure-summary scenarios.

These tests validate the same behavior shape across commands and are a good fit for one table-driven test to reduce duplication and future drift.

♻️ Suggested refactor sketch.
+func TestFailureSummaryRendering_WithCommandError(t *testing.T) {
+  p := &Plugin{}
+  tests := []struct {
+    name          string
+    command       string
+    wantHeader    string
+    wantBadgePart string
+    notHeader     string
+  }{
+    {"plan", "plan", "Plan Failed for", "PLAN-FAILED-ff0000", "No Changes for"},
+    {"apply", "apply", "Apply Failed for", "APPLY-FAILED-ff0000", "No Changes Applied for"},
+    {"deploy", "deploy", "Apply Failed for", "APPLY-FAILED-ff0000", "No Changes Applied for"},
+  }
+
+  for _, tt := range tests {
+    t.Run(tt.name, func(t *testing.T) {
+      ctx := newFailureSummaryHookContext(tt.command, fmt.Errorf("identity failed: assume role denied"))
+      mp := ctx.Provider.(*mockProvider)
+      var err error
+      switch tt.command {
+      case "plan":
+        err = p.onAfterPlan(ctx)
+      case "apply":
+        err = p.onAfterApply(ctx)
+      default:
+        err = p.onAfterDeploy(ctx)
+      }
+      require.NoError(t, err)
+      require.Len(t, mp.writer.summaries, 1)
+      rendered := mp.writer.summaries[0]
+      assert.Contains(t, rendered, tt.wantHeader)
+      assert.Contains(t, rendered, tt.wantBadgePart)
+      assert.NotContains(t, rendered, tt.notHeader)
+      assert.NotContains(t, rendered, "NO_CHANGE-inactive")
+    })
+  }
+}

As per coding guidelines: **/*_test.go: “Use table-driven tests for testing multiple scenarios in Go.”.

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

In `@pkg/ci/plugins/terraform/handlers_test.go` around lines 1208 - 1265, Replace
the three nearly identical tests
TestOnAfterPlan_WithCommandError_RendersFailureSummary,
TestOnAfterApply_WithCommandError_RendersFailureSummary, and
TestOnAfterDeploy_WithCommandError_RendersFailureSummary with a single
table-driven test that iterates over cases for command
("plan","apply","deploy"), expected header substring ("Plan Failed for"/"Apply
Failed for"), expected badge ("PLAN-FAILED-ff0000"/"APPLY-FAILED-ff0000"), and
uses newFailureSummaryHookContext(cmd, err) to build ctx, calls the appropriate
handler (call p.onAfterPlan/onAfterApply/onAfterDeploy based on the case), and
asserts on mp := ctx.Provider.(*mockProvider); mp.writer.summaries length and
that the rendered summary contains the command-specific header, badge, the error
string "identity failed: assume role denied", and does not contain the no-change
strings; keep references to Plugin, newFailureSummaryHookContext, mockProvider,
and mp.writer.summaries so the assertions target the same symbols as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pkg/ci/plugins/terraform/handlers_test.go`:
- Around line 1208-1265: Replace the three nearly identical tests
TestOnAfterPlan_WithCommandError_RendersFailureSummary,
TestOnAfterApply_WithCommandError_RendersFailureSummary, and
TestOnAfterDeploy_WithCommandError_RendersFailureSummary with a single
table-driven test that iterates over cases for command
("plan","apply","deploy"), expected header substring ("Plan Failed for"/"Apply
Failed for"), expected badge ("PLAN-FAILED-ff0000"/"APPLY-FAILED-ff0000"), and
uses newFailureSummaryHookContext(cmd, err) to build ctx, calls the appropriate
handler (call p.onAfterPlan/onAfterApply/onAfterDeploy based on the case), and
asserts on mp := ctx.Provider.(*mockProvider); mp.writer.summaries length and
that the rendered summary contains the command-specific header, badge, the error
string "identity failed: assume role denied", and does not contain the no-change
strings; keep references to Plugin, newFailureSummaryHookContext, mockProvider,
and mp.writer.summaries so the assertions target the same symbols as before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e0494b83-e04f-46be-af6b-ea3af57d3cab

📥 Commits

Reviewing files that changed from the base of the PR and between 8965bc6 and 9be492e.

📒 Files selected for processing (4)
  • pkg/ci/plugins/terraform/handlers.go
  • pkg/ci/plugins/terraform/handlers_test.go
  • pkg/ci/plugins/terraform/plugin.go
  • pkg/ci/plugins/terraform/plugin_test.go

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 1, 2026
@osterman Erik Osterman (Cloud Posse) (osterman) added the patch A minor, backward compatible change label May 1, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 1, 2026
…ge detection

Plumb the exit code through HookContext (via RunCIHooks → ExecuteOptions →
buildHookContext) and use it as the source of truth in parseOutputWithError:

  - apply/deploy: HasErrors = (exitCode != 0)
  - plan: HasErrors = (exitCode == 1); exitCode == 2 also implies HasChanges
  - other commands: HasErrors = (exitCode != 0)

Text parsing is downgraded from the primary failure signal to enrichment
only — it still extracts resource counts, output values, and error message
bodies, but no longer drives the binary HasErrors / HasChanges decisions.
Exit code is authoritative both ways: when it says success, any spurious
"Error:" lines from text matching are discarded.

This is the structural fix behind the previous commit's symptom: the auth
failure leaked through because the regex `^Error:` did not match the
captured stderr. With exit code authoritative, any pre-terraform failure
(auth, OOM, killed by signal, anything) flips HasErrors=true correctly,
regardless of output format.

cmd/terraform/utils.go computes the exit code via errUtils.GetExitCode,
which already unwraps exec.ExitError, ExecError, exitCoder, and the
WorkflowStepError types — so the existing error chains carry it through
without further plumbing.

RunCIHooks now takes a *RunCIHooksOptions struct (per CLAUDE.md options
pattern) since the parameter list grew past the linter's max-args limit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@osterman Erik Osterman (Cloud Posse) (osterman) changed the title fix(ci): show "Apply Failed" summary when deploy errors before terraform runs fix(ci): use terraform exit code as the source of truth for CI status May 1, 2026

@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 the current code and only fix it if needed.

Inline comments:
In `@cmd/terraform/utils.go`:
- Around line 60-71: Add a unit test that verifies the command error preserves
its exit-code wrapper before it flows into RunCIHooks: call the routine that
produces cmdErr (e.g., ExecuteShellCommand or the function that returns cmdErr),
assert errors.As(err, &errUtils.ExitCodeError{}) on that real error path, and
then pass that err into the same RunCIHooks plumbing (or simulate building a
RunCIHooksOptions with CommandError: cmdErr and ExitCode:
errUtils.GetExitCode(cmdErr)) to ensure the wrapper contract is preserved;
reference symbols to locate code: ExecuteShellCommand (or the function creating
cmdErr), cmdErr, errUtils.GetExitCode, errUtils.ExitCodeError, and
h.RunCIHooks/RunCIHooksOptions.

In `@pkg/ci/plugins/terraform/handlers_test.go`:
- Around line 1321-1339: The test
TestOnAfterApply_WithExitCodeOnly_RendersFailureSummary currently sets
ctx.ExitCode=1 but leaves checks disabled, so updateCheckRun() (which calls
resolveCheckResult(ctx) that only inspects CommandError) is not exercised for
the exit-code-only failure; enable checks on the fixture (set ctx.ChecksEnabled
= true or use the provider/mock fixture flag that turns on checks) before
calling p.onAfterApply(ctx) and then assert that the mockProvider's check update
recorded a "failure" result (inspect mp.updateCheckCalls or similar mock check
record used by updateCheckRun), ensuring updateCheckRun(ctx) treats
ExitCode-only failures as failure. Also keep the existing summary assertions in
TestOnAfterApply_WithExitCodeOnly_RendersFailureSummary.

In `@pkg/ci/plugins/terraform/handlers.go`:
- Around line 287-305: The code currently treats any non-nil ctx.CommandError as
a failure which flips a legitimate Terraform "plan" exit code 2 into an error;
update the reconciliation in handlers.go so CommandError only forces
hasErrors/ExitCode change when the ExitCode is not 2: change the guard from "if
ctx.CommandError != nil { hasErrors = true ... }" to "if ctx.CommandError != nil
&& result.ExitCode != 2 { hasErrors = true; if result.ExitCode == 0 {
result.ExitCode = 1 } }" so a detailed-exitcode plan (ExitCode==2) is preserved,
and ensure the downstream block that populates result.Errors remains driven by
hasErrors (so it won’t inject the CommandError when ExitCode==2); also add a
regression test exercising runHooksOnErrorWithOutput to forward both
CommandError and ExitCode==2 and assert the result still indicates "changes
detected" (ExitCode 2, HasErrors false/no error body).
🪄 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: 300321e9-1959-47d1-808e-e48346970cd4

📥 Commits

Reviewing files that changed from the base of the PR and between 07d0af8 and 9a2d655.

📒 Files selected for processing (7)
  • cmd/terraform/utils.go
  • pkg/ci/executor.go
  • pkg/ci/internal/plugin/types.go
  • pkg/ci/plugins/terraform/handlers.go
  • pkg/ci/plugins/terraform/handlers_test.go
  • pkg/hooks/hooks.go
  • pkg/hooks/hooks_test.go

Comment thread cmd/terraform/utils.go
Comment thread pkg/ci/plugins/terraform/handlers_test.go
Comment thread pkg/ci/plugins/terraform/handlers.go Outdated
@codecov

codecov Bot commented May 1, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.08197% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.06%. Comparing base (39fd0a4) to head (228608c).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
cmd/terraform/utils.go 86.95% 0 Missing and 3 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #2382      +/-   ##
==========================================
+ Coverage   77.95%   78.06%   +0.10%     
==========================================
  Files        1090     1091       +1     
  Lines      103078   103248     +170     
==========================================
+ Hits        80355    80601     +246     
+ Misses      18316    18226      -90     
- Partials     4407     4421      +14     
Flag Coverage Δ
unittests 78.06% <95.08%> (+0.10%) ⬆️

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

Files with missing lines Coverage Δ
pkg/ci/executor.go 80.15% <100.00%> (+3.22%) ⬆️
pkg/ci/internal/plugin/types.go 100.00% <ø> (ø)
pkg/ci/plugins/terraform/handlers.go 59.15% <100.00%> (+9.26%) ⬆️
pkg/ci/plugins/terraform/plugin.go 93.68% <100.00%> (+0.06%) ⬆️
pkg/hooks/hooks.go 80.58% <100.00%> (+10.97%) ⬆️
cmd/terraform/utils.go 54.96% <86.95%> (+6.38%) ⬆️

... and 10 files with indirect coverage changes

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

… wrapped

Address CodeRabbit review on PR #2382:

- parseOutputWithError no longer flips HasErrors=true when ctx.CommandError
  is set for `terraform plan -detailed-exitcode` exit code 2. The shell
  wrapper turns the non-zero exit into an ExitCodeError, so CommandError is
  non-nil for the success case — the override regressed the summary from
  "Changes detected" to "Plan Failed".
- resolveCheckResult now treats non-zero ExitCode as failure too (with the
  same plan/2 carve-out) so the check run cannot disagree with the summary.
- New regression tests cover plan/2 + wrapped error, exit-code-only check
  runs, and the ExitCodeError wrapping contract at the cmd/terraform →
  internal/exec boundary.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 2, 2026
Address CodeRabbit follow-up + codecov patch-coverage gap (67% → ≥80%):

- cmd/terraform/utils_exit_wrapping_test.go now feeds the wrapped cmdErr
  produced by ExecuteShellCommand through a real RunCIHooksOptions and
  invokes h.RunCIHooks (ci.enabled=false short-circuits). This proves the
  ExitCodeError wrapper survives the entire boundary CodeRabbit asked
  about, not just the GetExitCode call.
- pkg/hooks/hooks_test.go gains tests covering RunCIHooks ForwardsErrorAndExitCode
  (nil/code 1/code 2), nil AtmosConfig, and experimental=disable error
  paths. Lifts pkg/hooks/RunCIHooks coverage from 55.6% → 100%.
- cmd/terraform/utils_hooks_test.go covers runHooks/runHooksOnError/
  runHooksOnErrorWithOutput/runCIHooksForDeploy against the demo-stacks
  fixture (which has ci.enabled=false → clean RunCIHooks short-circuit).
  Lifts those wrapper functions from 0% to 65–100%.

Co-Authored-By: Claude Opus 4.7 <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.

🧹 Nitpick comments (1)
cmd/terraform/utils_hooks_test.go (1)

49-79: ⚡ Quick win

Add a plain-error test case to cover the documented default fallback.

The table tests ExitCodeError{Code:1} and ExitCodeError{Code:2}, but the utils.go comment explicitly documents a third case: GetExitCode "returns 1 by default for non-nil errors with no attached code (e.g., auth failures)." This default is the whole motivation for the PR — pre-exec failures (auth, OOM, signals) produce plain errors with no wrapped exit code, and without this case there's no regression guard for it.

✅ Proposed addition
 import (
 	"testing"
+	"errors"
 
 	"github.com/spf13/cobra"
 	...
 )
 
 tests := []struct {
 	name    string
 	cmdErr  error
 	wantExt int
 }{
 	{
 		name:    "wrapped ExitCodeError code 1",
 		cmdErr:  errUtils.ExitCodeError{Code: 1},
 		wantExt: 1,
 	},
 	{
 		name:    "wrapped ExitCodeError code 2 (plan changes detected)",
 		cmdErr:  errUtils.ExitCodeError{Code: 2},
 		wantExt: 2,
 	},
+	{
+		name:    "plain error defaults to exit code 1 (e.g. auth failure)",
+		cmdErr:  errors.New("auth failure: credentials not found"),
+		wantExt: 1,
+	},
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/terraform/utils_hooks_test.go` around lines 49 - 79, Add a third table
test case that covers a plain non-wrapped error so GetExitCode falls back to 1:
add an entry with name like "plain error falls back to 1", cmdErr set to a plain
error (e.g., errors.New("auth failed")), and wantExt 1; in the same t.Run block
keep the assert.Equal(t, tc.wantExt, errUtils.GetExitCode(tc.cmdErr)) and call
runHooksOnError(hooks.AfterTerraformPlan, cmd,
[]string{"--stack","dev","myapp"}, tc.cmdErr) to exercise the short-circuit
path. Ensure you import/errors.New or use the existing errors package
consistently and mirror the structure of the other table entries.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@cmd/terraform/utils_hooks_test.go`:
- Around line 49-79: Add a third table test case that covers a plain non-wrapped
error so GetExitCode falls back to 1: add an entry with name like "plain error
falls back to 1", cmdErr set to a plain error (e.g., errors.New("auth failed")),
and wantExt 1; in the same t.Run block keep the assert.Equal(t, tc.wantExt,
errUtils.GetExitCode(tc.cmdErr)) and call
runHooksOnError(hooks.AfterTerraformPlan, cmd,
[]string{"--stack","dev","myapp"}, tc.cmdErr) to exercise the short-circuit
path. Ensure you import/errors.New or use the existing errors package
consistently and mirror the structure of the other table entries.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f67ff115-54c0-44dc-a575-f4002886a9d1

📥 Commits

Reviewing files that changed from the base of the PR and between b280f38 and a7531f5.

📒 Files selected for processing (3)
  • cmd/terraform/utils_exit_wrapping_test.go
  • cmd/terraform/utils_hooks_test.go
  • pkg/hooks/hooks_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/terraform/utils_exit_wrapping_test.go

@atmos-pro atmos-pro 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.

There were no affected stacks, therefore this is approved by Atmos Pro.

@atmos-pro atmos-pro 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.

There were no affected stacks, therefore this is approved by Atmos Pro.

@osterman
Erik Osterman (Cloud Posse) (osterman) merged commit e6c3d73 into main May 3, 2026
58 checks passed
@osterman
Erik Osterman (Cloud Posse) (osterman) deleted the osterman/ci-summary-error-status branch May 3, 2026 00:27
@atmos-pro

atmos-pro Bot commented May 3, 2026

Copy link
Copy Markdown
Contributor

Note

Atmos Pro  

Waiting for your GitHub Actions workflow to upload affected stacks.
Learn More.

@github-actions

github-actions Bot commented May 3, 2026

Copy link
Copy Markdown

These changes were released in v1.217.0-rc.4.

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/m Medium size PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants