ActionBoxDOCS

GitHub Actions

Gate a GitHub Actions workflow with a durable ActionBox decision and fail closed on rejection or expiry.

Use a GitHub runner to create an ActionBox Action, wait for the Source-scoped result, and run the deployment only when the stable option ID is approved. The workflow below calls the hosted API directly, so the workflow only needs its own shell steps and a Source key.

Public integration boundary

ActionBox currently documents the hosted REST pattern for GitHub Actions. A public Marketplace or release-tagged composite action is not part of the current customer distribution path. The shell workflow below uses the supported API contract and is suitable for a GitHub-hosted Ubuntu runner.

Before the workflow

  1. Create a Source in the ActionBox dashboard.
  2. Save its live key as the repository secret ACTIONBOX_SOURCE_KEY.
  3. Ensure the runner has curl and jq. GitHub-hosted Ubuntu runners include both; install them explicitly on a self-managed runner.

Keep the Source key in GitHub's secret store. Never put it in workflow YAML, command arguments, logs, or Action context.

A fail-closed approval gate

This example creates one idempotent approval per run attempt, waits for up to 30 minutes, and runs the deployment only for the approve option. Rejection, expiry, cancellation, timeout, and API errors cannot become an approval.

name: deploy-production

on:
  workflow_dispatch:

permissions:
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      ACTIONBOX_API_URL: https://api.actionbox.cloud

    steps:
      - name: Check out the repository
        uses: actions/checkout@v4

      - name: Request production approval
        id: approval
        timeout-minutes: 35
        env:
          ACTIONBOX_SOURCE_KEY: ${{ secrets.ACTIONBOX_SOURCE_KEY }}
          ACTIONBOX_RUN_ID: ${{ github.run_id }}-${{ github.run_attempt }}
        shell: bash
        run: |
          set -euo pipefail

          action_id=""
          resolved="false"

          cleanup() {
            if [[ -n "$action_id" && "$resolved" != true ]]; then
              curl --fail --silent --show-error \
                --request POST \
                "$ACTIONBOX_API_URL/v1/actions/$action_id/cancel" \
                --header "Authorization: Bearer $ACTIONBOX_SOURCE_KEY" \
                --header "Content-Type: application/json" \
                --data '{"reason":"GitHub Actions stopped waiting"}' \
                >/dev/null || true
            fi
          }
          trap cleanup EXIT INT TERM

          payload="$(jq -n \
            --arg sha "$GITHUB_SHA" \
            --arg workflow "$GITHUB_WORKFLOW" \
            --arg repo "$GITHUB_REPOSITORY" \
            --arg ref "$GITHUB_REF_NAME" \
            --arg run "$ACTIONBOX_RUN_ID" \
            --arg server "$GITHUB_SERVER_URL" \
            '{
              title: ("Approve " + $sha[0:7] + " for production?"),
              description: "Required checks passed. Review the release before production changes.",
              priority: "high",
              dedupe_key: ("github-" + $run),
              options: [
                {id: "approve", label: "Approve deployment", style: "primary"},
                {id: "reject", label: "Reject deployment", style: "destructive"}
              ],
              metadata: {
                origin: {
                  provider: "github_actions",
                  ref: ($workflow + " · run " + $run),
                  url: ($server + "/" + $repo + "/actions/runs/" + ($run | split("-")[0])),
                  labels: [$repo, $ref, $sha[0:7]]
                }
              }
            }')"

          created="$(curl --fail-with-body --silent --show-error \
            --request POST "$ACTIONBOX_API_URL/v1/actions" \
            --header "Authorization: Bearer $ACTIONBOX_SOURCE_KEY" \
            --header "Content-Type: application/json" \
            --header "Idempotency-Key: github-$ACTIONBOX_RUN_ID" \
            --data "$payload")"

          action_id="$(jq -er '.data.id' <<<"$created")"
          echo "action_id=$action_id" >>"$GITHUB_OUTPUT"

          deadline=$(( $(date +%s) + 1800 ))
          while (( $(date +%s) < deadline )); do
            current="$(curl --fail-with-body --silent --show-error --max-time 45 \
              "$ACTIONBOX_API_URL/v1/source/actions/$action_id?wait_seconds=30" \
              --header "Authorization: Bearer $ACTIONBOX_SOURCE_KEY")"
            status="$(jq -er '.data.status' <<<"$current")"

            if [[ "$status" == open ]]; then
              continue
            fi
            if [[ "$status" != resolved ]]; then
              echo "Approval ended with status: $status" >&2
              exit 1
            fi

            decision="$(jq -er '.data.resolution_option_id' <<<"$current")"
            echo "decision=$decision" >>"$GITHUB_OUTPUT"
            resolved="true"
            exit 0
          done

          echo "No decision arrived within 30 minutes" >&2
          exit 1

      - name: Fail if the request was rejected
        if: steps.approval.outputs.decision != 'approve'
        run: |
          echo "The production deployment was rejected."
          exit 1

      - name: Deploy to production
        if: steps.approval.outputs.decision == 'approve'
        run: ./scripts/deploy.sh

The option IDs (approve and reject) are the machine contract. Labels can be made more descriptive without changing the branch condition. The Source-scoped read route supports a server wait of up to 30 seconds; it is not a decision and the workflow continues polling while the Action remains open.

Record the execution result

An approval authorizes an attempt; it does not prove that the deployment succeeded. After the deployment step, report success or failed to POST /v1/actions/{action_id}/outcome with the resolved Action's action_version and fingerprint. See Decisions and outcomes for the exact binding and retry rules.

For a longer walkthrough with outcome reporting and the same hosted API contract, read the GitHub Actions deployment gate guide.

GitHub controls remain useful

GitHub Environments can restrict branches, protect environment secrets, and require repository reviewers. Keep those controls when they are part of your security model. ActionBox adds a shared decision inbox, bounded review context, and a durable decision record that other workers can consume.

On this page