CLI
One subcommand: reconcile. It loads the policy and builds an authed client
for your Forgejo instance, then runs the selected cycles and prints one plan
per cycle per org.
forgejo-warden reconcile \
--config governance.yml \
--base-url https://forgejo.example.com \
--token-env FORGEJO_TOKEN \
--mode dry-run
forgejo-warden --help (or no arguments, or --help after a subcommand)
prints usage; forgejo-warden --version prints the version (inlined from
package.json at build time).
Flags
| Flag | Default | Meaning |
|---|---|---|
--config <path> |
required | policy file (YAML or JSON; see below) |
--mode dry-run\|apply |
dry-run |
dry-run computes and prints plans; apply also mutates the instance (guardrails permitting) |
--cycles <name[,name...]> |
all cycles | comma-separated subset of cycles to run, e.g. --cycles org-settings,teams. Unknown names exit 2 and list the known cycles |
--base-url <url> |
one of the two URL flags is required | Forgejo instance URL, e.g. https://forgejo.example.com or https://codeberg.org (no trailing /api) |
--base-url-env <VAR> |
— | env var holding the instance URL instead of putting it on the command line |
--token-env <VAR> |
required | env var holding the Forgejo API token. The token itself never appears in argv |
--removal-cap-fraction <f> |
0.25 |
removal-cap threshold, a number in (0,1]; values outside that range exit 2 (see "Guardrails" below) |
--allow-guardrail-override |
off | apply even when a guardrail trips (the plan still prints the guardrail block) |
Cycle names (see CYCLES.md): org-settings, membership, teams,
repo-settings, branch-protection, repo-baseline, secrets-variables,
webhooks.
Config loading
- A path ending in
.jsonis parsed as JSON; anything else is parsed as YAML. - The parsed document must be an object with an
orgsmap, otherwise the run exits 2 withinvalid governance config. - No further schema validation happens at load time; unknown fields are ignored by the cycles (each cycle reads only its declared slice).
Auth
Auth is a single Forgejo API token plus the instance base URL — there is no
GitHub-Apps-style installation-token machinery. Requests go to
<base-url>/api/v1/... with an Authorization: token <token> header. The same
invocation therefore works against any self-hosted Forgejo or Codeberg
(--base-url https://codeberg.org), and against a Gitea instance with a
compatible API.
Token permissions
The token must belong to a user with owner/admin rights in every org the policy manages. Scope-wise it needs read/write on the surfaces the cycles touch:
- organization (org settings, members, teams)
- repository (repo settings, topics, branch protections, webhooks, repo creation for
repo-baseline) - Actions secrets/variables (the
secrets-variablescycle)
When creating a scoped token in the Forgejo UI, grant read-and-write on
organization and repository at minimum; a dry-run needs only read. The e2e
suite mints its token with --scopes all for simplicity. See
SETUP.md for the click-path.
Output
For each cycle and org, the run prints:
=== <cycle> @ <org> ===
<plan: creates / updates (field diffs) / deletes>
In apply mode each block is followed by Applied: N, Failed: N and a FAILED
line per entry that errored (the run continues past individual failures). A
tripped guardrail prints GUARDRAIL BLOCK: <reason> and skips that cycle's
apply. Cycles that errored while fetching live state or building desired state
print ERROR in <cycle> @ <org> on
stderr, and cycles skipped because the request budget ran out print
DEFERRED (budget): ... (the budget defaults to 1000 API requests per run and
is not currently exposed as a flag).
Guardrails
The apply path runs one guardrail: chant's removalDeltaCap, evaluated per
resource type against the live managed entries counted during the diff, so a
truncated or mistyped policy cannot mass-delete in one run. See
POLICY.md, "The removal cap" for the full promise.
--removal-cap-fraction sets the threshold (default 0.25, valid in (0,1];
out-of-range values exit 2). --allow-guardrail-override applies anyway;
use both deliberately.
Deletes themselves are opt-in per org: the plan contains them only for orgs the
policy marks with owned: (see POLICY.md, "Delete semantics").
For an org without owned, plans never contain deletes and the cap has nothing
to block.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success: the plan printed (dry-run), or apply completed with no failures. |
| 1 | Guardrail block: apply mode, at least one guardrail tripped, and --allow-guardrail-override was not set. |
| 2 | Argument or config error (unknown flag, unknown cycle, unreadable or invalid config, missing auth). |
| 3 | Runtime error (API failure, an errored cycle, or failed apply entries). |
In CI, treat 0 as pass and 1 as "needs a human"; treat 2 and 3 as failures.