Skip to main content

ci.comment

The ci.comment function posts a comment on the pull request, or on the commit when the run has no pull request. Give the comment a key and later runs update the same comment instead of adding a new one each time. Locally, the comment is previewed on stderr and never posted.

Usage​

ci.comment(body = "", key = "", behavior = "upsert", pr = 0, target = "auto", template = "", data = None)

Arguments​

body

The comment text, as Markdown in a string. Required unless template is given, and it cannot be combined with template or data, even when it is an empty string.

key

(Optional) A stable name for the comment. The comment carries a hidden marker built from the key, which later runs use to find it. Without a key, the call always creates a new comment.

behavior

(Optional) What to do when a comment with the key already exists. The value "upsert" (the default) updates it and creates one when none exists, "create" always adds a new comment, and "update" only changes a comment that already exists. The "update" behavior needs a key, because the key is how the comment is found. In CI, "update" fails when no comment with the key exists yet, and the local preview fails the same way.

pr
(Optional) The pull request number to comment on, as an integer. It defaults to the current pull request.
target

(Optional) Where the comment goes, as a string. The value "auto" (the default) posts to the pull request when one is known and to a comment on the commit (ci.context.sha) otherwise. The value "pr" always posts to the pull request and fails when none is known. The value "commit" always posts a comment on the commit, even when the run has a pull request. Any other value fails with an argument error.

template

(Optional) The name of a template file to render into the comment body, instead of passing body. It is a file name under ci.templates.base_path, or an absolute path. There is no default template, so data always needs a template.

data

(Optional) A dict that becomes the template context. Keys are read in the template as .key. Passing data without template fails with an argument error that says template= is required.

Templates​

A script can keep the layout of a comment in a template file and pass only the values:

  • A call with template= renders that file, with data as the context. A call with only body posts the text as written.
  • There is no configured default template. Every call that renders a template names it with template=.
  • The name is resolved against ci.templates.base_path. A relative base_path resolves against the Atmos base_path. When no file has exactly that name, the name with a .md suffix is tried.
  • Templates use Go template syntax with the same functions as the built-in plan templates. The data dict is the template context.
  • A key that the template reads but data does not contain fails the render and names the key, so a typo does not post <no value>.
  • The key, behavior, pr, and target arguments work the same with a rendered body.

See script report templates for the configuration.

Returns​

A struct that describes the comment:

id
The identifier of the comment. A comment that was only previewed locally has a synthetic id that starts at 1.
url
A link to the comment, as a string. It is empty when the comment was only previewed locally.
created
A boolean that is True when the call created a new comment and False when it updated one.
target
The place the comment landed, as a string: "pr" or "commit". With target = "auto" it tells you which one was chosen.

Behavior​

  • In CI, the comment is posted through the provider's API. A pull request comment needs pull-requests: write on GitHub Actions, and a commit comment needs contents: write.
  • A commit comment is attached to ci.context.sha. It appears on the commit page and, for a commit that belongs to an open pull request, in that pull request's timeline. Keys work the same way, so a keyed commit comment is updated on the next run for the same commit.
  • A run with no pull request, such as a push, comments on the commit with the default target = "auto". Under workflow_run and pull_request_target, the pull request number comes from the event payload, so "auto" still finds the pull request.
  • Locally, the comment is previewed on stderr and never posted. The returned url is empty.
  • The body is masked before it leaves the process.
  • Comments are opt-in. In CI the call is gated by ci.comments.enabled, which is off by default. When it is off, the call warns, renders locally, and the script continues.
  • Under pull_request_target and workflow_run, the comment is held only when the pull request comes from a fork (ci.context.pr.fork), unless ci.allow_unsafe_fork_execution is true. A held call warns and renders locally instead. See the fork gate.

Examples​

Keep one comment up to date​

ci.comment("### Plan\n\n3 to add, 1 to change, 0 to destroy.", key = "plan")

Every run updates the same comment, so the pull request never collects a stack of stale ones.

Comment on a push​

ci.comment("Deployed **" + ci.context.sha[:7] + "** to prod.", key = "deploy")

This needs no guard. On a pull request run it comments on the pull request. On a push run it comments on the commit.

Require a pull request​

ci.comment("Preview ready.", key = "preview", target = "pr")

The call fails when the run has no pull request, which is useful when a missing pull request is a bug in the workflow.

Post to a specific pull request​

result = ci.comment("Released in v1.4.0.", pr = 214)
ui.info("Posted " + result.url)

Always add a new comment​

ci.comment("Rebased onto main.", behavior = "create")

Render a comment from a template​

.atmos/ci/templates/release.md
### Release {{ .version }}

Deployed to {{ .stack }}.
ci.comment(template = "release.md", data = {"version": "1.4.0", "stack": "prod"}, key = "release")

Errors​

  • An API error, a missing token, or a pull request number that does not exist fails the script. See errors.
  • An unknown behavior or target fails with an argument error that names ci.comment.
  • Passing body together with template or data (an empty body counts), a data value that is not a dict, or data without template fails with an argument error.
  • target = "pr" with no pull request fails with a pull-request-unknown error.
  • A template that does not exist fails the script with an error that names the resolved path and the value of ci.templates.base_path. A template that reads a key missing from data, or that fails to render, fails the script.
  • ci describes the module and the shared behavior rules.
  • CI pull request comments configures the native comments that Atmos posts for commands.
  • ci.summary writes to the job summary, which needs no pull request.