Run Tessl Code Review in GitHub Actions and publish one native pull-request review.
The Action handles pull-request resolution, exact-head checkout, Tessl CLI setup, review publication, stale-head protection, idempotency, failure notices, and result artifacts. Your workflow retains control of triggers, concurrency, permissions, secrets, runners, timeouts, and branch protection.
Store a Tessl API token as the TESSL_TOKEN repository secret, then add this
workflow:
name: Tessl Code Review
on:
pull_request:
types: [opened, reopened, ready_for_review]
permissions:
contents: read
checks: write
issues: write
pull-requests: write
concurrency:
group: tessl-code-review-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
review:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: tesslio/code-review-action@v1
with:
tessl-token: ${{ secrets.TESSL_TOKEN }}v1 is the major tag. It moves to each 1.x release, so a fix reaches the
repository without editing the workflow. To freeze the revision instead, pin the
full commit SHA a release's notes provide, and accept that updates then need a
deliberate bump.
The Action checks out the pull-request head itself, so the calling job does not need a separate checkout step.
For the full setup walkthrough, gate configuration and repository settings, security posture, update and removal procedures, and troubleshooting, see Set up Tessl Code Review.
The default configuration runs the standard profile in advisory mode.
- id: review
uses: tesslio/code-review-action@v1
with:
tessl-token: ${{ secrets.TESSL_TOKEN }}
profile: standard
mode: advisory
lenses: >-
["tessl/code-review@0.0.3#review-security-and-privacy","tessl/code-review@0.0.3#review-correctness-and-data-integrity"]To route lenses with a repository YAML profile, pass its path explicitly:
with:
tessl-token: ${{ secrets.TESSL_TOKEN }}
profile: ./.tessl-code-review.yml
mode: advisoryProfiles are not discovered automatically. Omitting profile continues to use
standard.
Passing lenses replaces the profile's default lens selection with the exact
ordered JSON array. A review supports at most 8 lenses. Pin registry
references so the review does not change when a plugin publishes a new
version. The selected profile owns review implementation details, which are
not supported customer workflow configuration.
Reviews are published as github-actions[bot] by default. To publish under
another identity, mint a token for it and pass it as github-token:
- uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2.2.2
id: reviewer
with:
app-id: ${{ vars.REVIEWER_APP_ID }}
private-key: ${{ secrets.REVIEWER_APP_PRIVATE_KEY }}
- uses: tesslio/code-review-action@v1
with:
tessl-token: ${{ secrets.TESSL_TOKEN }}
github-token: ${{ steps.reviewer.outputs.token }}The identity needs pull-requests write and contents read on the repository. It
authors the review and nothing else: the comment reaction, the check run and
the failure notice stay on the workflow's own token, so it needs no permission
beyond reviewing. A machine-user token works the same way. These permissions
belong to the identity, granted by the App installation or to the machine user;
the permissions block in the workflow examples grants the workflow's own
token and does not reach them.
Contents write is optional. GitHub gates thread reopening behind it, so an identity holding contents write reopens a resolved thread that still carries a finding requesting changes, while one limited to contents read republishes that finding as a new inline comment instead. The reviewer probes each thread as it reviews, so reopening is the only review behavior the grant adds. The grant is not review-scoped, though: contents write is GitHub's general write access to repository content, so weigh it against what else the identity could then do.
An App that authors pull requests in the same repository should also be named
in approver-logins if its comments are to request approvals, since an App
comments with author association NONE whatever its permissions.
See Action contract for supported customer configuration and outputs.
The quick-start workflow reviews a pull request when it opens, reopens, or leaves draft. The Action also supports comment-driven and manually dispatched runs, and it resolves the current head itself for each of them, so a review always covers the latest commit rather than the one that opened the pull request. The workflows below can be added alongside the quick start or instead of it.
Every published review closes by asking for a mention once fixes or replies are
ready. @tessl-code-review is text in a comment rather than a GitHub account,
so recognizing it is the workflow's job:
name: Tessl Code Review (requested)
on:
issue_comment:
types: [created]
permissions:
contents: read
checks: write
issues: write
pull-requests: write
concurrency:
group: tessl-code-review-${{ github.event.issue.number }}
cancel-in-progress: false
jobs:
review:
# A coarse prefilter, and only that: it keeps a runner from starting for
# every comment in the repository. The Action decides whether a comment
# actually requests a review.
if: >-
github.event.issue.pull_request != null &&
github.event.issue.state == 'open' &&
contains(github.event.comment.body, '@tessl-code-review')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: tesslio/code-review-action@v1
with:
tessl-token: ${{ secrets.TESSL_TOKEN }}
allowed-associations: OWNER,MEMBER,COLLABORATORallowed-associations is the actor check, and choosing it is the caller's
responsibility even though the Action enforces it. An issue_comment run holds
the repository write permissions and the TESSL_TOKEN secret, and anyone who can
comment on a pull request can start one, so without this input an outside
commenter can spend the token at will. Tighten the list to suit the repository,
or leave it out only if any commenter may request a review.
The condition above is deliberately loose. A workflow expression cannot match a
token boundary or read Markdown, so it tests for the handle anywhere in the body
and the Action applies the exact rule: the handle as a whole token,
case-insensitively, in text the comment is saying rather than showing, so
@tessl-code-reviewer is not a request and neither is `@tessl-code-review`
in a code span, a fenced block or a quoted line. That last part is what lets a
comment write about the reviewer, or quote a round that mentioned it, without
starting one. A comment the Action does not admit ends the run with nothing
published and status: not-requested.
The issue.state condition belongs in that prefilter: the Action refuses to
review a closed or merged pull request, so without it a stray mention there starts
a run that fails, and fails without explaining itself on the pull request. The
pull-request number is never resolved for a closed one, so the failure notice has
nothing to address and is not published either. cancel-in-progress: false keeps
a requested review from being canceled by the next request.
The concurrency group is the same one the every-commit workflow below uses, so a repository running both never has two runs publishing for the same pull request at once. Sharing it means a push cancels a requested run that is still in flight for the head it replaced, and a mention arriving during a push-driven run queues behind it rather than racing it.
name: Tessl Code Review
on:
pull_request:
types: [opened, reopened, ready_for_review, synchronize]
permissions:
contents: read
checks: write
issues: write
pull-requests: write
concurrency:
group: tessl-code-review-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
review:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: tesslio/code-review-action@v1
with:
tessl-token: ${{ secrets.TESSL_TOKEN }}synchronize fires on every push to the pull-request branch. Per-pull-request
cancel-in-progress: true means the next push cancels the review already
running for the previous head instead of racing it, which also avoids paying
for reviews that would end up superseded.
A workflow_dispatch job passes the pull-request number through the pr-number
input, which is the same input any event without pull-request context uses.
Advisory mode is the default. It publishes a COMMENT review and succeeds for
every valid review outcome, including outcomes with findings.
When no configured lens matches the changed files, the Action succeeds without
publishing a review or failure notice. It reports skipped-no-matching-lenses,
retains the safe result artifact, and concludes the check neutral in both modes;
this is not an approval or pass verdict.
Gate mode connects the review outcome to the check result:
- An approved review attempts to approve the pull request and succeeds.
- A review that requires changes attempts to request changes, publishes the complete review, and fails the check. The workflow job still succeeds: the run did what it was asked, and the verdict is what the check carries.
- If GitHub does not permit the requested review event, the completed review is published as a comment instead, and the Action explains the repository configuration problem and fails the check.
- If the review returns no approval verdict, the gate is not established and the check fails. Only a boolean verdict approves a commit, so a missing or malformed one fails closed rather than passing the commit through. Such an outcome cannot be published either, and gate mode reports the missing verdict rather than the publication failure it causes.
Approval also requires the repository setting that allows GitHub Actions to create and approve pull requests.
The Action reports its own check run, named Tessl Code Review, against the
pull-request head it reviewed. Require that name in branch protection when you
enforce gate mode.
Requiring the calling workflow's job instead only enforces on pull_request
runs. A comment-driven or manually dispatched run is associated with the
default branch, so its job status never lands on the pull request and the
required check never arrives. The Action resolves the reviewed head itself, so
its check run lands on that head whatever the trigger was.
Conclusions:
| Terminal status | Gate mode | Advisory mode |
|---|---|---|
| Changes approved | success | success |
| Findings reported | not reachable | neutral |
| Changes requested | failure | not reachable |
| Review or publication did not complete | failure | neutral |
| Repository settings blocked the review event | failure | not reachable |
| Review verdict missing | failure | not reachable |
| Superseded by a newer commit | neutral | neutral |
| No matching review lenses | neutral | neutral |
Advisory mode never concludes failure, so requiring the check while running advisory cannot turn advisory into a gate. What the advisory check tells you is whether the review reached a verdict, not whether the run was healthy: a run that broke reports neutral, and the breakage reaches you as a failed job and a pull-request comment.
The job status answers the other question, in both modes: whether the Action ran. A review that completed and was published as asked never fails the job, whatever verdict it reached. The job fails when the run could not deliver that: it broke, its head was superseded before publication, or repository settings forced a complete review into a comment.
If the pull-request head moves while the review is running, nothing is published
for the head that was reviewed. The job fails with exit code 1 so that an
unpublished review cannot read as a completed one, the status output is
superseded, and the check concludes neutral in both modes because the reviewed
head got no verdict and the commit that replaced it was never judged. The
workflow run carries a warning naming the reason, and the check-run output
repeats it.
Nothing needs fixing. The push that superseded the run is a new head, and a run against that head reviews it. If your triggers do not cover that push, re-run the workflow for the current head.
The conclusions assume neutral does not block a required check. Confirm that
against the branch protection or ruleset your repository uses before requiring
the check.
The check run needs the checks: write permission. A workflow that does not
grant it behaves exactly as before and logs a warning naming the missing
permission. Nothing else about the run changes.
A run killed before it finalizes, by a job timeout or a lost runner, leaves the
check in_progress, which holds a pull request that requires it. Re-run the
workflow to replace it.
Later steps in the same job can consume the Action's structured result:
- id: review
uses: tesslio/code-review-action@v1
with:
tessl-token: ${{ secrets.TESSL_TOKEN }}
- if: always()
run: echo "Review status: ${{ steps.review.outputs.status }}"The Action also uploads a versioned result artifact. Uploaded data is built from an explicit field allowlist and excludes credentials, source contents, prompts, and debug output. See Public artifact schema for the published fields.
- Cross-repository pull requests are rejected before review execution.
- The Action does not execute code from the reviewed checkout.
- Pull-request content and repository files are treated as untrusted input.
- The automatic GitHub token is used for review publication.
- Callers must grant the permissions shown in the quick-start example.
Read Security model before adding comment-driven or other privileged triggers.
To update, replace the pinned commit SHA after reviewing the target release
notes. The Tessl CLI is not pinned by the Action, so review behaviour also
follows CLI releases between Action updates; set the cli-version input to an
exact version where a repository needs both fixed. To remove Tessl Code Review, delete the calling workflow and remove the
TESSL_TOKEN repository secret. Also remove its required check from branch
protection if gate mode was enabled.
bash scripts/validate-foundation.shRead SECURITY.md before reporting a vulnerability.
MIT