Failed Commands and Policy Denials as CI Annotations
A CI job fails, and the run page says only "Process completed with exit code 1." To learn why, you open the job, scroll through the log, and hunt for the one error that matters. When the failure is a policy denial, the rule that blocked the run and the reason its author wrote are in that log too, out of sight of the person reviewing the pull request. Atmos now reports a failed command as a CI annotation, so the reason appears on the run page.
The Problem
A failed job hides its cause. CI providers show a step's exit status at a glance, but the explanation stays in the
log. That costs a reviewer a click and a search on every failure. For policy denials it is worse: the
policy author's guidance, such as "EIPs cannot be destroyed; use prevent_destroy instead," is only useful if
someone reads it.
The Fix
When a command fails in CI, Atmos reports the failure through the CI provider's annotation support:
- Policy denials produce one annotation per rule that denied the run. The title names the rule, and the message
is the rule's
reasonas plain text, or "Denied by<rule>." when the rule has none. - Any other failure produces one annotation from the error's title, message, and explanation.
- Location: when Atmos knows the file and line where the cause is defined, the annotation is anchored there.
- Scripts: a nested Atmos command that fails inside a script reports its own failure once. The script that called it does not repeat it.
On GitHub Actions, these are ::error workflow commands, which appear in the run's annotations:
::error title=Policy protect-prod/guard denied execution::Production components cannot be changed from this workstation.
How to Use It
Failure annotations follow the same switches as other CI annotations:
ci:
enabled: true
annotations:
enabled: true # default when ci.enabled is true
A command policy is evaluated before atmos.yaml loads. A denial at that point
is annotated whenever a CI provider is detected, because an annotation is the only CI signal it can produce.
Try it out
- Install Atmos
- Try the example
Copy the example into a new directory and make it your own:
atmos init examples/policies
cd policies
Only Atmos is required. Preview a failure annotation locally by running an intentionally denied command:
ATMOS_POLICY_DIR=./policy.d GITHUB_ACTIONS=true atmos terraform destroy demo -s local
ATMOS_POLICY_DIR selects the example's policy. GITHUB_ACTIONS=true enables GitHub provider detection for this
command, so Atmos emits the following annotation on stderr alongside the normal error output:
::error title=Policy local-safety/deny-destroy denied execution::Destructive Terraform operations are disabled in this example. Use a reviewed teardown process instead.
The terminal displays this raw workflow-command text. In GitHub Actions, the runner renders it as an annotation.
The command intentionally exits with code 77: the admission policy blocks it before Terraform runs or any
infrastructure is accessed. No Terraform installation or cloud credentials are needed. This denial occurs before
atmos.yaml loads, so the preview does not require ci.enabled configuration.
Get Involved
Enable CI annotations in one workflow, trigger a policy denial, and share feedback in GitHub Discussions.
