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
14 changes: 14 additions & 0 deletions src/github_agent_bridge/autoupdate.py
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,7 @@ def plan_update(

def record_update_plan(db: str | Path, plan: dict[str, Any]) -> dict[str, Any]:
queue = JobQueue(db)
previous = load_update_state(queue)
state = {
"updated_at": utc_now(),
"installed_version": plan["installed_version"],
Expand All @@ -918,5 +919,18 @@ def record_update_plan(db: str | Path, plan: dict[str, Any]) -> dict[str, Any]:
"service_plan": plan.get("service_plan", {}),
"warnings": plan.get("warnings", []),
}
if plan["decision"] == "noop" and previous.get("executor_reload_pending"):
# A refresh can observe the newly installed version before the
# completion pass has restarted the executor. Keep the deferred action
# actionable instead of replacing it with an up-to-date noop plan.
for key in (
"decision",
"executor_reload_pending",
"blocked_reason",
"classification",
"service_plan",
):
if key in previous:
state[key] = previous[key]
save_update_state(queue, state)
return state
42 changes: 42 additions & 0 deletions tests/test_autoupdate.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,48 @@ def test_executor_update_records_pending_reload_when_jobs_are_active(tmp_path, m
assert load_update_state(q)["service_plan"]["deferred"][0]["command"] == "restart"


def test_noop_refresh_preserves_pending_executor_reload(tmp_path, monkeypatch):
monkeypatch.setattr("github_agent_bridge.actors.github_actor_details_for_context", lambda ctx, *, gh_bin="gh": None)
db = tmp_path / "bridge.sqlite3"
q = JobQueue(db)
job_id = enqueue_job(q)

deferred_plan = plan_update(
db,
repo_dir=tmp_path,
installed_version="1.2.3",
runner=release_runner("v1.2.4", ["src/github_agent_bridge/executor.py"]),
)
record_update_plan(db, deferred_plan)
q.finish(job_id, "done", "finished")

noop_plan = plan_update(
db,
repo_dir=tmp_path,
installed_version="1.2.4",
runner=release_runner("v1.2.4", []),
)
state = record_update_plan(db, noop_plan)

assert noop_plan["decision"] == "noop"
assert state["installed_version"] == "1.2.4"
assert state["decision"] == "stage_defer_executor_reload"
assert state["executor_reload_pending"] is True
assert [action["unit"] for action in state["service_plan"]["deferred"]] == [
"github-agent-bridge.service"
]
calls: list[list[str]] = []
completion = complete_pending_reload(
db,
systemctl_bin="systemctl-test",
runner=lambda args, cwd: calls.append(list(args)) or completed("ok"),
)

assert completion["completed"] is True
assert calls == [["systemctl-test", "--user", "restart", "github-agent-bridge.service"]]
assert load_update_state(q)["executor_reload_pending"] is False


def test_migration_update_is_deferred_while_jobs_are_active(tmp_path, monkeypatch):
monkeypatch.setattr("github_agent_bridge.actors.github_actor_details_for_context", lambda ctx, *, gh_bin="gh": None)
db = tmp_path / "bridge.sqlite3"
Expand Down
Loading