Skip to content
Codeberg/Forgejo Compatibility

Codeberg/Forgejo Compatibility

fj-act is a fork of nektos/act retargeted at Codeberg/Forgejo by default. Most of upstream’s behavior carries over unchanged; this page covers exactly what’s different and why.

--instance vs --actions-url

These are two genuinely separate settings that are easy to conflate — mixing them up is the single most common surprise coming from GitHub Actions or from upstream act.

A fully-qualified uses: overrides both, for that one step only:

# resolves against --actions-url (https://data.forgejo.org by default)
- uses: actions/checkout@v4

# resolves against --actions-url too — a path after the repo is fine
- uses: some-org/some-repo/subdir@v1

# ignores --instance AND --actions-url entirely — the named host wins
- uses: https://codeberg.org/git-pages/action@v2

This is proven directly by a test fixture (pkg/runner/step_action_remote_test.go): it sets Instance: "codeberg.org" and ActionsURL: "https://data.forgejo.org" — deliberately different values — and asserts a bare uses: step resolves against ActionsURL, never Instance. A separate test sets a fully-qualified uses: https://code.forgejo.org/... under the same Instance: "codeberg.org" config and confirms the named host wins outright.

If a bare uses: org/name@ref step fails locally with an auth or 404-ish error even though it works on your real Codeberg instance, this is almost always the cause: the action doesn’t exist under --actions-url (data.forgejo.org by default), only under your --instance. Either pass --actions-url pointing at your instance, or write the step as a fully-qualified URL.

The forge/forgejo expression context

Forgejo documents forge/forgejo as context aliases for github — see Forgejo’s Actions reference. fj-act implements this literally: all three names evaluate to the exact same data at runtime.

// pkg/exprparser/interpreter.go
case "github", "forge", "forgejo":
    // Forgejo defines the forge/forgejo contexts as aliases of github
    return impl.env.Github, nil

So ${{ forge.ref }}, ${{ forgejo.ref }}, and ${{ github.ref }} are interchangeable — use whichever reads better in your workflow. With --strict, the workflow schema also whitelists forge/forgejo everywhere github is allowed, so strict validation won’t flag them as an unknown context.

GITHUB_* and FORGEJO_* environment variables

Every step/action container gets both sets:

  • GITHUB_* (GITHUB_REPOSITORY, GITHUB_SHA, GITHUB_REF, GITHUB_SERVER_URL, GITHUB_API_URL, …) is kept unconditionally. This isn’t GitHub-exclusive branding — it’s the wire format third-party Actions actually read in their JS/shell code, on GitHub or Forgejo. A real Forgejo runner sets these too. Dropping them would break any unmodified Action.
  • FORGEJO_* mirrors the same values under Forgejo’s own variable names (FORGEJO_REPOSITORY, FORGEJO_SHA, FORGEJO_REF, FORGEJO_SERVER_URL, …), matching Forgejo Runner v7.0.0+, so a workflow that already uses Forgejo’s idiomatic names works locally too.

GITHUB_GRAPHQL_URL is the one exception — it’s left unset unless you set it explicitly, since Forgejo has no GraphQL API.

The env.ACT sentinel

fj-act unconditionally sets env.ACT = "true" in every job — a real Codeberg/Forgejo-hosted runner never defines it. That makes if: ${{ !env.ACT }} a reliable way to skip a step only when running locally, e.g. a deploy step you don’t want to trigger from your laptop:

- name: Deploy to Codeberg Pages
  if: ${{ forge.ref == 'refs/heads/main' && !env.ACT }}
  uses: https://codeberg.org/git-pages/action@v2
  with:
    site: "https://gnoblet.codeberg.page/fj-act/"
    token: ${{ forge.token }}
    source: site/public/

(This exact pattern is what deploys this docs site — see .forgejo/workflows/site.yml in the repo.)

Token resolution

Covered in full on the Secrets & Environment page: GITHUB_TOKEN secret → FORGEJO_TOKEN secret (copied in) → gh auth token fallback. Set FORGEJO_TOKEN explicitly if you’re not using GitHub at all — there’s no tea CLI fallback today.