fj pr cannot label or assign a pull request (create or edit), though it can request reviewers #110

Closed
opened 2026-06-11 00:01:28 +00:00 by stephen · 2 comments
Owner

Observation

fj can request reviewers on a pull request but cannot label or assign one. There is no --label/--assignee on fj pr create, no add/remove on fj pr edit, and no fj pr subcommand for either, even though the PR data model already reads both fields back and a dormant API helper for the write already exists.

fj pr has a rich verb set (checkout, merge, close, reopen, ready, comment, review, request-review, unrequest-review), so reviewers are covered. Labels and assignees are simply absent:

$ fj pr create --help | grep -iE 'label|assignee'
(nothing)
$ fj pr edit --help   | grep -iE 'label|assignee'
(nothing)

The arg structs confirm it: CreateArgs (src/cli/pr.rs:107-132) is title/base/head/body/attach/draft/web; EditArgs (src/cli/pr.rs:134-152) is title/body/body_editor/attach. The API payloads match: CreatePull (src/api/pull_core.rs:147-153) carries title/head/base/body/draft only, and EditPull (src/api/pull_core.rs:168-179) carries title/body/state/base only. Neither models labels or assignees on the write path.

This is a write-only gap, not a data-model gap. The Pull struct already deserializes both fields on read (src/api/pull_core.rs:27 pub labels: Vec<Label>, :43 pub requested_reviewers: Vec<User>), so fj pr view can show a PR's labels but nothing can set them. And the label write endpoint is already implemented, just dormant (src/api/label.rs:98-122):

#[allow(dead_code)]
pub async fn add_to_issue(...)      // POST {repo}/issues/{number}/labels
#[allow(dead_code)]
pub async fn remove_from_issue(...) // DELETE {repo}/issues/{number}/labels/{id}

In Forgejo a PR is an issue, so POST /issues/{number}/labels labels a PR too. The plumbing exists; no CLI verb reaches it.

Note this is distinct from the issue-side gap tracked in rasterstate/fj#95 / rasterstate/fj#98 (those are fj issue create/edit). This is the parallel hole on the pull-request surface, where reviewers are already solved but labels and assignees are not.

Why it matters

Routing a PR is half of code-review triage: label it needs-review / area/api, assign the owner, then it shows up in everyone's filtered queues. gh pr create --label --assignee --reviewer and gh pr edit --add-label --add-assignee are standard, and a team scripting "open PR, label it, assign it" against fj can do the open and the reviewers but must drop to fj api for the label and assignee, the same raw-API fallback the tool exists to remove. It is more jarring here than for a totally-missing feature because fj pr request-review exists right next to it, so the user reasonably expects labeling to be a sibling flag and only discovers otherwise mid-script. It also pairs badly with the read side: fj pr view shows labels the CLI gives you no way to have set.

Possible directions (sketches)

  • (sketch) Add --label/--assignee (repeatable, comma-split) to fj pr create, threading into CreatePull (Forgejo's create-PR endpoint accepts labels as IDs and assignees as logins; resolve label names to IDs via the labels API first).
  • (sketch) Add --add-label/--remove-label/--add-assignee/--remove-assignee to fj pr edit, backed by the already-present add_to_issue/remove_from_issue (src/api/label.rs:98-122, drop the #[allow(dead_code)]) and the parallel assignees endpoint. Whatever solution lands for rasterstate/fj#98 on the issue side should be shared verbatim, since PRs and issues hit the same endpoints.
  • (sketch) If a unified design is preferred, a single fj label add/remove <number> that works for both issues and PRs would close #98 and this in one verb.

Confidence

High. Flag absence verified from --help and the arg/payload structs (src/cli/pr.rs:107-152, src/api/pull_core.rs:147-179); read-side modeling verified (src/api/pull_core.rs:27,43); the dormant write helper verified (src/api/label.rs:98-122). The shared-endpoint fact (PR == issue in Forgejo) is standard Gitea/Forgejo behavior. The only open question is whether to solve it jointly with the issue-side #98, which is a design call.

## Observation `fj` can request reviewers on a pull request but cannot label or assign one. There is no `--label`/`--assignee` on `fj pr create`, no add/remove on `fj pr edit`, and no `fj pr` subcommand for either, even though the PR data model already reads both fields back and a dormant API helper for the write already exists. `fj pr` has a rich verb set (`checkout`, `merge`, `close`, `reopen`, `ready`, `comment`, `review`, `request-review`, `unrequest-review`), so reviewers are covered. Labels and assignees are simply absent: ``` $ fj pr create --help | grep -iE 'label|assignee' (nothing) $ fj pr edit --help | grep -iE 'label|assignee' (nothing) ``` The arg structs confirm it: `CreateArgs` (`src/cli/pr.rs:107-132`) is title/base/head/body/attach/draft/web; `EditArgs` (`src/cli/pr.rs:134-152`) is title/body/body_editor/attach. The API payloads match: `CreatePull` (`src/api/pull_core.rs:147-153`) carries `title/head/base/body/draft` only, and `EditPull` (`src/api/pull_core.rs:168-179`) carries `title/body/state/base` only. Neither models labels or assignees on the write path. This is a write-only gap, not a data-model gap. The `Pull` struct already deserializes both fields on read (`src/api/pull_core.rs:27` `pub labels: Vec<Label>`, `:43` `pub requested_reviewers: Vec<User>`), so `fj pr view` can show a PR's labels but nothing can set them. And the label write endpoint is already implemented, just dormant (`src/api/label.rs:98-122`): ```rust #[allow(dead_code)] pub async fn add_to_issue(...) // POST {repo}/issues/{number}/labels #[allow(dead_code)] pub async fn remove_from_issue(...) // DELETE {repo}/issues/{number}/labels/{id} ``` In Forgejo a PR is an issue, so `POST /issues/{number}/labels` labels a PR too. The plumbing exists; no CLI verb reaches it. Note this is distinct from the issue-side gap tracked in `rasterstate/fj#95` / `rasterstate/fj#98` (those are `fj issue create`/`edit`). This is the parallel hole on the pull-request surface, where reviewers are already solved but labels and assignees are not. ## Why it matters Routing a PR is half of code-review triage: label it `needs-review` / `area/api`, assign the owner, then it shows up in everyone's filtered queues. `gh pr create --label --assignee --reviewer` and `gh pr edit --add-label --add-assignee` are standard, and a team scripting "open PR, label it, assign it" against `fj` can do the open and the reviewers but must drop to `fj api` for the label and assignee, the same raw-API fallback the tool exists to remove. It is more jarring here than for a totally-missing feature because `fj pr request-review` exists right next to it, so the user reasonably expects labeling to be a sibling flag and only discovers otherwise mid-script. It also pairs badly with the read side: `fj pr view` shows labels the CLI gives you no way to have set. ## Possible directions (sketches) - *(sketch)* Add `--label`/`--assignee` (repeatable, comma-split) to `fj pr create`, threading into `CreatePull` (Forgejo's create-PR endpoint accepts `labels` as IDs and `assignees` as logins; resolve label names to IDs via the labels API first). - *(sketch)* Add `--add-label`/`--remove-label`/`--add-assignee`/`--remove-assignee` to `fj pr edit`, backed by the already-present `add_to_issue`/`remove_from_issue` (`src/api/label.rs:98-122`, drop the `#[allow(dead_code)]`) and the parallel assignees endpoint. Whatever solution lands for `rasterstate/fj#98` on the issue side should be shared verbatim, since PRs and issues hit the same endpoints. - *(sketch)* If a unified design is preferred, a single `fj label add/remove <number>` that works for both issues and PRs would close `#98` and this in one verb. ## Confidence High. Flag absence verified from `--help` and the arg/payload structs (`src/cli/pr.rs:107-152`, `src/api/pull_core.rs:147-179`); read-side modeling verified (`src/api/pull_core.rs:27,43`); the dormant write helper verified (`src/api/label.rs:98-122`). The shared-endpoint fact (PR == issue in Forgejo) is standard Gitea/Forgejo behavior. The only open question is whether to solve it jointly with the issue-side `#98`, which is a design call.
Author
Owner

Converted to backlog item rasterstate/fj#114 (p2, size M).

Tracked there with task / priority / reason / acceptance / dependencies. Keeping this open with the converted label as the originating opportunity.

Converted to backlog item `rasterstate/fj#114` (p2, size M). Tracked there with task / priority / reason / acceptance / dependencies. Keeping this open with the `converted` label as the originating opportunity.
Author
Owner

Derived backlog item rasterstate/fj#114 is merged. Closing this opportunity per the issue state machine.

Derived backlog item rasterstate/fj#114 is merged. Closing this opportunity per the issue state machine.
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#110
No description provided.