Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Install the reviewed `v0.2.25` source commit directly from GitHub:

```bash
python3 -m pip install \
'deploybot-merge-queue[mcp] @ git+https://github.com/Forward-Future/DeployBot.git@12c6c03aa76a553fa4068279baa29e90a30bbeb1'
'deploybot-merge-queue[mcp] @ git+https://github.com/Forward-Future/DeployBot.git@3fb42e2e3cf3a6f21cddf43e3d06deaa24a3ac80'
deploybot init
```

Expand Down Expand Up @@ -87,18 +87,19 @@ the user does not repeat `deploy`. No polling timer is involved.
Install `examples/github-workflow.yml` on the default branch. It reacts to
deploy labels, ready/synchronize events, reviews, named CI `workflow_run`
completions, and completed external check suites. Keep its `workflows` list
aligned with `pipeline.ci_workflows`. A five-minute scheduled reconciliation
rereads all durable state in case GitHub concurrency coalesces the last pending
event in a burst. The privileged worker never checks out or executes
pull-request code. The Action advances releases to the configured admission
gate. In the default `merged` mode it returns after each healthy observation,
leaving completion to later release events and keeping the serialized merge
worker free. It can still dispatch deployment when GitHub suppresses the
`workflow_run` event for token-dispatched CI. Pin the Action to the full reviewed
release commit:
aligned with `pipeline.ci_workflows`. Each wake runs two independently
serialized jobs: the queue reactor can admit more ready work immediately, while
the release-only follower owns cumulative exact `main` through CI, deployment,
and verification. The follower exits immediately when no release needs work and
dispatches deployment when GitHub suppresses the `workflow_run` handoff for
token-dispatched CI, avoiding a wait for the five-minute scheduled
reconciliation. The schedule still rereads all durable state if GitHub
concurrency coalesces the last event in a burst. Neither privileged job checks
out or executes pull-request code. Pin the Action to the full reviewed release
commit:

```yaml
- uses: Forward-Future/DeployBot@12c6c03aa76a553fa4068279baa29e90a30bbeb1
- uses: Forward-Future/DeployBot@3fb42e2e3cf3a6f21cddf43e3d06deaa24a3ac80
```

The Action uses GitHub's built-in workflow token. GitHub intentionally does not
Expand Down
30 changes: 23 additions & 7 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
name: DeployBot
description: Promote, batch, merge, and follow agent-authored pull requests
inputs:
mode:
description: Run queue reaction or release-only follow
required: false
default: react
config:
description: Repository-relative merge queue policy path
required: false
Expand Down Expand Up @@ -32,16 +36,28 @@ runs:
- shell: bash
env:
DEPLOYBOT_CONFIG: ${{ inputs.config }}
DEPLOYBOT_MODE: ${{ inputs.mode }}
GH_TOKEN: ${{ inputs.token || github.token }}
DEPLOYBOT_FOLLOW: ${{ inputs.follow }}
DEPLOYBOT_DISPATCH_CI: ${{ inputs.dispatch_ci }}
DEPLOYBOT_TIMEOUT: ${{ inputs.timeout }}
run: |
args=(react --timeout "$DEPLOYBOT_TIMEOUT")
if [[ "$DEPLOYBOT_DISPATCH_CI" == "true" ]]; then
args+=(--dispatch-ci)
fi
if [[ "$DEPLOYBOT_FOLLOW" == "true" ]]; then
args+=(--follow)
fi
case "$DEPLOYBOT_MODE" in
react)
args=(react --timeout "$DEPLOYBOT_TIMEOUT")
if [[ "$DEPLOYBOT_DISPATCH_CI" == "true" ]]; then
args+=(--dispatch-ci)
fi
if [[ "$DEPLOYBOT_FOLLOW" == "true" ]]; then
args+=(--follow)
fi
;;
follow)
args=(follow --timeout "$DEPLOYBOT_TIMEOUT" --if-needed)
;;
*)
echo "mode must be react or follow" >&2
exit 2
;;
esac
deploybot --config "$DEPLOYBOT_CONFIG" "${args[@]}"
2 changes: 1 addition & 1 deletion adapters/claude-code/.mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"command": "uvx",
"args": [
"--from",
"deploybot-merge-queue[mcp] @ git+https://github.com/Forward-Future/DeployBot.git@12c6c03aa76a553fa4068279baa29e90a30bbeb1",
"deploybot-merge-queue[mcp] @ git+https://github.com/Forward-Future/DeployBot.git@3fb42e2e3cf3a6f21cddf43e3d06deaa24a3ac80",
"deploybot-mcp"
]
}
Expand Down
15 changes: 15 additions & 0 deletions adapters/claude-code/skills/deploybot/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ commands. Re-read GitHub, honor a pipeline pause, freeze one exact batch,
preserve first-in order unless dependencies require otherwise, and skip blocked
items that do not block independent work.

Use `react_to_delivery_event` (or `deploybot react --follow --dispatch-ci`) for
queue-changing coordination. `follow_release` / `deploybot follow` is
release-only: it follows the current base revision but never promotes or drains
queued pull requests. Do not substitute it for a reaction when work is queued.
In GitHub Actions, keep queue reaction and release-only follow in separate
concurrency groups so `release_admission = "merged"` can admit more work while
one follower owns cumulative exact-main CI and deployment.

Merge independent ready pull requests back-to-back. Route source-overlap groups
through `create_integration_pull_request`; when policy mode is `all`, validate
the entire frozen batch through that cumulative PR. Never invent a conflict
Expand All @@ -92,6 +100,13 @@ after its new exact head passes. Keep release tracking event-driven: in
after a healthy merge while later events continue CI, deployment, and health
tracking. Scheduled reconciliation is a fallback, not the normal promotion path.

For queue-wide requests such as "all open PRs," treat the target set as live,
not as the first snapshot. After each verified cumulative release, refresh
pipeline status, the queue plan, and the provider's open-PR list once more.
Admit newly opened authorized work and react again. Stop only when the queue,
unbound list, and provider open-PR list are all empty at the same fresh
boundary; do not leave a tight idle polling loop behind.

Genuine repair blocks may hold overlapping ready work for the configured bounded
repair window, but they remain merge-ineligible until the trusted source agent
resumes the freshly reviewed exact head. If `main` advances while a repair is
Expand Down
8 changes: 8 additions & 0 deletions adapters/claude-code/skills/manage-merge-queue/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ and use `resume_pull_request` after fresh review. In `release_admission =
"merged"` mode, admit independent ready work immediately after merge while
later events track CI and deployment; a later failure pauses the pipeline.

Use the reaction path for queue work. `follow_release` / `deploybot follow` is
release-only and never promotes or drains queued pull requests. For an
"all open PRs" request, refresh status, the plan, and the provider's open list
after the verified release; react again for newly opened authorized work and
stop only when all three are empty at the same fresh boundary.
In GitHub Actions, keep queue reaction and release-only follow in separate
concurrency groups so release ownership never holds up merged-mode admission.

A genuine repair remains merge-ineligible, but DeployBot may temporarily hold
overlapping ready work for the configured bounded repair window so concurrent
merges do not repeatedly invalidate the replacement head.
Expand Down
15 changes: 15 additions & 0 deletions adapters/codex/agent-merge-queue/skills/deploybot/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ Only the designated coordinator may run `deploybot promote`, `deploybot react`,
pause, freeze one exact batch, preserve first-in order unless dependencies
require otherwise, and skip blocked items that do not block independent work.

Use `deploybot react --follow --dispatch-ci` for queue-changing coordination.
`deploybot follow` is release-only: it follows the current `main` revision but
never promotes or drains queued pull requests. Do not substitute it for
`react` when work is queued.
In GitHub Actions, keep queue reaction and release-only follow in separate
concurrency groups so `release_admission = "merged"` can admit more work while
one follower owns cumulative exact-main CI and deployment.

Merge independent ready pull requests back-to-back. Route source-overlap groups
through `deploybot integrate`; when policy mode is `all`, validate the entire
frozen batch through that cumulative PR. Never invent a conflict resolution.
Expand All @@ -86,6 +94,13 @@ its new exact head passes. Keep release tracking event-driven: in
after a healthy merge while later events continue CI, deployment, and health
tracking. Scheduled reconciliation is a fallback, not the normal promotion path.

For queue-wide requests such as "all open PRs," treat the target set as live,
not as the first snapshot. After each verified cumulative release, refresh
`deploybot status --json`, `deploybot plan --json`, and the provider's open-PR
list once more. Admit newly opened authorized work and react again. Stop only
when the queue, unbound list, and provider open-PR list are all empty at the
same fresh boundary; do not leave a tight idle polling loop behind.

Genuine repair blocks may hold overlapping ready work for the configured bounded
repair window, but they remain merge-ineligible until the trusted source agent
resumes the freshly reviewed exact head. If `main` advances while a repair is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ after fresh review. In `release_admission = "merged"` mode, admit independent
ready work immediately after merge while later events track CI and deployment;
a later failure pauses the pipeline.

Use `deploybot react --follow --dispatch-ci` for queue work. `deploybot follow`
is release-only and never promotes or drains queued pull requests. For an
"all open PRs" request, refresh status, the plan, and the provider's open list
after the verified release; react again for newly opened authorized work and
stop only when all three are empty at the same fresh boundary.
In GitHub Actions, keep queue reaction and release-only follow in separate
concurrency groups so release ownership never holds up merged-mode admission.

A genuine repair remains merge-ineligible, but DeployBot may temporarily hold
overlapping ready work for the configured bounded repair window so concurrent
merges do not repeatedly invalidate the replacement head.
Expand Down
2 changes: 1 addition & 1 deletion adapters/cursor/.cursor/mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"command": "uvx",
"args": [
"--from",
"deploybot-merge-queue[mcp] @ git+https://github.com/Forward-Future/DeployBot.git@12c6c03aa76a553fa4068279baa29e90a30bbeb1",
"deploybot-merge-queue[mcp] @ git+https://github.com/Forward-Future/DeployBot.git@3fb42e2e3cf3a6f21cddf43e3d06deaa24a3ac80",
"deploybot-mcp"
]
}
Expand Down
9 changes: 9 additions & 0 deletions adapters/cursor/.cursor/rules/deploybot.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ the stable Cursor thread ID, never prompts or transcripts. Refresh intent only
after replacement-head review. Only the coordinator may react, integrate,
drain, follow, pause, or resume repaired work.

Use the reaction path for queue-changing coordination. `follow_release` is
release-only: it observes the current base revision and never promotes or
drains queued pull requests. For a queue-wide request such as "all open PRs,"
refresh `pipeline_status`, `queue_plan`, and the provider's open pull-request
list after the verified release. Stop only when the queue, unbound PRs, and
provider list are all empty at that same fresh boundary.
In GitHub Actions, keep queue reaction and release-only follow in separate
concurrency groups so release ownership never holds up merged-mode admission.

Honor `pipeline.release_admission`. In `merged` mode, admit the next independent
ready PR immediately after a healthy merge while release events continue CI,
deployment, and health tracking. A later failure pauses future merges normally.
Expand Down
9 changes: 9 additions & 0 deletions adapters/cursor/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ fresh review, and follow cumulative `main` through verified deployment. When
`release_admission = "merged"`, admit independent ready work immediately after
merge while release events continue asynchronously; later failures still pause.

Use the reaction path for queue-changing coordination. `follow_release` is
release-only: it observes the current base revision and never promotes or
drains queued pull requests. For a queue-wide request such as "all open PRs,"
refresh `pipeline_status`, `queue_plan`, and the provider's open pull-request
list after the verified release. Stop only when the queue, unbound PRs, and
provider list are all empty at that same fresh boundary.
In GitHub Actions, keep queue reaction and release-only follow in separate
concurrency groups so release ownership never holds up merged-mode admission.

For each verified `thread_notifications` entry, post its message back to the
native PR-opening thread and only then call `acknowledge_thread_deployment`. Leave
failed notifications `pending` for a later retry, and pass the matching
Expand Down
12 changes: 8 additions & 4 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ never substitute its own thread ID.
| `deploybot drain [--json]` | Freeze as needed and merge independent ready entries in the active batch. |
| `deploybot react [--follow] [--dispatch-ci] [--timeout SECONDS]` | Run the event-driven promotion, batching, merge, and optional release-follow flow. The timeout defaults to 1800 seconds. |
| `deploybot integrate [--all]` | Scaffold a cumulative integration PR for overlap groups, or the whole frozen batch with `--all`. |
| `deploybot follow [--timeout SECONDS] [--poll SECONDS] [--json]` | Follow the newest exact base-branch head through CI, deployment, and HTTP verification. Defaults: 1800-second timeout and 10-second poll. |
| `deploybot follow [--if-needed] [--timeout SECONDS] [--poll SECONDS] [--json]` | Follow the newest exact base-branch head through CI, deployment, and HTTP verification. `--if-needed` exits immediately when no unfinished release exists. This command never promotes or drains queued PRs; when queue work remains its output includes a `queue_handoff` naming `deploybot react --follow --dispatch-ci`. Defaults: 1800-second timeout and 10-second poll. |
| `deploybot pause --reason TEXT` | Pause merging after a delivery failure. |
| `deploybot unpause --sha SHA --control-id ID [--follow] [--dispatch-ci] [--timeout SECONDS] [--no-wake]` | Conditionally resume the matching failed release after fresh status revalidation and verified repair; a running record can clear only that unique pause, so changed control or advanced main fails closed. The recovery records the exact SHA and reason it resumed so a stale reread of the same failure cannot re-pause it. After recording the recovery, DeployBot immediately reacts so the elected repair merges without waiting for the next event or the five-minute sweep; `--no-wake` records the recovery only, and `--follow`/`--dispatch-ci`/`--timeout` shape that wake-up reaction. The original deploy instruction remains sufficient unless rollback, bypass, or mismatched recovery expands authority. |
| `deploybot claim-release-repair --provider CLIENT --thread-id ID [--thread-url URL] [--sha SHA]` | Atomically claim the owner-encoded deterministic repair branch for the current failed exact-main release. Other threads recover the same owner from the ref instead of creating duplicate repair PRs. |
Expand Down Expand Up @@ -200,19 +200,23 @@ Provider fields are:

## GitHub Action

The composite Action runs `deploybot react` from the checked-out default branch.
The composite Action runs either queue reaction or release-only follow from the
checked-out default branch. The example workflow uses separate job concurrency
groups so `release_admission = "merged"` can keep admitting ready work while one
cumulative release follower owns CI and deployment.

| Input | Default | Purpose |
| --- | --- | --- |
| `mode` | `react` | Run `react` for queue work or `follow` for release-only ownership. Follow mode uses `--if-needed` and always continues through verified deployment. |
| `config` | `.mergequeue.toml` | Repository-relative policy path. |
| `follow` | `"true"` | Add `--follow` to the event worker. |
| `follow` | `"true"` | In `react` mode, add `--follow` to advance only to the configured merge-admission gate. The split example workflow sets this to `"false"` because its independent release job owns completion. |
| `dispatch_ci` | `"true"` | Dispatch configured CI after a merge made with `github.token`. |
| `timeout` | `"1800"` | Release-follow timeout in seconds. |
| `token` | `""` | GitHub App installation token used to author integration PRs so normal PR checks and events run; empty falls back to `github.token`. |

The workflow needs `contents: write`, `pull-requests: write`, `checks: read`,
`issues: write`, and `actions: write`. Use the event filters, concurrency group,
and scheduled full-state reconciliation in
separate queue/release concurrency groups, and scheduled full-state reconciliation in
[`examples/github-workflow.yml`](../examples/github-workflow.yml), pin the Action
to a reviewed full commit SHA, and keep its `workflow_run.workflows` list aligned
with `pipeline.ci_workflows`.
Expand Down
40 changes: 30 additions & 10 deletions examples/github-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ permissions:
issues: write
actions: write # required for post-merge workflow_dispatch with github.token

concurrency:
group: deploybot-${{ github.repository }}
cancel-in-progress: false

jobs:
react:
# If CI intentionally hands off from a protected release branch, replace
Expand Down Expand Up @@ -72,16 +68,40 @@ jobs:
)
)
runs-on: ubuntu-latest
concurrency:
group: deploybot-queue-${{ github.repository }}
cancel-in-progress: false
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
with:
ref: ${{ github.event.repository.default_branch }}
persist-credentials: false
# v0.2.25 implementation; keep the full commit for privileged workflows.
- uses: Forward-Future/DeployBot@12c6c03aa76a553fa4068279baa29e90a30bbeb1
# Reviewed split-coordinator implementation; keep the full commit for
# privileged workflows.
- uses: Forward-Future/DeployBot@3fb42e2e3cf3a6f21cddf43e3d06deaa24a3ac80
with:
# PR and review events reconcile immediately. Release-owner events
# advance to the configured admission gate; "merged" observations
# return quickly, while the schedule remains a fallback reconciliation.
follow: ${{ github.event_name == 'workflow_run' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }}
# Keep queue admission independent from release ownership so merged
# mode can continue admitting ready work while CI and Deploy run.
follow: "false"
timeout: ${{ (github.event_name == 'workflow_run' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && '2400' || '600' }}

release:
needs: react
# Release progress is independent from a queue-specific failure, but an
# intentionally cancelled or policy-skipped reaction starts no follower.
if: ${{ always() && (needs.react.result == 'success' || needs.react.result == 'failure') }}
runs-on: ubuntu-latest
concurrency:
group: deploybot-release-${{ github.repository }}
cancel-in-progress: false
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
with:
ref: ${{ github.event.repository.default_branch }}
persist-credentials: false
# Release-only ownership never promotes or drains pull requests. It
# exits immediately when no exact-main release needs work.
- uses: Forward-Future/DeployBot@3fb42e2e3cf3a6f21cddf43e3d06deaa24a3ac80
with:
mode: follow
timeout: "2400"
Loading