Skip to content

OMN-9273: wire overseer-verify to gh pr checks against agent self-reports - #354

Merged
jonahgabriel merged 3 commits into
mainfrom
jonahgabriel/omn-9273-overseer-gh-checks
Apr 20, 2026
Merged

jonahgabriel merged 3 commits into
mainfrom
jonahgabriel/omn-9273-overseer-gh-checks

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Apr 20, 2026 •

Copy link
Copy Markdown
Contributor

OMN-9273: wire overseer-verify to gh pr checks.

[skip-deploy-gate: pure in-process verifier logic shelling out to gh pr checks via subprocess; no Dockerfile, runtime image, or topic wiring changes.]

Summary

  • Adds a new pr_checks_live check dimension to HandlerOverseerVerifier that shells out to gh pr checks for each PR an agent self-reports as green.
  • Previously the verifier ran five pure-Python checks and rubber-stamped PR completion claims without ever contacting GitHub — the feat(merge-sweep): add failure history tracking and Track A-resolve [OMN-7573] #1 leverage failure mode in overnight dispatch ("agents lie").
  • Follows canonical shell-out pattern from node_pr_snapshot_effect (30s timeout, per-PR error isolation, capture_output/text, isolated try/except for TimeoutExpired / OSError / JSONDecodeError).

Test plan

  • 6 new unit tests + 19 existing overseer tests pass
  • Full suite green locally (2225 passed)
  • ruff format/check clean
  • mypy --strict clean (878 files)

Related

Linear: OMN-9273

Summary by CodeRabbit

  • New Features

    • Added live GitHub PR checks verification with per-PR timeout, aggregated pass/fail results, skip when no claimed PRs, and a safety cap for too-many claimed PRs; introduced a claimed-PR input model and request field to submit PRs for validation.
  • Tests

    • Expanded tests for PR checks covering pass/fail paths, timeout, CLI errors, JSON/shape errors, non-string names, and the claimed-PR limit.

…orts

Adds a new `pr_checks_live` check dimension to HandlerOverseerVerifier that
shells out to `gh pr checks <num> --repo <owner>/<name> --json bucket,state,
conclusion,name,completedAt` for each PR an agent self-reports as green.
Previously the verifier ran five pure-Python checks and rubber-stamped PR
completion claims without ever contacting GitHub — the #1 leverage failure
mode in overnight dispatch ("agents lie").

Changes:
- ModelClaimedPr(pr_number, repo) model for (PR, repo) claim tuples
- ModelVerifierRequest gains optional `claimed_prs: list[ModelClaimedPr]`
- _check_pr_checks_live: per-PR subprocess shell-out with 30s timeout, per-PR
  error isolation, `bucket=="pass"` + conclusion whitelist gating; any red
  check surfaces PR + failing check name in the failure reason
- Follows canonical shell-out pattern from node_pr_snapshot_effect (subprocess
  .run, capture_output=True, text=True, isolated try/except for timeout / OSError
  / JSONDecodeError)
- `pr_checks_live` inserted at priority slot 4 (above allowed_action_scope):
  a broken PR is stronger evidence of agent lying than a schema quibble
- Skipped with a note when `claimed_prs` is empty, preserving zero-I/O
  semantics for non-PR verifications

Tests (6 new + 19 existing overseer tests pass):
- pr_checks_live skipped when no PRs claimed (subprocess never invoked)
- two green PRs → verdict PASS, pr_checks_live passed
- one green + one red → verdict FAIL, failing PR + check name in message
- subprocess timeout → verdict FAIL, descriptive timeout message
- gh non-zero exit → verdict FAIL with exit code + stderr
- malformed JSON → verdict FAIL with parse-error message
- existing consumer golden-chain test updated for 6-dim expectation

`uv run ruff format/check` clean, `uv run mypy src/ --strict` clean (878 files).
@github-actions
github-actions Bot enabled auto-merge April 20, 2026 00:57
@coderabbitai

coderabbitai Bot commented Apr 20, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2748bede-ffbd-4460-89ad-c5b73e94018b

📥 Commits

Reviewing files that changed from the base of the PR and between b60e94b and 8a49cca.

📒 Files selected for processing (2)
  • src/omnimarket/nodes/node_overseer_verifier/handlers/handler_overseer_verifier.py
  • tests/test_handler_overseer_verifier_pr_checks.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/omnimarket/nodes/node_overseer_verifier/handlers/handler_overseer_verifier.py
  • tests/test_handler_overseer_verifier_pr_checks.py

📝 Walkthrough

Walkthrough

Adds a sixth verification dimension, pr_checks_live, performing live gh pr checks validation per claimed PR, introduces ModelClaimedPr and claimed_prs on verifier requests, enforces a max-claimed-PRs cap, aggregates per-PR errors, and adds comprehensive unit tests and a golden update.

Changes

Cohort / File(s) Summary
PR Checks Live Verification Handler
src/omnimarket/nodes/node_overseer_verifier/handlers/handler_overseer_verifier.py
Inserted pr_checks_live into the verification pipeline, added live-check logic that invokes gh pr checks via subprocess.run with timeout, parses JSON, uses _check_row_passes() to evaluate rows, aggregates per-PR errors into a single failure message, enforces _MAX_CLAIMED_PRS, and adjusted failure-priority mapping.
PR Model Definition
src/omnimarket/nodes/node_overseer_verifier/models/model_claimed_pr.py
Added frozen Pydantic model ModelClaimedPr with pr_number: int (ge=1) and repo: str, exported via __all__.
Request Model Extension
src/omnimarket/nodes/node_overseer_verifier/models/model_verifier_request.py
Imported ModelClaimedPr and added claimed_prs: list[ModelClaimedPr] = Field(default_factory=list, ...) to ModelVerifierRequest for PR verification input.
Tests — Golden & Unit
tests/test_golden_chain_overseer_verifier_consumer.py, tests/test_handler_overseer_verifier_pr_checks.py
Updated golden-chain expectation to six checks. Added unit tests covering empty claimed_prs (skip), green-checks pass, failing checks, subprocess timeout, non-zero gh exit, malformed JSON/shape errors, non-string name robustness, and too-many-PRs cap behavior (no subprocess calls).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Handler
    participant Subprocess as "gh subprocess"
    participant GitHubAPI as "GitHub API"

    Client->>Handler: Submit ModelVerifierRequest (claimed_prs)
    Handler->>Handler: Validate claimed_prs count against _MAX_CLAIMED_PRS
    alt no claimed_prs
        Handler-->>Client: pr_checks_live = PASS (skipped)
    else has claimed_prs
        loop per claimed_pr
            Handler->>Subprocess: run `gh pr checks --repo <repo> <pr>` (--json fields, timeout)
            Subprocess->>GitHubAPI: request checks
            GitHubAPI-->>Subprocess: checks JSON
            Subprocess-->>Handler: stdout / exit code / stderr (or timeout)
            Handler->>Handler: parse JSON, call _check_row_passes() per row, collect failures
        end
        Handler-->>Client: Aggregate pr_checks_live result (PASS/FAIL) with message
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped through six checks where five once played,
Calling gh for truths the shadows made.
I tally green and red with careful cheer,
Timeouts and JSON I greet without fear.
A tiny rabbit verifier—hopping clear!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically identifies the main change: adding a new pr_checks_live verification dimension that invokes GitHub CLI to validate pull requests.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jonahgabriel/omn-9273-overseer-gh-checks

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Apr 20, 2026 •

Copy link
Copy Markdown

⚠️ Hostile Reviewer — DEGRADED (informational)

Blocking findings (critical/error): 0
Total findings: 0
Models succeeded: none

Note: All reviewer models failed or were unavailable. Degraded results are informational during the pilot phase (OMN-8524) and do not block merge. Error: cli_review exited non-zero (all models failed or binary error)


Gate semantics (pilot phase)

Verdict Meaning Blocks merge?
passed No critical/error findings No
blocked CRITICAL or ERROR findings found Yes
degraded All models unavailable (infra) No (pilot)

Powered by omniintelligence.review_pairing.cli_review — node-based adversarial review via HandlerLlmCliSubprocess (OMN-8524)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/test_handler_overseer_verifier_pr_checks.py (1)

231-247: Add a regression for valid-but-wrong JSON shape.

Please add a case where stdout is valid JSON but not a list[dict] (e.g. {} or ["x"]) so per-PR isolation stays protected against schema drift from CLI output.

🧪 Suggested additional test
+@pytest.mark.unit
+def test_pr_checks_live_fails_on_unexpected_json_shape() -> None:
+    """Valid JSON with wrong shape should fail gracefully, not raise."""
+    handler = HandlerOverseerVerifier()
+    claimed = [ModelClaimedPr(pr_number=1, repo="OmniNode-ai/omnimarket")]
+
+    with patch(
+        "omnimarket.nodes.node_overseer_verifier.handlers."
+        "handler_overseer_verifier.subprocess.run",
+        return_value=_FakeCompleted(stdout='{"not":"a-list"}', returncode=0),
+    ):
+        result = handler.verify(_make_request(claimed_prs=claimed))
+
+    assert result["verdict"] == "FAIL"
+    checks = {c["name"]: c for c in result["checks"]}  # type: ignore[union-attr]
+    assert checks["pr_checks_live"]["passed"] is False
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_handler_overseer_verifier_pr_checks.py` around lines 231 - 247,
Add a new unit test alongside test_pr_checks_live_fails_on_malformed_json that
exercises HandlerOverseerVerifier.verify with subprocess.run returning stdout
that is valid JSON but of the wrong shape (e.g., "{}" or '["x"]') to ensure
pr_checks_live still fails and the pr_checks_live check message indicates a
schema/format problem; reference the existing test name
test_pr_checks_live_fails_on_malformed_json, the HandlerOverseerVerifier class
and its verify method, and assert the resulting result["verdict"] == "FAIL" and
that checks["pr_checks_live"]["passed"] is False and its message mentions
schema/format or unexpected JSON shape.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/omnimarket/nodes/node_overseer_verifier/handlers/handler_overseer_verifier.py`:
- Around line 449-456: After parsing JSON into raw, validate its shape before
iterating: ensure raw is a list and each element is a dict (e.g.,
isinstance(raw, list) and all(isinstance(item, dict) for item in raw)); if the
check fails, return a clear error like "{claim.repo}#{claim.pr_number}: invalid
JSON payload shape, expected list of objects" instead of proceeding to build
red_checks or calling _check_row_passes, so you avoid AttributeError when items
are strings or other types.

---

Nitpick comments:
In `@tests/test_handler_overseer_verifier_pr_checks.py`:
- Around line 231-247: Add a new unit test alongside
test_pr_checks_live_fails_on_malformed_json that exercises
HandlerOverseerVerifier.verify with subprocess.run returning stdout that is
valid JSON but of the wrong shape (e.g., "{}" or '["x"]') to ensure
pr_checks_live still fails and the pr_checks_live check message indicates a
schema/format problem; reference the existing test name
test_pr_checks_live_fails_on_malformed_json, the HandlerOverseerVerifier class
and its verify method, and assert the resulting result["verdict"] == "FAIL" and
that checks["pr_checks_live"]["passed"] is False and its message mentions
schema/format or unexpected JSON shape.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 45df5e16-aa00-4566-bf4e-b6989188f45a

📥 Commits

Reviewing files that changed from the base of the PR and between d869f38 and c551c10.

📒 Files selected for processing (5)
  • src/omnimarket/nodes/node_overseer_verifier/handlers/handler_overseer_verifier.py
  • src/omnimarket/nodes/node_overseer_verifier/models/model_claimed_pr.py
  • src/omnimarket/nodes/node_overseer_verifier/models/model_verifier_request.py
  • tests/test_golden_chain_overseer_verifier_consumer.py
  • tests/test_handler_overseer_verifier_pr_checks.py

Two mechanical hardening fixes raised by CodeRabbit:

1. **Fan-out cap** — Add `_MAX_CLAIMED_PRS = 20` constant. `_check_pr_checks_live`
   fails fast with a descriptive message when `claimed_prs` exceeds the cap,
   preventing pathological N-PR invocations from stalling verification up to
   N×30s. 20 keeps the worst-case at ~10min, well above any realistic
   agent self-report size.

2. **JSON shape guard** — Previously `raw: list[dict[str, Any]] = json.loads(...)`
   trusted gh's output shape. A valid non-list JSON (e.g. top-level object) would
   iterate over dict keys (strings) and `AttributeError` on `.get()`, breaking
   per-PR error isolation and crashing the verifier. Now explicitly assert
   top-level list + per-item dict, returning a `JSON shape error` message
   matching the existing parse-error envelope shape.

Tests added (3):
- too many claimed PRs → fail fast, subprocess.run never invoked
- non-list JSON (top-level object) → JSON shape error, no crash
- list with non-dict items → JSON shape error, no crash

Full suite: 2228 passed (3 new, 2225 prior). ruff + mypy --strict clean.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/omnimarket/nodes/node_overseer_verifier/handlers/handler_overseer_verifier.py`:
- Around line 481-488: red_checks is built from untyped JSON and may contain
non-string values (e.g., None or ints), which will cause TypeError when
sorted/joined; before collecting names for failing rows in the red_checks list
comprehension (the expression using raw and _check_row_passes), coerce each
item's "name" to a string (e.g., via str(item.get("name", "<unnamed>")) or
conditional fallback) so sorted(set(red_checks)) and ', '.join(...) always
operate on strings; keep the same message construction that uses claim.repo and
claim.pr_number.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7cb04301-78ff-4a54-b903-bc39cf1f664b

📥 Commits

Reviewing files that changed from the base of the PR and between c551c10 and b60e94b.

📒 Files selected for processing (2)
  • src/omnimarket/nodes/node_overseer_verifier/handlers/handler_overseer_verifier.py
  • tests/test_handler_overseer_verifier_pr_checks.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_handler_overseer_verifier_pr_checks.py

CodeRabbit flagged that red_checks sourced from untyped JSON could contain
None or int values for `name`, causing TypeError when sort/join runs. Coerce
via `str(item.get('name') or '<unnamed>')`. New test covers None + int + str
mixed rows, asserts sorted/joined output contains each coerced value.
@jonahgabriel
jonahgabriel added this pull request to the merge queue Apr 20, 2026
Merged via the queue into main with commit 0d6561e Apr 20, 2026
31 of 32 checks passed
@jonahgabriel
jonahgabriel deleted the jonahgabriel/omn-9273-overseer-gh-checks branch April 20, 2026 05:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant