babysit-pr

Contributors

GitHub-linked commit authors for this SKILL.md at the saved revision. Co-authors and history before file renames are not included.

File history ↗

Babysit a GitHub pull request after creation by continuously polling review comments, CI checks/workflow runs, and mergeability state until the PR is merged/closed or user help is required. Diagnose failures, retry likely flaky failures up to 3 times, auto-fix/push branch-related issues when appropriate, and keep watching open PRs so fresh review feedback is surfaced promptly. Use when the user asks Codex to monitor a PR, watch CI, handle review comments, or keep an eye on failures and feedback on an open PR.

.codex/skills/babysit-pr/SKILL.md

Download bundle ↓
main · 18d7ace6 bundle filesScanned 2026-09-14

scripts/test_gh_pr_watch.py

1,959 tokens · o200k_base · 8,080 bytes

Source excerpt starting at line 1.
import argparseimport importlib.utilfrom pathlib import Path import pytest  MODULE_PATH = Path(__file__).with_name("gh_pr_watch.py")MODULE_SPEC = importlib.util.spec_from_file_location("gh_pr_watch", MODULE_PATH)gh_pr_watch = importlib.util.module_from_spec(MODULE_SPEC)assert MODULE_SPEC.loader is not NoneMODULE_SPEC.loader.exec_module(gh_pr_watch)  def sample_pr():    return {        "number": 123,        "url": "https://github.com/openai/codex/pull/123",        "repo": "openai/codex",        "head_sha": "abc123",        "head_branch": "feature",        "state": "OPEN",        "merged": False,        "closed": False,        "mergeable": "MERGEABLE",        "merge_state_status": "CLEAN",        "review_decision": "",    }  def sample_checks(**overrides):    checks = {        "pending_count": 0,        "failed_count": 0,        "passed_count": 12,        "all_terminal": True,    }    checks.update(overrides)    return checks  def test_collect_snapshot_fetches_review_items_before_ci(monkeypatch, tmp_path):    call_order = []    pr = sample_pr()     monkeypatch.setattr(gh_pr_watch, "resolve_pr", lambda *args, **kwargs: pr)    monkeypatch.setattr(gh_pr_watch, "load_state", lambda path: ({}, True))    monkeypatch.setattr(        gh_pr_watch,        "get_authenticated_login",        lambda: call_order.append("auth") or "octocat",    )    monkeypatch.setattr(        gh_pr_watch,        "fetch_new_review_items",        lambda *args, **kwargs: call_order.append("review") or [],    )    monkeypatch.setattr(        gh_pr_watch,        "get_pr_checks",        lambda *args, **kwargs: call_order.append("checks") or [],    )    monkeypatch.setattr(        gh_pr_watch,        "summarize_checks",        lambda checks: call_order.append("summarize") or sample_checks(),    )    monkeypatch.setattr(        gh_pr_watch,        "get_workflow_runs_for_sha",        lambda *args, **kwargs: call_order.append("workflow") or [],    )    monkeypatch.setattr(        gh_pr_watch,        "failed_runs_from_workflow_runs",        lambda *args, **kwargs: call_order.append("failed_runs") or [],    )    monkeypatch.setattr(        gh_pr_watch,        "failed_jobs_from_workflow_runs",        lambda *args, **kwargs: call_order.append("failed_jobs") or [],    )    monkeypatch.setattr(        gh_pr_watch,        "recommend_actions",        lambda *args, **kwargs: call_order.append("recommend") or ["idle"],    )    monkeypatch.setattr(gh_pr_watch, "save_state", lambda *args, **kwargs: None)     args = argparse.Namespace(        pr="123",        repo=None,        state_file=str(tmp_path / "watcher-state.json"),        max_flaky_retries=3,    )     gh_pr_watch.collect_snapshot(args)     assert call_order.index("review") < call_order.index("checks")    assert call_order.index("review") < call_order.index("workflow")  def test_recommend_actions_prioritizes_review_comments():    actions = gh_pr_watch.recommend_actions(        sample_pr(),        sample_checks(failed_count=1),        [{"run_id": 99}],        [],        [{"kind": "review_comment", "id": "1"}],        0,        3,    )     assert actions == [        "process_review_comment",        "diagnose_ci_failure",        "retry_failed_checks",    ]  def test_pending_review_feedback_surfaces_only_after_publication(monkeypatch):    state = {        "seen_review_comment_ids": ["20"],        "seen_review_ids": ["10"],    }    review = {        "id": 10,        "user": {"login": "octocat"},        "author_association": "MEMBER",        "state": "PENDING",        "body": "Please rename this.",        "created_at": "2026-06-08T10:00:00Z",        "submitted_at": None,        "html_url": "https://github.com/openai/codex/pull/123#pullrequestreview-10",    }    review_comment = {        "id": 20,        "pull_request_review_id": 10,        "user": {"login": "octocat"},        "author_association": "MEMBER",        "body": "Please rename this.",        "created_at": "2026-06-08T10:00:00Z",        "path": "src/example.rs",        "line": 7,        "html_url": "https://github.com/openai/codex/pull/123#discussion_r20",    }     def fake_list(endpoint, **kwargs):        if endpoint.endswith("/issues/123/comments"):            return []        if endpoint.endswith("/pulls/123/comments"):            return [review_comment]        if endpoint.endswith("/pulls/123/reviews"):            return [review]        raise AssertionError(f"unexpected endpoint: {endpoint}")     monkeypatch.setattr(gh_pr_watch, "gh_api_list_paginated", fake_list)     assert (        gh_pr_watch.fetch_new_review_items(            sample_pr(),            state,            fresh_state=True,            authenticated_login="octocat",        )        == []    )    assert state["seen_review_comment_ids"] == []    assert state["seen_review_ids"] == []     review["state"] = "COMMENTED"    review["submitted_at"] = "2026-06-08T10:05:00Z"     published_items = gh_pr_watch.fetch_new_review_items(        sample_pr(),        state,        fresh_state=False,        authenticated_login="octocat",    )     assert {(item["kind"], item["id"]) for item in published_items} == {        ("review", "10"),        ("review_comment", "20"),    }    assert state["seen_review_comment_ids"] == ["20"]    assert state["seen_review_ids"] == ["10"]  def test_run_watch_keeps_polling_open_ready_to_merge_pr(monkeypatch):    sleeps = []    events = []    snapshot = {        "pr": sample_pr(),        "checks": sample_checks(),        "failed_runs": [],        "failed_jobs": [],        "new_review_items": [],        "actions": ["ready_to_merge"],        "retry_state": {            "current_sha_retries_used": 0,            "max_flaky_retries": 3,        },    }     monkeypatch.setattr(        gh_pr_watch,        "collect_snapshot",        lambda args: (snapshot, Path("/tmp/codex-babysit-pr-state.json")),    )    monkeypatch.setattr(        gh_pr_watch,        "print_event",        lambda event, payload: events.append((event, payload)),    )     class StopWatch(Exception):        pass     def fake_sleep(seconds):        sleeps.append(seconds)        if len(sleeps) >= 2:            raise StopWatch     monkeypatch.setattr(gh_pr_watch.time, "sleep", fake_sleep)     with pytest.raises(StopWatch):        gh_pr_watch.run_watch(argparse.Namespace(poll_seconds=30))     assert sleeps == [30, 30]    assert [event for event, _ in events] == ["snapshot", "snapshot"]  def test_failed_jobs_include_direct_logs_endpoint(monkeypatch):    jobs_by_run = {        99: [            {                "id": 555,                "name": "unit tests",                "status": "completed",                "conclusion": "failure",                "html_url": "https://github.com/openai/codex/actions/runs/99/job/555",            },            {                "id": 556,                "name": "lint",                "status": "completed",                "conclusion": "success",            },        ]    }     monkeypatch.setattr(        gh_pr_watch,        "get_jobs_for_run",        lambda repo, run_id: jobs_by_run[run_id],    )     failed_jobs = gh_pr_watch.failed_jobs_from_workflow_runs(        "openai/codex",        [            {                "id": 99,                "name": "CI",                "status": "in_progress",                "conclusion": "",                "head_sha": "abc123",            }        ],        "abc123",    )     assert failed_jobs == [        {            "run_id": 99,            "workflow_name": "CI",            "run_status": "in_progress",            "run_conclusion": "",            "job_id": 555,            "job_name": "unit tests",            "status": "completed",            "conclusion": "failure",            "html_url": "https://github.com/openai/codex/actions/runs/99/job/555",            "logs_endpoint": "repos/openai/codex/actions/jobs/555/logs",        }    ]