---
title: "Bisque GitHub Actions"
description: "Explain pull requests and releases from GitHub Actions on your Claude plan, signed in with GitHub."
canonical: https://bisque.cloud/docs/present/github-action
---

# GitHub Actions

Explain pull requests and releases from GitHub Actions on your Claude plan, signed in with GitHub.

Page: https://bisque.cloud/docs/present/github-action

Open a pull request and Claude Code decides whether it needs a watchable
walkthrough. The ones that do get one, linked in a comment a few minutes
later; a typo fix or a dependency bump is left without a comment. Mention `@bisque-cloud` on any pull request to get one on request,
and publish a release to have its notes gain one too.

It runs in your repository's GitHub Actions on your Claude Code subscription,
so writing an explainer is part of the plan you already pay for, with no API
bill. It signs in with GitHub: the workflow run's own GitHub identity
publishes to your repository's channel, `bisque.today/p/{owner}/{repo}`, so
there is no Bisque account and no Bisque key.

## Hand it to your agent

> Set up the Bisque pull request and release explainers on this repository by
> following https://bisque.today/docs/github.md

Your agent does the steps below. Making the token is yours, because only you
can sign in to your Claude account.

## On your Claude plan

One secret, `CLAUDE_CODE_OAUTH_TOKEN`, runs every explainer.

### Claude Code decides which pull requests get one

1. **Make a Claude Code token and store it as a repository secret.** In a
   terminal where `claude` is installed:

   ```bash
   claude setup-token
   gh secret set CLAUDE_CODE_OAUTH_TOKEN --repo OWNER/REPO
   ```

   The first command opens a browser to sign in and prints a token. Paste it
   when the second asks. An agent should ask the person to run both and wait,
   and never read the token itself.

2. **Add the workflow** as `.github/workflows/explain-pull-requests.yml`:

   ```yaml
   name: Pull request explainer

   on:
     pull_request:
       types: [opened, ready_for_review, reopened]

   permissions:
     contents: read
     pull-requests: write # post the watch link
     id-token: write # sign in with GitHub to publish

   concurrency:
     group: explain-pull-request-${{ github.event.pull_request.number }}

   jobs:
     explain:
       if: >-
         !github.event.pull_request.draft &&
         github.event.pull_request.head.repo.full_name == github.repository &&
         github.actor != 'dependabot[bot]'
       runs-on: ubuntu-latest
       steps:
         - uses: bisque-cloud/presenter@v1
           with:
             source: pull-request
             when: judged
             claude-code-oauth-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
   ```

   `when: judged` is the part that decides. Claude Code reads the pull
   request first and explains it when someone who did not write it would want
   the reasoning: a design decision, a new command or surface, a migration, a
   change across packages. It skips a dependency bump, formatting, a lockfile,
   a typo, a one-line fix, or a change only its author will read, and leaves
   no comment on it. A skipped run passes, nothing is published, and the
   run's log says why in a sentence.

   It never fails a contributor's checks over your plan. When the plan is
   out of usage or the token has expired, the run passes with one warning
   and publishes nothing. An expired token also leaves a one-line note on the
   pull request naming the secret to replace; a usage limit leaves no
   comment, because it clears on its own. On a public repository, the
   explainer comment shows how many times it has been watched, and the count
   keeps going up after the run ends.

   It runs when a pull request opens, reopens, or leaves draft, and not on
   every push, so each pull request costs one Claude Code run. Pull requests
   from forks and from Dependabot are left out: GitHub gives those runs no
   secrets and a read-only token. Mention `@bisque-cloud` on one to explain
   it, as set up below.

3. **Open a pull request that adds it.** A `pull_request` workflow runs from
   the pull request's own branch, so the pull request that adds this file is
   the first one judged, most likely as a skip.

### On request, with a mention

Mention `@bisque-cloud` in a comment on a pull request and it is explained,
whatever Claude Code would have decided. This covers pull requests from forks,
and one that was skipped. It uses the same `CLAUDE_CODE_OAUTH_TOKEN` secret,
so set that up first. On its own, this is also the setup for a repository
that wants explainers only when someone asks.

1. **Add the workflow** as `.github/workflows/explain-on-mention.yml`:

   ```yaml
   name: Pull request explainer on mention

   on:
     issue_comment:
       types: [created]

   permissions:
     contents: read
     pull-requests: write # post the watch link as a comment
     issues: write # react to the comment that mentions @bisque-cloud
     id-token: write # sign in with GitHub to publish

   jobs:
     explain:
       if: >-
         github.event.issue.pull_request &&
         contains(github.event.comment.body, '@bisque-cloud')
       runs-on: ubuntu-latest
       steps:
         - name: Check the mention and write access, and react so the commenter knows
           id: mention
           env:
             GH_TOKEN: ${{ github.token }}
             REPO: ${{ github.repository }}
             ACTOR: ${{ github.actor }}
             COMMENT_ID: ${{ github.event.comment.id }}
             BODY: ${{ github.event.comment.body }}
           run: |
             if ! grep -Eiq '(^|[[:space:]])@bisque-cloud([[:space:].,!?;:]|$)' <<<"$BODY"; then
               echo "No @bisque-cloud mention."; exit 0
             fi
             perm="$(gh api "repos/$REPO/collaborators/$ACTOR/permission" --jq .permission)"
             case "$perm" in
               write|maintain|admin) echo "$ACTOR: $perm" ;;
               *) echo "::error::$ACTOR has '$perm' permission; @bisque-cloud needs write."; exit 1 ;;
             esac
             gh api --silent -X POST \
               "repos/$REPO/issues/comments/$COMMENT_ID/reactions" -f content=eyes
             echo "explain=true" >> "$GITHUB_OUTPUT"
         - if: steps.mention.outputs.explain == 'true'
           uses: bisque-cloud/presenter@v1
           with:
             source: pull-request
             claude-code-oauth-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
   ```

   If the repository takes pull requests from people you do not know, add
   `network: deny` under `with:`. The agent then has no shell and no internet
   while it reads their text, and the explainer uses system fonts.

2. **Commit it to the default branch.** GitHub runs comment-triggered
   workflows from the default branch only, so a workflow on a feature branch
   does nothing yet.

3. **Try it.** On an open pull request, comment `@bisque-cloud explain this`.
   The mention is what starts it; the rest of the comment is not read. An 👀
   reaction appears within a minute, and the reply with the watch link follows
   several minutes later. Only people with write access can start one.

### Release explainers

It uses the same `CLAUDE_CODE_OAUTH_TOKEN` secret as the pull request
explainers.

1. **Add the workflow** as `.github/workflows/explain-releases.yml`:

   ```yaml
   name: Release explainer

   on:
     release:
       types: [published]

   permissions:
     contents: write # append the watch link to the release notes
     id-token: write # sign in with GitHub to publish

   jobs:
     explain:
       runs-on: ubuntu-latest
       steps:
         - uses: bisque-cloud/presenter@v1
           with:
             claude-code-oauth-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
   ```

2. **Publish a release.** The agent reads its notes and the diff since the
   previous tag, and a few minutes later the watch link is appended to the
   notes. To explain a release you already published, add
   `workflow_dispatch:` under `on:` and pass its tag as `tag:` under `with:`.

## Change the voice

Every explainer speaks in `kokoro:af_heart`. To use another voice, add it
under `with:`, engine first:

```yaml
with:
  source: pull-request
  claude-code-oauth-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
  voice: kokoro:am_michael
```

The Kokoro voices start with the accent and gender, then a name:
`af_` and `am_` are American, `bf_` and `bm_` British, for example
`kokoro:bf_emma` or `kokoro:bm_george`. The voice is downloaded on the first
run and cached.

## With another model

The pull request and release explainers run on a Claude Code subscription by
default. Without one, give them an API key from Google, OpenAI, xAI or
Anthropic instead. Everything else is the same: they still sign in with
GitHub to publish, so there is still no Bisque account and no Bisque key.

### Swap the token for a key

Store the key as a repository secret, then pass it in place of
`claude-code-oauth-token` in either workflow:

```bash
gh secret set GOOGLE_API_KEY --repo OWNER/REPO
```

```yaml
- uses: bisque-cloud/presenter@v1
  with:
    google-api-key: ${{ secrets.GOOGLE_API_KEY }}
```

The key names the provider, and the provider picks the model:

| Input                     | What writes the explainer                        |
| ------------------------- | ------------------------------------------------ |
| `claude-code-oauth-token` | Claude Code on your Claude plan, its own default |
| `google-api-key`          | `gemini-3.1-pro-preview`                         |
| `openai-api-key`          | `gpt-5.6-sol`, medium effort                     |
| `xai-api-key`             | `grok-4.6`                                       |
| `anthropic-api-key`       | `claude-opus-5`, medium effort                   |

Set one. An API key is billed by its provider for each explainer, where a
Claude plan token counts against the plan you already pay for.

### Pick a different model

Add `model:` with any `provider/model` id from [models.dev](https://models.dev),
from the provider whose key you set, for example
`model: anthropic/claude-sonnet-4-6` with an Anthropic key to spend less. `variant:` sets reasoning
effort where the provider has one.

### How the key is kept from the agent

The agent never holds the key. Its model calls go through a local proxy that
holds it and rewrites the auth header, so it is in no environment the agent or
its tools can read. The means to mint the run's GitHub identity is removed
from the agent's step too, and the publishing step refuses to run a skill
whose hash changed while the agent ran.

## Where explainers live

A public repository's explainers are public on `bisque.today/p/{owner}/{repo}`,
next to the GitHub path you already know: release `v1.2.0` of `acme/widgets`
is at `bisque.today/p/acme/widgets/v1-2-0`. Mentioning `@bisque-cloud` again
on the same pull request, or re-running a release's workflow, replaces that
explainer rather than adding a second one. A pull request keeps one comment
from the explainer, and a later skip leaves an existing explainer in place.

A private repository's explainers are private. Anyone who can open the
repository on GitHub watches them after choosing **Sign in with GitHub**.
Their access is checked against GitHub again at least every 12 hours, so
someone removed from the repository loses its explainers too.

## Manage the channel

Open `bisque.today/p/{owner}/{repo}` and choose **Sign in with GitHub**, as an
admin of the repository. The channel then carries your GitHub name, and you
can delete an explainer or open its analytics. Signing in is optional;
explainers publish either way.

## What it costs

Writing an explainer is one Claude Code run, counted like any other use of
your plan. With `when: judged`, a skipped pull request is a shorter run that
stops after reading it. Narration runs on the GitHub runner, which is free on
a public repository and a few minutes of Actions time on a private one.
Publishing and hosting are free.

## If something looks wrong

- **A pull request got no comment at all.** Claude Code skipped it, and the
  run's log says why; or it was a draft, came from a fork or Dependabot, or
  was opened before the workflow reached the default branch. Mention
  `@bisque-cloud` on it, or mark it ready for review.
- **It skipped one you wanted.** Mention `@bisque-cloud` on it; a mention
  always explains.
- **No 👀 reaction.** The workflow is not on the default branch, or the comment
  does not mention `@bisque-cloud` as a whole word: `@bisque-cloudy` or
  `@bisque-cloud/presenter` does not count.
- **The run fails at "Narrate on this runner and publish" about `id-token`.**
  The workflow's `permissions:` block is missing `id-token: write`.
- **The run fails while authoring.** The job log shows what the agent did, and
  the run's artifacts hold its working files. An expired or revoked
  `CLAUDE_CODE_OAUTH_TOKEN` fails here; make a new one with
  `claude setup-token`.
- **The release notes have no link.** The release workflow needs
  `contents: write` to edit them.

Every input is described in
[`action.yml`](https://github.com/bisque-cloud/presenter/blob/main/action.yml).
