Skip to content
Closed
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ integration PRs, follows `main` through production, and pauses after failures.

## Install

Install the reviewed `v0.2.6` source commit directly from GitHub:
Install the reviewed `v0.2.7` source commit directly from GitHub:

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

Expand Down Expand Up @@ -86,7 +86,7 @@ aligned with `pipeline.ci_workflows`. The privileged worker never checks out or
executes pull-request code. Pin the Action to the full reviewed release commit:

```yaml
- uses: Forward-Future/DeployBot@a0e2c833282e63bf5db4339f99773aa9ba2f0c82
- uses: Forward-Future/DeployBot@90f231d7d566901a5f1c0172ac170ec7fab61da0
```

The Action uses GitHub's built-in workflow token. GitHub intentionally does not
Expand Down
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@a0e2c833282e63bf5db4339f99773aa9ba2f0c82",
"deploybot-merge-queue[mcp] @ git+https://github.com/Forward-Future/DeployBot.git@90f231d7d566901a5f1c0172ac170ec7fab61da0",
"deploybot-mcp"
]
}
Expand Down
2 changes: 1 addition & 1 deletion adapters/codex/agent-merge-queue/.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@a0e2c833282e63bf5db4339f99773aa9ba2f0c82",
"deploybot-merge-queue[mcp] @ git+https://github.com/Forward-Future/DeployBot.git@90f231d7d566901a5f1c0172ac170ec7fab61da0",
"deploybot-mcp"
]
}
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@a0e2c833282e63bf5db4339f99773aa9ba2f0c82",
"deploybot-merge-queue[mcp] @ git+https://github.com/Forward-Future/DeployBot.git@90f231d7d566901a5f1c0172ac170ec7fab61da0",
"deploybot-mcp"
]
}
Expand Down
4 changes: 2 additions & 2 deletions examples/github-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,5 @@ jobs:
with:
ref: ${{ github.event.repository.default_branch }}
persist-credentials: false
# v0.2.6 implementation; keep the full commit for privileged workflows.
- uses: Forward-Future/DeployBot@a0e2c833282e63bf5db4339f99773aa9ba2f0c82
# v0.2.7 implementation; keep the full commit for privileged workflows.
- uses: Forward-Future/DeployBot@90f231d7d566901a5f1c0172ac170ec7fab61da0
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "deploybot-merge-queue"
version = "0.2.6"
version = "0.2.7"
description = "DeployBot: a provider-neutral GitHub merge queue for coding agents"
readme = "README.md"
license = "MIT"
Expand Down
2 changes: 1 addition & 1 deletion src/agent_merge_queue/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""DeployBot: a provider-neutral GitHub merge queue for coding agents."""

__version__ = "0.2.6"
__version__ = "0.2.7"
23 changes: 22 additions & 1 deletion src/agent_merge_queue/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2757,7 +2757,28 @@ def command_react(
)
raise
release: dict[str, Any] | None = None
if follow and drained.get("merged"):
should_follow = bool(drained.get("merged"))
if follow and not should_follow:
current = release_state(
main_sha=client.base_sha(),
runs=client.workflow_runs(),
config=client.config.pipeline,
)
# The worker that merged a batch can be replaced while it waits for CI
# (for example, when an older workflow still cancels in-progress runs).
# A later event must take over that release even though it did not merge
# anything itself. Avoid holding every idle event open when no main CI
# exists, and revisit a verified release only when merged thread records
# still need their final completion marker.
if current["state"] == "testing":
should_follow = current.get("latest_ci") is not None
elif current["state"] == "verified":
should_follow = any(
record.get("phase") == "merged" for record in client.thread_records()
)
else:
should_follow = True
if follow and should_follow:
release = command_follow(
client,
timeout_seconds=timeout_seconds,
Expand Down
46 changes: 46 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1191,6 +1191,52 @@ def test_reactor_dispatches_ci_once_after_a_merged_batch(self) -> None:
client.dispatch_ci_workflows.assert_called_once_with()
self.assertEqual(result["dispatched_ci"], [{"id": 7, "name": "CI"}])

def test_reactor_resumes_release_when_original_merge_worker_was_replaced(
self,
) -> None:
sha = "a" * 40
client = Mock()
client.config = CONFIG
client.pipeline_control.return_value = {"state": "running"}
client.base_sha.return_value = sha
client.workflow_runs.return_value = [
{
"id": 42,
"name": "CI",
"head_sha": sha,
"status": "completed",
"conclusion": "success",
"event": "workflow_dispatch",
"created_at": "2026-06-20T00:00:00Z",
"updated_at": "2026-06-20T00:01:00Z",
}
]
empty = FreezeResult(None, [], [], [], [])
with (
patch("agent_merge_queue.cli.promote_integrations", return_value=[]),
patch("agent_merge_queue.cli.command_promote", return_value={}),
patch("agent_merge_queue.cli.freeze_queue", return_value=empty),
patch(
"agent_merge_queue.cli.command_drain",
return_value={"merged": []},
),
patch(
"agent_merge_queue.cli.command_follow",
return_value={"state": "verified", "main_sha": sha},
) as follow_release,
redirect_stdout(io.StringIO()),
):
result = command_react(client, follow=True, timeout_seconds=10)

follow_release.assert_called_once_with(
client,
timeout_seconds=10,
poll_seconds=10,
json_output=False,
emit=False,
)
self.assertEqual(result["release"]["state"], "verified")

def test_reactor_pauses_when_post_merge_ci_dispatch_fails(self) -> None:
client = Mock()
client.config = CONFIG
Expand Down
2 changes: 1 addition & 1 deletion tests/test_skill.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

ROOT = Path(__file__).resolve().parents[1]
CANONICAL = ROOT / "skills" / "deploybot" / "SKILL.md"
RELEASE_COMMIT = "a0e2c833282e63bf5db4339f99773aa9ba2f0c82"
RELEASE_COMMIT = "90f231d7d566901a5f1c0172ac170ec7fab61da0"
CHECKOUT_COMMIT = "9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0"


Expand Down