Documentation

Search Composal documentation

Search public documentation, commands, and release notes.

Browse documentation

Repositories and Changes

Submit and stacking

How com submit turns work into a reviewable Change, and how to stack dependent changes from a Composal-native checkout or a plain Git clone.

On this page

com submit turns local work into a reviewable Change. It never rewrites the target branch itself — landing does that, separately, once review and CI gates pass. A stack is just what you get when one submitted Change is built directly on another: Composal tracks the relation and lands the stack in dependency order, and where the repository is GitHub-synced, projects each Change to its own pull request with the parent's branch as the base rather than one flat diff against trunk.

There are two ways to reach com submit, and this page covers both:

  • A Composal-native checkout — cloned with com clone, working against the JJ change graph directly. This is the deep, first-class path: stacking is implicit in how you build commits.
  • A plain Git clone — no native Composal workspace at all, just git clone and an ordinary branch. com submit still works here, and branches can still stack, but the relationship has to be named explicitly.

The submit model

Regardless of which mode you're in, com submit does the same three things: import the exact content you built, create or update the Composal Change and its patchset, and (where the repository is GitHub-synced) project a matching pull request. Re-submitting the same work appends a patchset to the existing Change rather than creating a new one — com submit is safe to run again after a small fix, a rebase, or a retried network call.

Compare patchsets

Use the P1/P2 slider on a Change's Diff tab to compare submitted revisions. For native JJ patchsets, Composal replays the older patchset onto the newer patchset's base before comparing them, so unrelated changes introduced by a rebase stay out of the diff. If that replay conflicts, the comparison reports an error.

Use Other Refs to select bookmarks, commits, or patchset bases for a direct tree comparison. Its labels distinguish a patchset from its base; moving the slider switches back to a patchset comparison. Selecting two patchsets in Other Refs also uses the patchset comparison.

Stacking from a Composal-native checkout

In a Composal-native checkout there is no separate "stack" command to create one. A stack is just the ordinary JJ change graph: com new starts the next change on top of wherever you are, so a chain of com new calls is already a stack before you submit anything.

Terminal
com new main -m "feat: add the storage layer"
# ...edit files...
com new -m "feat: use the storage layer in the API"
# ...edit files...
com submit -r 'main..@' --target main

com submit -r 'main..@' submits every change in that range as its own Change, each one still carrying its place in the graph. Composal numbers parents before children, so the change numbers themselves read in dependency order. Submitting a single change at a time works the same way — com submit --target main from @ submits just the current change, and Composal still detects its parent from the JJ graph if that parent was submitted too.

Inspect and manage a stack's identity with com stack:

Terminal
com stack list                # active graph-derived stacks in this repository
com stack show '#42'          # the stack a given Change belongs to
com stack name my-feature     # give a stack a stable bookmark alias

If the target branch moves while your stack is still open, com sync fetches it and restacks your submitted changes on top, then resubmits — pass --no-restack to pull and resubmit without rewriting anything locally, and --restack to say so explicitly. A GitHub-synced repository projects each Change in the stack to its own pull request, base branch chained to the parent's branch automatically.

Submitting (and stacking) from a plain Git clone

com submit also works from an ordinary git clone — no .jj directory, no native Composal workspace. Push a branch, then submit it:

Terminal
git clone https://github.com/<org>/<repo>.git
git checkout -b feature/widget
# ...commit work...
git push origin feature/widget
com submit --target main

Composal re-fetches the exact branch head itself (it does not trust a locally-computed diff), imports the commits between the target and your branch, and creates the Change and pull request. Push again and re-run com submit to append a patchset — the branch is adopted, never rewritten out from under you.

Stacking is explicit in this mode. There is no local graph for Composal to read a parent out of, so name the parent branch with --target:

Terminal
git checkout -b feature/widget-styling feature/widget
# ...commit work built on top of feature/widget...
git push origin feature/widget-styling
com submit --target feature/widget

Composal resolves feature/widget to the Change that already adopted it, and the new Change inherits that Change's landing target rather than trying to land onto feature/widget itself — the pull request Composal creates uses feature/widget as its base, so it renders as a genuinely stacked PR, not a flat diff against main.

Two things this mode requires that a Composal-native checkout does not:

  • Submit bottom-up. The parent branch has to already be submitted (and pushed) before you submit a branch stacked on it — Composal needs to find an existing Change for the parent to attach to.
  • Rebase forward, don't fall behind. A stacked branch has to be built on its parent's current pushed head. If the parent branch has moved since you branched from it, com submit rejects the child with a stack_parent_not_at_tip error rather than importing a stale diff — rebase your branch onto the parent's latest commit, push, and submit again.

There is no local restack for this mode: when a parent branch changes, git rebase the child branch yourself and push, the same as you would with any other Git remote.

Commands, side by side

TaskComposal-native checkoutPlain Git clone
Start the next change in a stackcom newgit checkout -b <branch> <parent-branch>
Submit one changecom submit --target maincom submit --target main
Submit a whole stack at oncecom submit -r 'main..@' --target mainnot available — submit each branch after its parent
Stack on another submitted changeautomatic (JJ parent)com submit --target <parent-branch>
Update after a small fixedit, then com submit againcommit, git push, then com submit again
Pull the target and reconcilecom sync --target maingit pull on the target, then resubmit
Inspect a stackcom stack show '#42'com change show '#42' (walk parent_change)
Landcom land '#42'com land '#42'

Landing a stack

Landing is identical once a Change exists, in either mode: com land '#N' requests server-verified landing for that Change and its submitted-together closure — every open ancestor it depends on lands first, in order. com land '#N' --dry-run prints the computed closure and gate state without requesting anything; --when-ready queues the change and lets the server land it once CI and approvals clear instead of refusing while they're outstanding.

A child cannot land ahead of its parent. If the parent is still open, landing the child lands the whole stack up to that point in one queued request — you do not need to land each Change in a stack by hand.

If the queue reports that a candidate became stale, its error identifies the changed target commit, ref, ref version, candidate base, or preceding queue entries. A preceding entry may have left the queue or acquired a new validation or candidate. Run com land '#N' --dry-run to check the current landing gates, then retry com land '#N' to build a candidate against the current target and queue.

Queue health over time

The merge queue's Queue health panel uses the selected 7, 14, or 30-day reporting window. It shows merge conflicts, operational errors, retries, candidate rebuilds, and worker recoveries by UTC day, with a breakdown of failure categories. A rebuild can be normal when an earlier queue entry or candidate input changes.

Landing success is the share of attempts that landed among landed, ejected, and needs-attention attempts. Cancellations are excluded. Retrying starts another attempt, so it does not erase the earlier failure. Recoverable errors indicate that the queue deferred work for another attempt; worker recoveries indicate that processing resumed after a worker lease expired.

Health history starts when reporting is enabled. It can lag recent activity; when analytics is unavailable, the panel uses live recorded events and labels the fallback. Existing queue timing charts may contain older history.