From e6a6a6042e6765e5c1aba62c3ad4c315da64608c Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Tue, 21 Apr 2026 10:12:00 -0700 Subject: [PATCH] C0: suggest mise run runner-logs in container-build-and-release After dispatching, poll the Forgejo API for the run matching our head_sha and print `mise run runner-logs ` so the suggested monitor command is one copy-paste away. Falls back to the bare command if the poll times out. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...ontainer-build-suggest-runner-logs.misc.md | 1 + mise-tasks/container-build-and-release | 61 ++++++++++++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 docs/changelog.d/+container-build-suggest-runner-logs.misc.md diff --git a/docs/changelog.d/+container-build-suggest-runner-logs.misc.md b/docs/changelog.d/+container-build-suggest-runner-logs.misc.md new file mode 100644 index 0000000..d10ea51 --- /dev/null +++ b/docs/changelog.d/+container-build-suggest-runner-logs.misc.md @@ -0,0 +1 @@ +`container-build-and-release` now prints the specific `mise run runner-logs ` command after dispatching, polling the Forgejo API to resolve the run number for the commit it just triggered. diff --git a/mise-tasks/container-build-and-release b/mise-tasks/container-build-and-release index 2e1be27..afa970e 100755 --- a/mise-tasks/container-build-and-release +++ b/mise-tasks/container-build-and-release @@ -15,6 +15,7 @@ Dockerfile and Nix builds in a single workflow. import subprocess import sys +import time from pathlib import Path import httpx @@ -48,6 +49,52 @@ def get_forge_token() -> str: return result.stdout.strip() +def max_run_number(headers: dict[str, str]) -> int: + """Return the highest current run_number for WORKFLOW, or 0 if none.""" + resp = httpx.get( + f"{FORGE_API}/repos/{REPO}/actions/tasks", + params={"limit": 50}, + headers=headers, + timeout=15, + ) + if resp.status_code != 200: + return 0 + runs = [ + t["run_number"] + for t in resp.json().get("workflow_runs", []) + if t.get("workflow_id") == WORKFLOW + ] + return max(runs, default=0) + + +def find_dispatched_run( + ref: str, floor: int, headers: dict[str, str], timeout_s: int = 20 +) -> int | None: + """Poll the tasks endpoint for the run triggered by our dispatch. + + Matches by head_sha + workflow + run_number > floor so we don't pick up + an older build of the same commit or a concurrent unrelated dispatch. + """ + deadline = time.monotonic() + timeout_s + while time.monotonic() < deadline: + resp = httpx.get( + f"{FORGE_API}/repos/{REPO}/actions/tasks", + params={"limit": 20}, + headers=headers, + timeout=15, + ) + if resp.status_code == 200: + for task in resp.json().get("workflow_runs", []): + if ( + task.get("head_sha") == ref + and task.get("workflow_id") == WORKFLOW + and task.get("run_number", 0) > floor + ): + return task["run_number"] + time.sleep(1) + return None + + def list_containers() -> None: typer.echo("Available containers:") for d in sorted(Path("containers").iterdir()): @@ -112,7 +159,8 @@ def main( if dry_run: typer.echo(f"[dry-run] Would dispatch {WORKFLOW}") typer.echo() - typer.echo(f"Monitor builds at: {FORGE_ACTIONS}") + typer.echo("Monitor builds with: mise run runner-logs") + typer.echo(f" or visit: {FORGE_ACTIONS}") return token = get_forge_token() @@ -132,6 +180,10 @@ def main( typer.echo("Push your changes before triggering a build: git push origin main") raise typer.Exit(1) + # Snapshot the highest existing run_number so we can identify the one + # our dispatch creates. + floor = max_run_number(headers) + url = f"{FORGE_API}/repos/{REPO}/actions/workflows/{WORKFLOW}/dispatches" payload = { "ref": "main", @@ -148,7 +200,12 @@ def main( raise typer.Exit(1) typer.echo() - typer.echo(f"Monitor builds at: {FORGE_ACTIONS}") + run_number = find_dispatched_run(ref, floor, headers) + if run_number is not None: + typer.echo(f"Monitor builds with: mise run runner-logs {run_number}") + else: + typer.echo("Monitor builds with: mise run runner-logs") + typer.echo(f" or visit: {FORGE_ACTIONS}") if __name__ == "__main__":