Landing Requests
Landing Requests (LRs) are JJHub’s native replacement for Pull Requests. They’re built from the ground up for jj’s stacked changes model - stable Change IDs, first-class conflict tracking, and automatic rebasing.Landing Requests vs Pull Requests
| Aspect | Pull Requests (GitHub) | Landing Requests (JJHub) |
|---|---|---|
| Identity | Tied to branch name | Tied to stable Change IDs |
| Stacked changes | Painful (rebase chains) | First-class (--stack flag) |
| Conflicts | Block merge | Tracked, work continues |
| Rebase | Changes commit hashes | Change IDs survive |
| Landing queue | Basic merge queue | Programmable (TypeScript) |
Create a Landing Request
| Flag | Description |
|---|---|
-t, --title <text> | LR title |
-b, --body <text> | LR description |
--target <bookmark> | Target bookmark (default: main) |
--change-id <id> | Explicit change ID(s), repeatable |
--stack | Include the full stack of changes |
--draft | Create the LR as a draft (not ready for review) |
Landing a Stack
When you’re working with stacked changes, use--stack to include the entire chain:
stack_size and all change_ids in the stack.
Draft Landing Requests
A draft landing request is a work-in-progress LR that is not yet ready for review. Drafts let you push changes early, share context with teammates, and iterate on a stack without triggering review notifications or enforcing protection rules.What makes a draft different from an open LR
| Behavior | Draft | Open |
|---|---|---|
| Review notifications sent | No | Yes |
| Status checks required | No | Yes |
| Bookmark protection rules enforced | No | Yes |
Visible in default jjhub land list | No (use --state draft or --state all) | Yes |
| Can receive reviews | Yes (but no notifications are sent) | Yes |
| Can be merged | No (must be published first) | Yes |
draft state badge when viewed with jjhub land view.
Creating a draft landing request
Use the--draft flag when creating:
draft state. No review notifications are sent and no CI status checks are required while it remains a draft.
Creating a draft via the API
"state": "draft" to confirm the LR was created as a draft.
Publishing a draft (moving from draft to open)
When the changes are ready for review, publish the draft to move it toopen state. There are two ways to do this:
Using jjhub land ready:
draft to open, triggering review notifications and enabling protection rule enforcement.
Using jjhub land edit:
When to use drafts
- Early feedback — Push changes and share the LR URL with a teammate for informal feedback before the code is complete.
- Work-in-progress changes — Start an LR to track your progress without cluttering the review queue.
- Stacked changes still in progress — When building a stack, create a draft LR for the full stack so you can push intermediate states without triggering CI on incomplete code.
- AI agent collaboration — Create a draft LR, let an AI agent iterate on the code via suggestions, then publish once the changes are ready for human review.
Converting an open LR back to draft
If you need to pull a published LR back to draft state (for example, if you discover the changes need more work), use:List Landing Requests
View a Landing Request
Review
Suggested Changes
Suggested changes let reviewers propose specific code edits inline within a review comment. Instead of describing what to change in plain text, a reviewer can include the exact replacement code. The landing request author can then accept the suggestion with a single command, and JJHub automatically creates a new change with the edit applied. This is JJHub’s equivalent of GitHub’s suggested changes feature, adapted for jj’s change model.How suggested changes differ from regular review comments
Regular review comments are plain text — they describe what should change but leave the author to interpret and implement the fix. Suggested changes contain an actual code diff: the reviewer specifies the exact lines to replace and what they should become. This eliminates ambiguity, speeds up the review cycle, and is particularly powerful for AI agent reviews where the agent can propose precise fixes. Because JJHub is jj-native, accepting a suggestion creates a new jj change (with a stable Change ID) rather than amending an existing commit. This preserves the full history of how the code evolved during review.Creating a suggestion via CLI
Use thesuggestion code fence syntax inside a review comment body. The fenced block specifies the replacement code for the lines being commented on:
```suggestion block works the same way as on GitHub: the contents replace the lines the comment is attached to. You can include multiple suggestion blocks in a single review if commenting on different parts of the diff.
When creating a review comment via the API, attach the suggestion to a specific file path and line range so JJHub knows which lines the replacement targets.
Viewing suggestions
When viewing a landing request, suggestions are shown alongside regular review comments. Each suggestion has a unique ID and a status (pending, accepted, or rejected):Accepting and rejecting suggestions
The landing request author (or anyone with write access) can accept or reject individual suggestions:- JJHub applies the code replacement to the target file at the specified lines.
- A new jj change is created with the edit. The change description is auto-generated (e.g., “Apply suggestion from @reviewer on src/auth.go”).
- The new change is added to the landing request’s change stack.
- The suggestion status updates to
accepted. - CI checks re-run against the updated code.
rejected and no code changes are made.
Batch accepting suggestions
For landing requests with many suggestions, useaccept-all to apply every pending suggestion in a single operation:
--squash flag is used, combines all accepted suggestions into a single change:
Suggestion syntax in comments
The suggestion syntax uses a fenced code block with thesuggestion language identifier:
suggestion block replaces the lines that the review comment is anchored to. A single comment can contain at most one suggestion block. To suggest changes to multiple locations, create separate review comments for each.
API endpoints for suggestions
Suggestions are managed through dedicated endpoints on the landing request:change_id of the newly created change:
How accepted suggestions create new changes
When a suggestion is accepted, JJHub performs the following operations on the repo-host:- The repo-host reads the target file at the specified lines from the change that the suggestion is attached to.
- It replaces the specified line range with the suggestion content.
- A new jj change is created as a descendant of the current tip of the landing request’s change stack.
- The change description is set to:
Apply suggestion from @<reviewer> on <path>. - The new change’s stable Change ID is recorded on the suggestion record.
- The landing request’s
change_idslist andstack_sizeare updated to include the new change.
Suggestions and AI agents
Suggested changes are particularly powerful when combined with JJHub’s AI agent system. An AI agent reviewing a landing request can propose precise code fixes as suggestions rather than just describing issues in prose. The author can review the agent’s suggestions and accept them with a single command, creating a tight human-in-the-loop workflow:Check CI Status
Conflict Resolution
jj treats conflicts as first-class objects, not file markers. When two changes modify the same file, jj records a conflict object that preserves all three sides: the base (common ancestor), the left (your version), and the right (the other version). This means conflicts are data, not corruption — you can continue working, push, and even review code that contains conflicts. Landing Requests surface jj’s conflict tracking automatically. When a conflict is detected, the LR’sconflict_status field changes from "clean" to "conflicted", and a landing.conflict webhook event fires if webhooks are configured.
How jj conflicts differ from git merge conflicts
In git, a conflict produces inline markers (<<<<<<<, =======, >>>>>>>) that corrupt the file until manually resolved. The file is in a broken state and cannot be compiled or tested.
In jj, a conflict is a structured object stored alongside the file. The file itself is not corrupted — jj records what the base, left, and right versions are as separate trees. This means:
- You can view each side independently
- Other changes in the stack can continue to land even if one change has conflicts
- Conflict state is tracked per-change, not per-file globally
- The programmable landing queue can be configured to allow landing with conflicts (for workflows that resolve them later)
Viewing conflicts
Usejjhub land conflicts to see the conflict details for a landing request:
jjhub change show command displays the full conflict object, including the base, left, and right file contents so you can understand exactly what diverged.
Viewing conflicts via the API
GET /repos/{owner}/{repo}/landings/{number}/conflicts endpoint returns the list of conflicts for the landing request, including the file paths, conflict type, and the change IDs where conflicts were detected.
You can also check conflicts at the individual change level:
The conflict resolution workflow
-
Detect — The LR shows
conflict_status: "conflicted"automatically. You can also see it when runningjjhub land view 42. -
Inspect — Run
jjhub land conflicts 42to see which files are conflicted and which changes are involved. Then usejjhub change show <change-id>to see the base, left, and right versions. -
Resolve locally — In your local jj repository, resolve the conflicts using jj’s built-in resolution tools:
-
Push — After resolving, push your changes. The LR updates automatically because it tracks stable Change IDs, not commit hashes:
-
Verify — The LR’s
conflict_statusupdates to"clean"once all conflicts are resolved. CI checks re-run automatically against the updated changes.
Conflicts in stacked changes
When working with stacked changes, a conflict in one change can cascade to descendant changes in the stack. jj handles this naturally — resolving the conflict in the parent change and rebasing descendants will clear the cascading conflicts:Review Conversations
When reviewing a landing request, reviewers leave comments — either general comments about the overall LR or inline comments on specific lines of code. These comments form review conversations (also called threads). A conversation starts with a top-level review comment and may include replies from the author or other reviewers. Review conversations can be resolved to indicate that the feedback has been addressed. Resolving a conversation collapses it visually and signals to the team that the discussion point is handled. Resolved conversations are not deleted — they remain accessible and can be re-opened (unresolved) at any time if the issue resurfaces.Resolving and unresolving conversations
Usejjhub land thread resolve to mark a review comment thread as resolved, and jjhub land thread unresolve to re-open it:
<comment-id> is the ID of the review comment that anchors the thread. You can find comment IDs by listing the threads on a landing request (see below).
Listing threads
To see review threads on a landing request, usejjhub land threads. By default this shows all threads; use --unresolved to filter to only unresolved ones:
Who can resolve conversations
The following users can resolve or unresolve a review conversation:- The LR author — can resolve conversations on their own landing request to indicate feedback has been addressed
- The comment author — the reviewer who started the conversation can resolve it to confirm the fix is satisfactory
- Repository admins and owners — can resolve or unresolve any conversation
- Users with write access — any collaborator with write access to the repository can resolve conversations, matching Gitea’s behavior
API endpoints
Review conversation resolution is managed through the landing request comments API:resolved set to false and resolved_by and resolved_at cleared to null.
Integration with bookmark protection
Bookmark protection rules can require that all review conversations are resolved before a landing request can be merged. This ensures that every piece of review feedback has been explicitly addressed before code lands. To enable this rule on a protected bookmark:jjhub land land will fail with a clear error:
jjhub land view command also shows the count of unresolved conversations, so authors can track their progress toward satisfying this protection rule.
Land
Programmable Landing Queue
The landing queue is programmable via TypeScript workflows. By default, it serializes merges (one at a time), but you can configure custom merge strategies:Bookmark Protection
Bookmark protection lets you define rules that gate landing into important bookmarks. When a landing request targets a protected bookmark, it must satisfy all configured rules before it can be merged. This is the jj-native equivalent of Gitea’s branch protection.Why Bookmark Protection?
Without protection rules, any collaborator with write access can land changes directly intomain or other critical bookmarks. Protection rules enforce code quality gates:
- Require peer review before landing
- Require CI status checks to pass
- Prevent force pushes that rewrite history
- Automatically dismiss stale reviews when new changes are pushed
Setting Up Protection Rules
Usejjhub bookmark protect to configure protection on a bookmark pattern:
Configurable Rules
| Rule | Flag | Description |
|---|---|---|
| Required reviews | --require-review | Landing requests must be reviewed before landing |
| Required approvals | --approvals <n> | Minimum number of approving reviews required (default: 1) |
| Required status checks | --require-status-checks | All CI status checks must pass before landing |
| Resolved conversations | --require-resolved-conversations | All review conversations must be resolved before landing |
| Dismiss stale reviews | --dismiss-stale-reviews | Approvals are dismissed when new changes are pushed to the LR |
| No force push | --no-force-push | Prevent force-pushing to the protected bookmark |
Pattern Matching
Bookmark protection rules use glob-style pattern matching:| Pattern | Matches |
|---|---|
main | Exactly the main bookmark |
release/* | Any bookmark starting with release/ (e.g., release/v1.0, release/v2.0) |
staging-* | Any bookmark starting with staging- |
main requires 1 approval and * requires status checks, a landing request targeting main requires both 1 approval and passing status checks.
API Endpoints
Bookmark protection rules are managed through the repository API:How Protection Interacts with Landing Requests
When you runjjhub land land on a landing request that targets a protected bookmark, JJHub enforces all matching protection rules:
- Review check - If
require_reviewis enabled, the LR must have at leastrequired_approvalsapproving reviews. Reviews with “request changes” block landing regardless of approval count. - Status check - If
require_status_checksis enabled, all CI status checks associated with the LR’s changes must report asuccessstatus. - Resolved conversations - If
require_resolved_conversationsis enabled, all review comment threads on the LR must be resolved before landing. Usejjhub land threads 42 --unresolvedto see remaining threads. - Stale review dismissal - If
dismiss_stale_reviewsis enabled, pushing new changes to the LR automatically dismisses previous approvals, requiring fresh reviews. - Force push prevention - If
no_force_pushis enabled, any attempt to force-push to the protected bookmark is rejected at the git protocol level.
jjhub land view and jjhub land checks commands also display protection rule status, so authors know exactly what is needed before attempting to land.
Lifecycle
- Create - Author opens an LR with
jjhub land create(optionally as a--draft) - Draft (optional) - If created as a draft, iterate on changes without triggering notifications. Publish with
jjhub land readywhen ready. - Review - Team reviews with
jjhub land review. Reviewers can leave comments, approve, request changes, or propose suggested changes with inline code replacements. - Apply Suggestions - Author reviews any suggested changes and accepts or rejects them with
jjhub land suggestion accept/reject. Accepted suggestions automatically create new changes in the stack. - CI Checks - Automated workflows validate the changes
- Resolve Conflicts - If conflicts arise, view them with
jjhub land conflicts, resolve locally withjj resolve, and push. The LR updates automatically. - Resolve Conversations - Address review feedback and mark threads as resolved with
jjhub land thread resolve. If bookmark protection requires resolved conversations, all threads must be resolved before landing. - Protection Rules - All bookmark protection rules for the target bookmark must be satisfied
- Land - Author or maintainer lands with
jjhub land land
Reactions
Reactions let you add emoji responses to issues and comments. JJHub supports the same reaction types as Gitea:+1, -1, laugh, hooray, confused, heart, rocket, and eyes.
Supported Reaction Types
| Reaction | Emoji | Description |
|---|---|---|
+1 | :+1: | Thumbs up |
-1 | :-1: | Thumbs down |
laugh | :laughing: | Laughing |
hooray | :tada: | Hooray / celebration |
confused | :confused: | Confused |
heart | :heart: | Heart |
rocket | :rocket: | Rocket |
eyes | :eyes: | Eyes |
CLI Commands
API Endpoints
Issue Reactions
Comment Reactions
id, content (the reaction type), and the user who reacted.