Pause a workflow for a mobile (or comment) approval before continuing. Forge-native, fail-closed deploy gate Forgejo lacks. No github.com.
  • JavaScript 79.5%
  • Shell 15.7%
  • Makefile 4.8%
Find a file
Stephen Way 86e6f12491
Some checks are pending
test / unit (push) Waiting to run
Repoint references from rasterstate to fjord org
Org migration: the action family now lives under fjord/. Repoints internal repo references (uses:, CI badges, docs, migration guides) at fjord/ and sets the action author to fjord. The old rasterstate copies are left in place.
2026-06-03 16:16:52 -07:00
.forgejo/workflows Initial release: fjord-approval-gate-action 2026-06-01 18:42:14 -07:00
src Initial release: fjord-approval-gate-action 2026-06-01 18:42:14 -07:00
tests Initial release: fjord-approval-gate-action 2026-06-01 18:42:14 -07:00
.gitignore Initial release: fjord-approval-gate-action 2026-06-01 18:42:14 -07:00
action.yml Repoint references from rasterstate to fjord org 2026-06-03 16:16:52 -07:00
CHANGELOG.md Initial release: fjord-approval-gate-action 2026-06-01 18:42:14 -07:00
LICENSE Initial release: fjord-approval-gate-action 2026-06-01 18:42:14 -07:00
Makefile Initial release: fjord-approval-gate-action 2026-06-01 18:42:14 -07:00
README.md Repoint references from rasterstate to fjord org 2026-06-03 16:16:52 -07:00

Fjord Approval Gate

The deploy approval gate Forgejo doesn't have. Pause a workflow until an authorized human approves, from the Fjord mobile app (tap to approve) or by commenting /approve on the approval thread. Fail-closed: a timeout or /deny stops the deploy. No github.com.

Part of the Fjord Actions bundle. Uses fjord-notify's relay to reach phones.

Why

Forgejo has no equivalent of GitHub's environment protection rules, so prod deploys ship the instant CI is green, with nobody in the loop. Teams hand-roll a "gate" job; when one such gate was cancelled mid-deploy it caused a ~1.5h outage. This action is the missing gate, and unlike a hosted feature you can actually self-host it.

The approval state lives in the forge (an issue + its comments). If the runner dies while waiting, re-run with the same issue-number and it finds the decision instead of stranding a half-applied deploy.

Usage

jobs:
  deploy:
    runs-on: [self-hosted, Linux]
    # Queue concurrent deploys instead of cancelling a waiting one.
    concurrency:
      group: deploy-prod
      cancel-in-progress: false
    permissions:
      issues: write
    steps:
      - uses: https://rasterhub.com/fjord/fjord-approval-gate-action@v1
        with:
          environment: production
          approvers: stephen, ops-oncall
          timeout-minutes: 30
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          FJORD_RELAY_URL: ${{ vars.FJORD_RELAY_URL }}        # optional: push to phones
          FJORD_RELAY_TOKEN: ${{ secrets.FJORD_RELAY_TOKEN }}

      - run: ./deploy.sh    # only runs after approval

Approve from the Fjord app, or comment /approve (or /deny) on the issue the action opens. /lgtm and also approve; /reject denies.

Inputs

Input Default Description
environment production Name shown in the prompt.
approvers Logins allowed to approve. Empty = anyone except the run actor.
allow-self-approval false Let the run actor approve their own deploy.
timeout-minutes 60 Wait before timing out.
poll-interval-seconds 15 How often to check.
fail-on-timeout true Block the deploy if no approval arrives.
issue-number new issue Reuse an approval thread (for re-runs).
issue-title / instructions generated Customize the prompt.
relay-url / relay-token FJORD_RELAY_* env Push the request to phones.
decisions-url Relay endpoint polled for an app decision (fast path).
token / api_url / repository derived Forgejo auth + target.

Outputs

Output Description
approved true, or false on timeout when fail-on-timeout is off.
approver Login of the approver.
issue-number The approval thread (reuse on a re-run).

How approval arrives

  1. Mobile (fast path): with relay-url set, the action pushes an approval-request to the Fjord relay; the app shows Approve/Deny. If you also set decisions-url, the action polls it for the app's decision ({ "decision": "approve" | "deny", "approver": "login" }).
  2. Comment (always works): the action opens an issue and polls its comments for /approve or /deny from an authorized approver. This needs no relay and no app, just Forgejo.

Pair with concurrency: { cancel-in-progress: false } so a second deploy queues behind a waiting one instead of cancelling it (the "hold, don't cancel" property that avoids the half-deploy failure mode).

License

MIT, see LICENSE.