fj secret / fj variable are repo-only: no --org scope for shared CI credentials #127

Open
opened 2026-06-11 00:51:51 +00:00 by stephen · 1 comment
Owner

What

fj secret and fj variable operate on repo-scoped Actions secrets and
variables only. There is no --org <name> (or user-level) scope, so a team that
shares CI credentials across many repos via Forgejo's org-level Actions secrets
cannot manage them with fj at all. gh secret set --org / gh variable set --org are the standard tool for this.

Evidence

Every secret/variable API call is hard-wired to the repo path
(src/api/workflow_secrets.rs):

pub async fn list_secrets(client: &Client, owner: &str, name: &str) -> Result<Vec<ActionSecret>> {
    let path = format!("{}/actions/secrets", crate::api::repo_path(owner, name));
    ...
}
pub async fn set_secret(client, owner, name, secret_name, value) -> Result<()> {
    let path = format!("{}/actions/secrets/{}", crate::api::repo_path(owner, name), ...);
    ...
}

list_variables / set_variable / delete_variable (same file) are identical.
The CLI args carry only RepoFlag and offer no scope selector
(src/cli/workflow_secret.rs:29-61), and var/secret share those same args
via workflow_variable.rs. Forgejo exposes the org surface at
/orgs/{org}/actions/secrets and /orgs/{org}/actions/variables (mirroring the
repo endpoints fj already calls), so the gap is purely on the fj side.

Why it matters for CI/automation buyers

Org-level secrets are how teams avoid copy-pasting the same REGISTRY_TOKEN /
DEPLOY_KEY into 30 repos. The provisioning script for a new org is:

echo "$TOKEN" | fj secret set REGISTRY_TOKEN --org acme --from-file -
fj variable set DEFAULT_REGION --org acme --value us-east-1

Today none of that is expressible; the only escape hatch is hand-rolled
fj api -X PUT /orgs/acme/actions/secrets/REGISTRY_TOKEN ..., which also
re-exposes the --input stdin gap (rasterstate/fj#126).

Proposed shape

  • Add a mutually-exclusive scope selector to fj secret and fj variable:
    --org <name> (and consider --user for user-level secrets, which Forgejo
    also supports). Default remains repo scope inferred from the remote.
  • Route the four verbs (list/set/delete for both secrets and variables) to the
    /orgs/{org}/actions/... path when --org is set; the request/response
    shapes match the repo endpoints already implemented.

Scope

Adds a scope dimension to existing commands; no new verbs, no new auth. The
table/JSON rendering is unchanged.

## What `fj secret` and `fj variable` operate on **repo-scoped** Actions secrets and variables only. There is no `--org <name>` (or user-level) scope, so a team that shares CI credentials across many repos via Forgejo's org-level Actions secrets cannot manage them with fj at all. `gh secret set --org` / `gh variable set --org` are the standard tool for this. ## Evidence Every secret/variable API call is hard-wired to the repo path (`src/api/workflow_secrets.rs`): ```rust pub async fn list_secrets(client: &Client, owner: &str, name: &str) -> Result<Vec<ActionSecret>> { let path = format!("{}/actions/secrets", crate::api::repo_path(owner, name)); ... } pub async fn set_secret(client, owner, name, secret_name, value) -> Result<()> { let path = format!("{}/actions/secrets/{}", crate::api::repo_path(owner, name), ...); ... } ``` `list_variables` / `set_variable` / `delete_variable` (same file) are identical. The CLI args carry only `RepoFlag` and offer no scope selector (`src/cli/workflow_secret.rs:29-61`), and `var`/`secret` share those same args via `workflow_variable.rs`. Forgejo exposes the org surface at `/orgs/{org}/actions/secrets` and `/orgs/{org}/actions/variables` (mirroring the repo endpoints fj already calls), so the gap is purely on the fj side. ## Why it matters for CI/automation buyers Org-level secrets are how teams avoid copy-pasting the same `REGISTRY_TOKEN` / `DEPLOY_KEY` into 30 repos. The provisioning script for a new org is: ```sh echo "$TOKEN" | fj secret set REGISTRY_TOKEN --org acme --from-file - fj variable set DEFAULT_REGION --org acme --value us-east-1 ``` Today none of that is expressible; the only escape hatch is hand-rolled `fj api -X PUT /orgs/acme/actions/secrets/REGISTRY_TOKEN ...`, which also re-exposes the `--input` stdin gap (rasterstate/fj#126). ## Proposed shape - Add a mutually-exclusive scope selector to `fj secret` and `fj variable`: `--org <name>` (and consider `--user` for user-level secrets, which Forgejo also supports). Default remains repo scope inferred from the remote. - Route the four verbs (list/set/delete for both secrets and variables) to the `/orgs/{org}/actions/...` path when `--org` is set; the request/response shapes match the repo endpoints already implemented. ## Scope Adds a scope dimension to existing commands; no new verbs, no new auth. The table/JSON rendering is unchanged.
Author
Owner

Converted to backlog item rasterstate/fj#137 (p3, size M).

Converted to backlog item rasterstate/fj#137 (p3, size M).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
rasterstate/fj#127
No description provided.