forge pr create reports status: open but creates no Forgejo PR when JJ working copy is empty #403

Open
opened 2026-08-23 21:35:53 +00:00 by gmackie · 0 comments
Owner

Summary

In a JJ-colocated repo, forge changeset create / forge push / forge pr create bind to @ (the JJ working copy). After a git-based commit flow, @ is an empty child of the bookmark, so the changeset binds jj_change_id to that empty change — no author, no head SHA. forge pr create then reports success with status: open but creates no Forgejo pull request.

The failure is silent: the CLI says the PR was created, and forge pr list shows it as open. Only querying Forgejo directly reveals there is no PR.

Reproduction

Repo: gmackie/pulse (JJ-colocated, git.colocate = true), 2026-08-23.

# commits made via git; bookmark feat/task-dod-salvage at f33a1f12, pushed to forge
$ forge changeset create
Changeset 7709d0b0-d03a-47ec-8ca4-e834197c0b75 created (status: draft)

$ forge push
Warning: No bookmarks found in the default push revset: remote_bookmarks(remote=origin)..@
Nothing changed.
Changeset 9c39d92b registered (status: draft)

$ forge pr create --title "..." --body "..."
PR 7709d0b0-d03a-47ec-8ca4-e834197c0b75 created: ... (status: open)

But:

$ curl .../api/v1/repos/gmackie/pulse/pulls?state=open
total open: 4   # none for feat/task-dod-salvage

Evidence

@ was the empty working-copy commit, and both changesets bound to it:

ywzwkqtslsqp e518477bcecf empty=true  bookmarks=          | (no description)   <- @
wpzppynskuww f33a1f12dd1f empty=false bookmarks=feat/task-dod-salvage | chore: bump version ...
$ forge changeset show 7709d0b0-...
Identities:
  jj_change_id: ywzwkqtslsqpxmypyknllyxkqnvrlwrv     # the EMPTY change
# no Author, no Head SHA

$ forge changeset show bb7e034c-...      # auto-created by ForgeGraph from the push
Author:      gmackie
Head SHA:    f33a1f12dd1fd7efcc944f904b5accafe385505c    # correct

The auto-created changeset (from the push webhook) is correct. The CLI-created ones are not.

Impact

  1. Silent no-op. forge pr create reports status: open having created nothing reviewable. A user reasonably believes the PR exists.
  2. forge pr show gives no way to notice. It returns no URL, no branch, no base, no head SHA — only Author: unknown, which reads as cosmetic.
  3. Junk accumulates and cannot be removed. Each retry creates another draft changeset. There is no forge changeset delete, and DELETE /api/fg/changesets/<id> returns 405 while GET returns 200. The three drafts from this session are permanent.
  4. No recovery via the documented path. jj edit <bookmark> to re-key the changeset fails once the commit is pushed (immutable), so the user cannot move @ onto the real change.

Suggested fixes

  • Resolve the target from the bookmark, not @. When @ is empty, walk to the nearest non-empty ancestor / the bookmark being pushed.
  • Fail loudly. If the resolved change is empty or has no head SHA, error instead of creating a changeset. forge push already prints "No bookmarks found … Nothing changed" — that should be fatal for pr create, not a warning.
  • Verify the Forgejo PR exists before reporting status: open, and surface its number/URL in forge pr show output.
  • Add forge changeset delete (or support DELETE /api/fg/changesets/<id>) so junk drafts can be cleaned up.

Workaround

Use the changeset ForgeGraph auto-creates from the push (correct author + head SHA), and create the PR directly:

POST https://git.forgegraf.com/api/v1/repos/OWNER/REPO/pulls
{"head": "<branch>", "base": "master", "title": "...", "body": "..."}
Authorization: token <forgejo_token>

Note the git-remote Forgejo token has PR scope but lacks read:issue/write:issue, so it can open PRs but not issues (forge issue proxies through the server for that, which works well).

## Summary In a JJ-colocated repo, `forge changeset create` / `forge push` / `forge pr create` bind to `@` (the JJ working copy). After a git-based commit flow, `@` is an **empty** child of the bookmark, so the changeset binds `jj_change_id` to that empty change — no author, no head SHA. `forge pr create` then reports success with `status: open` but **creates no Forgejo pull request**. The failure is silent: the CLI says the PR was created, and `forge pr list` shows it as `open`. Only querying Forgejo directly reveals there is no PR. ## Reproduction Repo: `gmackie/pulse` (JJ-colocated, `git.colocate = true`), 2026-08-23. ``` # commits made via git; bookmark feat/task-dod-salvage at f33a1f12, pushed to forge $ forge changeset create Changeset 7709d0b0-d03a-47ec-8ca4-e834197c0b75 created (status: draft) $ forge push Warning: No bookmarks found in the default push revset: remote_bookmarks(remote=origin)..@ Nothing changed. Changeset 9c39d92b registered (status: draft) $ forge pr create --title "..." --body "..." PR 7709d0b0-d03a-47ec-8ca4-e834197c0b75 created: ... (status: open) ``` But: ``` $ curl .../api/v1/repos/gmackie/pulse/pulls?state=open total open: 4 # none for feat/task-dod-salvage ``` ## Evidence `@` was the empty working-copy commit, and both changesets bound to it: ``` ywzwkqtslsqp e518477bcecf empty=true bookmarks= | (no description) <- @ wpzppynskuww f33a1f12dd1f empty=false bookmarks=feat/task-dod-salvage | chore: bump version ... ``` ``` $ forge changeset show 7709d0b0-... Identities: jj_change_id: ywzwkqtslsqpxmypyknllyxkqnvrlwrv # the EMPTY change # no Author, no Head SHA $ forge changeset show bb7e034c-... # auto-created by ForgeGraph from the push Author: gmackie Head SHA: f33a1f12dd1fd7efcc944f904b5accafe385505c # correct ``` The auto-created changeset (from the push webhook) is correct. The CLI-created ones are not. ## Impact 1. **Silent no-op.** `forge pr create` reports `status: open` having created nothing reviewable. A user reasonably believes the PR exists. 2. **`forge pr show` gives no way to notice.** It returns no URL, no branch, no base, no head SHA — only `Author: unknown`, which reads as cosmetic. 3. **Junk accumulates and cannot be removed.** Each retry creates another draft changeset. There is no `forge changeset delete`, and `DELETE /api/fg/changesets/<id>` returns **405** while `GET` returns 200. The three drafts from this session are permanent. 4. **No recovery via the documented path.** `jj edit <bookmark>` to re-key the changeset fails once the commit is pushed (immutable), so the user cannot move `@` onto the real change. ## Suggested fixes - **Resolve the target from the bookmark, not `@`.** When `@` is empty, walk to the nearest non-empty ancestor / the bookmark being pushed. - **Fail loudly.** If the resolved change is empty or has no head SHA, error instead of creating a changeset. `forge push` already prints "No bookmarks found … Nothing changed" — that should be fatal for `pr create`, not a warning. - **Verify the Forgejo PR exists** before reporting `status: open`, and surface its number/URL in `forge pr show` output. - **Add `forge changeset delete`** (or support `DELETE /api/fg/changesets/<id>`) so junk drafts can be cleaned up. ## Workaround Use the changeset ForgeGraph auto-creates from the push (correct author + head SHA), and create the PR directly: ``` POST https://git.forgegraf.com/api/v1/repos/OWNER/REPO/pulls {"head": "<branch>", "base": "master", "title": "...", "body": "..."} Authorization: token <forgejo_token> ``` Note the git-remote Forgejo token has PR scope but lacks `read:issue`/`write:issue`, so it can open PRs but not issues (`forge issue` proxies through the server for that, which works well).
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
gmackie/ForgeGraph#403
No description provided.