GitHub Actions: generate a release video with a Sume Format

Start a Sume Format run when a GitHub release is published, pass the notes as input, poll until the video is ready, and attach it to the release.

6 min readSume
All posts

To generate a release video with GitHub Actions, run a workflow on the release event with types: [published], start a run of your Sume Format with the release notes in input and the tag in the Idempotency-Key, poll the run until it finishes, then download primary_output_url and attach the file with gh release upload.

Sume has no GitHub Action, and the Sume CLI ships no video command, so the Sume steps are plain HTTPS calls with curl. Sume facts come from Create a run, Runs and results, and the Format cookbook; GitHub behavior comes from GitHub's own docs, read 2026-09-27. For the Sume CLI in a pipeline, see How to run the Sume CLI in CI.

What do I need before the first run?

Three things, set up once:

  • A Format of your own that turns release notes into a video; this post uses the placeholder address acme/release-video. See What is a Sume Format?
  • A Sume API key with formats:read and formats:write. A team Format needs a key created in that team's workspace, and service-account keys cannot create Format runs.
  • The key saved as a repository secret named SUME_API_KEY. Sume's docs list CI secret stores among the places a key may live. If the secret is missing, GitHub's expression returns an empty string, and Sume answers 401 unauthorized.

How do I start the run when a release is published?

For a release event, GITHUB_REF is the tag ref refs/tags/<tag_name>, so GITHUB_REF_NAME is the tag. The job copies the release description from the event payload into NOTES, builds the request body with jq, starts the run, and saves the run id for the next step:

on:
  release:
    types: [published]
permissions:
  contents: write
jobs:
  video:
    runs-on: ubuntu-latest
    timeout-minutes: 100
    env:
      SUME_API_KEY: ${{ secrets.SUME_API_KEY }}
      NOTES: ${{ github.event.release.body }}
    steps:
      - name: Start the Format run
        run: |
          jq -n --arg tag "$GITHUB_REF_NAME" --arg notes "$NOTES" \
            '{input: {tag: $tag, release_notes: $notes}, generation_spend_cap_usd: 20}' > body.json
          curl -sS -X POST https://api.sume.com/v1/formats/acme/release-video/runs \
            -H "Authorization: Bearer $SUME_API_KEY" -H "Content-Type: application/json" \
            -H "Idempotency-Key: release-video-$GITHUB_REF_NAME-v1" -d @body.json > run.json
          RUN_ID=$(jq -er .data.id run.json) || { cat run.json; exit 1; }
          echo "RUN_ID=$RUN_ID" >> "$GITHUB_ENV"

Why do the notes go in input and the tag in the key?

Each choice follows a rule on one side:

  • NOTES is an intermediate environment variable on purpose. GitHub treats contexts ending in body as potentially untrusted input, and for inline scripts it calls an intermediate environment variable the preferred approach.
  • The notes go in input, not instruction. Sume writes input to a file in the run's workspace and tells the agent it is caller-supplied data, not instructions. input takes at most 64 top-level keys and 2 MiB.
  • The key comes from the tag. The same key with the same body returns 200 and the original run, so re-running the workflow does not pay twice. Bump -v1 when you want a fresh video for the same tag.
  • generation_spend_cap_usd bounds what this run may spend, up to $500. Spend caps for unattended agents covers the rules.

How does the job wait for the video?

Sume's cookbook names a CI job among the cases for polling with backoff instead of a webhook. The second step reads GET /v1/format-runs/{run_id} with a gap that starts at 5 seconds and doubles to 60, and treats a failed read as another wait: a 429 or 503 mid-loop is transient while the run keeps working. RUN_ID arrives through GITHUB_ENV, which passes a variable to later steps in the job.

completed fills primary_output_url, the one thing to show, and Sume media URLs are durable and public, so the MP4 download needs no Sume key. failed, canceled, and skipped are terminal too; the step prints a failed run's error and fails the job.

The GitHub CLI is preinstalled on GitHub-hosted runners, and each step that uses it needs GH_TOKEN. The job requests contents: write, which GitHub's workflow syntax reference says allows the action to create a release. With --clobber, a re-run replaces an asset of the same name:

      - name: Wait for the video and attach it
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          SLEEP=5
          while :; do
            RUN=$(curl -sS "https://api.sume.com/v1/format-runs/$RUN_ID" \
              -H "Authorization: Bearer $SUME_API_KEY") || RUN='{}'
            case "$(echo "$RUN" | jq -r '.data.status // empty')" in
              completed) break ;;
              failed|canceled|skipped) echo "$RUN" | jq '.data.error'; exit 1 ;;
            esac
            sleep "$SLEEP"; SLEEP=$(( SLEEP < 60 ? SLEEP * 2 : 60 ))
          done
          curl -sSL --fail -o release-video.mp4 "$(echo "$RUN" | jq -r .data.primary_output_url)"
          gh release upload "$GITHUB_REF_NAME" release-video.mp4 --clobber --repo "$GITHUB_REPOSITORY"

What are the time limits on each side?

Keep timeout-minutes above Sume's 90-minute ceiling so GitHub does not cancel the job first. If GitHub stops it anyway, the Sume run keeps running and billing; read it back later by its id.

From Sume's Runs and results and Format API docs and GitHub's Actions limits and workflow syntax, read 2026-09-27.
LimitValueDocumented by
Sume run deadline (expires_at)90 minutes after created_at, or sooner once a run older than 25 minutes has been silent for 10Sume
Typical long-form host video15 to 30 minutesSume
GitHub-hosted job execution timeUp to 6 hoursGitHub
timeout-minutes when omitted360GitHub
Re-runs of one workflow run50GitHub

Why didn't my workflow run, or why did it fail?

Causes to check, from both sides:

  • The release is still a draft. GitHub does not trigger workflows for created, edited, or deleted drafts; publishing one fires published.
  • Another workflow published the release with GITHUB_TOKEN. Events triggered by that token do not create new workflow runs, and release is not among the exceptions GitHub lists.
  • 401 unauthorized: the secret is missing, or the key was revoked.
  • 403 insufficient_scope: the key lacks formats:write or predates the Formats API. Create a new key; scopes cannot be added later.
  • 409 idempotency_conflict: the tag's key arrived with a different body, for example a release published again with edited notes. Bump the version in the key.

Sources

Related posts

More in Integrations

All Integrations posts

Written by Sume