Skip to content

Publications

After this page you can turn a reviewed working tree into a pull request, poll it to a terminal state, and land it at exactly the commit a human approved — or find out that it moved, instead of merging something nobody read.

Publishing and merging are separate durable objects, and the separation is the design:

Object What it does
Publication Pushes the session’s work to a branch and opens (or updates) a pull request.
Merge Integrates that pull request at one exact head commit.

They are split because opening a pull request and landing it have different authority, different retry behavior, and different things that can block them — a review, a required check, a branch protection rule. Folding them into one call would mean an integration could not express “publish now, land later, only if the head is still the one that was reviewed”, which is the normal case.

Both are created with PUT to an id you mint. Same id and same body replays as 200 with the object you already have; the same id with a different body is a 409. Publications and merges are therefore safe to retry after a timeout, in the same way turns are — see Sessions, turns, runs.

You publish a revision, not a diff and not a branch name. Read it first from the session’s repository review — the 64-character hex digest of the working tree described in Repositories — and pass it back. That is the “I reviewed exactly this” token: if the agent touches the tree after you read the review, the revision changes and your publish request no longer matches what you looked at.

The body is four fields:

Field Required Notes
revision yes 64 hex characters, from the review.
title yes 1–256 characters, no line breaks.
body yes Up to 65,536 characters. May be "".
draft no Defaults to true.

The branch is not yours to choose. It is derived from the session, stable across republishes of that session, and lives in a reserved namespace (wamp/publications-v1/…). Publishing the same session twice updates the same branch and the same pull request rather than opening a second one, which is why a successful result tells you whether it was reused.

Publishing requires a repository grant that includes PUBLISH. Without one the request is 403 cloud_repository_grant_required, and no administrator action happens on your behalf.

admitted ──▶ prepared ──▶ attempted ──┬──▶ succeeded
└──▶ failed

admitted means the command is committed and will be worked on; the three non-terminal states are the server making progress, not something you drive. You poll the publication until status is succeeded or failed, exactly as you poll a run.

At most one non-terminal publication per session. A second concurrent publish is 409 cloud_publication_in_progress. That is a queue you do not have to build.

On success, result appears:

{
"publication": {
"id": "e8b1c4d6-0a2f-4c3b-9d1e-5f6a7b8c9d01",
"status": "succeeded",
"revision": "5c1e…",
"title": "Fix CSV export rounding",
"result": {
"branch": "wamp/publications-v1/cloud-3f7d4f4c-2b6a-1a2b3c4d",
"commitSha": "7c9e1a3b5d7f9012345678901234567890abcdef",
"pullRequest": { "number": 412, "url": "https://github.com/…/pull/412", "draft": true, "reused": false }
},
"completedAt": "2026-08-11T09:12:44.010Z"
}
}

result is present only when the publication actually produced a pull request — in practice, only on succeeded. On failed, read lastError.code. The pullRequest.reused flag is the one field people forget: true means an existing pull request was updated, so your product should say “updated PR #412”, not “opened PR #412”.

Two other places the same fact arrives: the event log gets wamp.publication.created (or wamp.publication.failed with an errorCode), and the session gains a github.pull_request artifact carrying the publicationId. See Artifacts.

A merge names the head it was prepared against. You do not supply that head — the server takes expectedHeadSha from the publication, and the merge is submitted to GitHub against that exact commit. If someone pushed to the branch in between, the merge fails rather than integrating commits nobody reviewed. That is the whole point of the separation.

The body is optional, and all four fields have safe defaults or are omitted:

Field Default Notes
strategy squash merge, squash, or rebase.
mode now now or when_ready.
commitTitle 1–256 characters, no line breaks.
commitMessage Up to 65,536 characters.

GET /v1/capabilities advertises the strategies and modes the server accepts; read them from there rather than hardcoding the list.

A merge can only be admitted for a publication that has already succeeded and has a pull request. Asking earlier is 409 cloud_publication_merge_not_ready.

admitted ──┬──▶ attempted ──┬──▶ succeeded
└──▶ waiting ────┴──▶ failed
status Meaning
admitted Committed, not yet attempted.
attempted The merge has been submitted.
waiting Legal, but not yet allowed to land: a required check, a review, or a branch protection rule has not cleared. Only mode: when_ready parks here; it is retried.
succeeded Landed. mergedCommitSha is the resulting 40-character commit id.
failed Did not land. lastError.code says why.

Three invariants worth building on, each enforced in the database rather than by convention:

  • mergedCommitSha is present exactly when the merge succeeded. Its presence is equivalent to success; you do not need to trust the status field alone.
  • At most one successful merge per publication, ever. Not “one at a time” — once. A publication that has landed cannot be landed again, and a request to do so is a 409.
  • At most one live merge per publication. While one is admitted, attempted or waiting, a second merge id for that publication is 409 cloud_publication_merge_conflict.

attempts is exposed because the server retries transient failures on its own. A rising attempts on a waiting merge is the system working, not your problem.

They arrive in lastError.code on the merge, and in the errorCode of wamp.publication.merge_failed:

lastError.code What happened
pull_request_changed The head moved after the publication. Nothing was merged. Publish the new revision and merge that.
pull_request_closed Someone closed the pull request.
merge_not_ready The pull request cannot land yet — most commonly because it is still a draft, which is the default a publication opens with.
merge_not_allowed The repository refuses this merge, for example a strategy the repository does not permit.

The draft case is the one that surprises people: draft defaults to true, and a draft pull request is not mergeable. With mode: now the merge ends failed with merge_not_ready; with mode: when_ready it sits in waiting and lands once the pull request is marked ready and its checks pass. Choose when_ready when a human or a CI pipeline is expected to act, and now when you believe the pull request is already landable and want a definite answer.

Review, publish, poll, merge — four calls, two ids you mint.

Terminal window
PUB_ID=$(uuidgen | tr 'A-Z' 'a-z')
MERGE_ID=$(uuidgen | tr 'A-Z' 'a-z')
REVISION=$(curl -sS "$WAMP_API/v1/sessions/$SESSION_ID/repository/review" \
-H "Authorization: Bearer $WAMP_TOKEN" | jq -r '.review.revision')
curl -sS -X PUT "$WAMP_API/v1/sessions/$SESSION_ID/publications/$PUB_ID" \
-H "Authorization: Bearer $WAMP_TOKEN" \
-H 'Content-Type: application/json' \
-d "$(jq -nc --arg r "$REVISION" '{
revision: $r,
title: "Fix CSV export rounding",
body: "Adds a regression test for the invoice total.",
draft: false
}')"
# Poll until status is succeeded or failed.
curl -sS "$WAMP_API/v1/sessions/$SESSION_ID/publications/$PUB_ID" \
-H "Authorization: Bearer $WAMP_TOKEN" | jq '.publication | {status, result, lastError}'
curl -sS -X PUT \
"$WAMP_API/v1/sessions/$SESSION_ID/publications/$PUB_ID/merges/$MERGE_ID" \
-H "Authorization: Bearer $WAMP_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"strategy":"squash","mode":"when_ready"}'

Note draft: false in the publish body: it is what makes the merge above able to land at all. Then poll the merge the same way, and watch for wamp.publication.merge_succeeded or wamp.publication.merge_failed in the event log if you are already following the session.

What you may assume, and what you must handle

Section titled “What you may assume, and what you must handle”

Assume. A publication’s revision and a merge’s expectedHeadSha are fixed at admission. A merge never lands a commit other than that head. A publication that succeeded twice does not exist, and a publication that merged twice cannot. Retrying an identical PUT is free.

Handle. 409 cloud_publication_in_progress while one is in flight. 409 cloud_publication_conflict and 409 cloud_publication_merge_conflict from a replay whose body drifted — usually a re-read review with a new revision, which needs a new publication id. 403 cloud_repository_grant_required if publishing authority was revoked between session creation and publish. 409 cloud_publication_merge_not_ready when you merge too early. And a merge that ends failed with pull_request_changed, which means starting the review-publish cycle again rather than retrying the same merge id.