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@v2This 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.
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, nilSo ${{ 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.