Skip to content

Code Review Workflow

Code Review Workflow

jjj provides built-in code review capabilities that work seamlessly with Jujutsu’s change-based workflow.

Why Code Review in jjj?

Traditional code review tools (GitHub PRs, GitLab MRs) are designed for Git’s commit-based model. They struggle with:

  • History rewrites: Comments get lost when you rebase
  • Force pushes: Review state resets
  • Squashing: All comments disappear

jjj solves this by leveraging change IDs, which remain stable across all history modifications.

Core Concepts

Change IDs

Every change in Jujutsu has a unique, stable change ID:

Terminal window
$ jj log -r @
@ kpqxywon alice@example.com 2025-11-23 15:30:00 my-feature kpqxywon
| Add user authentication
~

The change ID kpqxywon stays the same even if you:

  • Rebase onto a different parent
  • Amend the change
  • Squash with other changes
  • Split into multiple changes

This stability makes it perfect for attaching review metadata.

Unified Critique Model

In jjj, code review is attached to solutions, not directly to changes. A solution may have one or more jj changes associated with it. Review requests and critiques are unified into a single model: critiques with a reviewer field.

When you assign reviewers at solution creation (using --reviewer), jjj automatically creates review-type critiques for each reviewer. These critiques must be resolved (typically by addressing them with an LGTM comment) before the solution can be accepted.

Comment Relocation

When code changes, jjj intelligently relocates comments using:

  1. Exact match (fast path): Line number + content hash match
  2. Fuzzy match: Similarity scoring finds new location (70% threshold)
  3. Orphaned: Comment marked as unresolved if context disappears

This is powered by context fingerprinting using SHA-256 hashing.

Basic Workflow

1. Assign Reviewers

You can assign reviewers when creating a solution, or add them later using critiques.

At creation:

Terminal window
jjj solution new "Use JWT tokens" --problem "authentication" --reviewer @alice --reviewer @bob

This creates review-type critiques for each reviewer. You can also add reviewers to an existing solution by creating review critiques:

Terminal window
# Add a reviewer via critique
jjj critique new "JWT tokens" "Review requested" --reviewer @alice

Output:

Created critique 01958a: Review requested (reviewer: @alice)

2. Reviewer: Examine the Solution

Alice receives a notification and wants to review the code.

Fetch and Checkout

Terminal window
# 1. Fetch latest changes from remote
jj git fetch
# 2. Check out the change to review
jj new kpqxywon

Review the Solution Context

Before looking at code, Alice can understand the solution’s context:

Terminal window
# See what problem this solution addresses
jjj solution show "JWT tokens"
# See any existing critiques
jjj critique list --solution "JWT"

Run Tests

Terminal window
cargo test
npm start

3. Reviewer: Raise Critiques or Sign Off

After examining the solution, Alice has two paths.

If issues are found: raise a critique

Critiques are the formal mechanism for identifying problems with a solution. They must be resolved before the solution can be accepted.

Terminal window
# Design-level critique
jjj critique new "JWT tokens" "JWT tokens stored in localStorage are vulnerable to XSS" \
--severity high
# Code-level critique with file location
jjj critique new "JWT" "Password comparison is not constant-time" \
--severity critical \
--file src/auth/password.rs \
--line 42

See the Critique Guidelines for severity levels and how to write effective critiques.

If the implementation looks correct: sign off (LGTM)

When a reviewer approves the solution, they address their review critique:

Terminal window
# Find your review critique
jjj critique list --solution "JWT" --reviewer @alice
# Address it with an LGTM comment
jjj critique reply "review requested" "LGTM - clean implementation"
jjj critique address "review requested"

Addressing a review critique is the sign-off. The critique’s resolution records the reviewer’s approval with a timestamp.

4. Author: Respond to Critiques

Check what critiques are open:

Terminal window
jjj critique list --solution "JWT" --status open

For each critique, address it, dismiss it, or validate it:

Terminal window
# Fix the issue and mark as addressed
jjj critique address "constant-time"
# Or dismiss with explanation
jjj critique reply "localStorage" "The token is stored in an httpOnly cookie, not localStorage. See the solution's approach section."
jjj critique dismiss "localStorage"

After addressing critiques, request re-review if needed by creating a new review critique:

Terminal window
jjj critique new "JWT" "Re-review requested after fixes" --reviewer @alice

5. Submit

Once all critiques are resolved and reviews are in, submit the solution:

Terminal window
jjj submit

jjj submit checks:

  1. All critiques on the solution are resolved (addressed, dismissed, or validated)
  2. All assigned reviewers have signed off (if reviewers are assigned)
  3. The problem has no open sub-problems (if accepting would solve the parent problem)

If any check fails, submit will explain what is still needed. Use --force to bypass the gates in emergencies (this sets the force_accepted flag on the solution).

Unified Gate to Acceptance

jjj uses a unified critique model where all feedback — including review requests — are critiques:

Critique TypeWhat it representsHow to resolve
Regular critiqueA flaw or issue in the approachAddress, dismiss, or validate
Review critique (has --reviewer)A review request from a specific personReviewer addresses it (LGTM) or raises issues

All critiques must be resolved before a solution can be accepted. Review critiques are resolved when the assigned reviewer addresses them, which serves as the sign-off.

This unified model means:

  • A solution with any open critique (regular or review) cannot be accepted
  • Review requests and issue critiques follow the same lifecycle
  • The --reviewer field distinguishes review requests from issue critiques

Landing Changes

Since jjj decouples review from the forge (GitHub/GitLab), “merging” is updating the main branch.

Direct Landing

Terminal window
# Rebase onto latest main
jj git fetch
jj rebase -r kpqxywon -d main
# Advance main bookmark
jj bookmark set main -r kpqxywon
# Push
jj git push -b main
jj git push -b jjj # Push review metadata

Hybrid Workflow (GitHub/GitLab)

If your team requires Pull Requests for CI gating or compliance:

  1. Push your change as a bookmark:

    Terminal window
    jj bookmark set my-solution -r kpqxywon
    jj git push -b my-solution
  2. Open a PR on GitHub/GitLab targeting main.

  3. Paste jjj solution show "JWT" output in the PR description to show it has been reviewed and critiques resolved.

  4. Merge via the forge’s UI.

In the hybrid flow, review and critique happen in jjj. The GitHub PR is used for CI checks and the merge button.

Advanced Features

Viewing Review Requests

Terminal window
# All solutions with pending reviews
jjj solution list --status review
# Review critiques assigned to you
jjj critique list --reviewer @alice --status open
# Your actionable items
jjj status

Comment Relocation Example

Initial State

You request review for this code:

// src/auth/password.rs:40
pub fn hash_password(password: &str) -> Result<String> {
let salt = generate_salt();
let hash = sha256(password + &salt); // Line 42
Ok(hash)
}

Alice raises a critique at line 42:

Terminal window
jjj critique new "password hashing" "Use bcrypt instead of SHA-256" \
--severity high \
--file src/auth/password.rs \
--line 42

After Rebase

You rebase onto main, which added some imports:

// src/auth/password.rs:43 (was 40)
use bcrypt::{hash, DEFAULT_COST}; // New imports
pub fn hash_password(password: &str) -> Result<String> {
let salt = generate_salt();
let hash = sha256(password + &salt); // Now line 45 (was 42)
Ok(hash)
}

Result: The critique’s location automatically relocates from line 42 to line 45.

After Addressing

You address the critique:

pub fn hash_password(password: &str) -> Result<String> {
hash(password, DEFAULT_COST)
.map_err(|e| AuthError::HashingFailed(e))
}

The original code context is gone. jjj detects the content change and marks the critique’s location as orphaned, signaling that the code was modified (likely in response to the critique).

Best Practices

Review Early, Review Often

Request reviews for work-in-progress solutions to get feedback before you invest heavily in an approach.

Keep Solutions Focused

Smaller solutions are faster to review and easier to critique precisely. Aim for solutions that address one problem clearly.

Use Solution-Based Bookmarks

Name your jj bookmarks using a meaningful name to make changes easy to find:

jj bookmark set solution/jwt-auth

Respond to All Critiques

Every critique must be resolved before acceptance. Either:

  • Fix the issue and mark as addressed
  • Reply with your reasoning and dismiss
  • Acknowledge the flaw and validate (then refute the solution)

Write Clear Descriptions

Include context in your solution’s approach field so reviewers understand the “why”:

jj describe -m "Add bcrypt password hashing
Replaces SHA-256 with bcrypt for better security.
Uses DEFAULT_COST (12) for work factor.
Addresses: password security problem"

Troubleshooting

Critique Locations Not Relocating

If critique locations do not update after rebase:

  1. Check context: Did you completely rewrite the section?
  2. View orphaned critiques: jjj critique list --solution "JWT"
  3. Re-create if needed: Raise a new critique at the correct location

Submit Fails

If jjj submit reports unresolved critiques or missing sign-offs:

Terminal window
# Check what is blocking
jjj critique list --solution "JWT" --status open
jjj solution show "JWT" # Shows reviewer and sign-off status

Finding the Right Solution

If you forget which solution you are working on:

Terminal window
# List solutions for a problem
jjj solution list --problem "auth"
# The dashboard shows your assigned work
jjj status

Next Steps