Skip to content

fix: avoid asyncio.get_event_loop() RuntimeError on Python >=3.12#7

Open
pollockjj wants to merge 29 commits into
developfrom
fix/event-loop-modern-python
Open

fix: avoid asyncio.get_event_loop() RuntimeError on Python >=3.12#7
pollockjj wants to merge 29 commits into
developfrom
fix/event-loop-modern-python

Conversation

@pollockjj

@pollockjj pollockjj commented Jun 6, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes the pre-existing develop CI breakage: every non-torch test job failed with
RuntimeError: There is no current event loop in thread 'MainThread'.

Root cause

AsyncRPC.__init__ eagerly called asyncio.get_event_loop(). On Python >=3.12
that raises when the RPC is constructed outside a running loop. The host launches
extensions from a synchronous path (host._launch_with_uds -> AsyncRPC(transport=...)),
so this is a real production crash risk on 3.12 — not only a test artifact.
tests/test_event_channel.py used the same deprecated
asyncio.get_event_loop().run_until_complete(...) pattern.

Fix

  • AsyncRPC.__init__: resolve the loop preserving get_event_loop() semantics without
    the >=3.12 RuntimeError — prefer the running loop, then the thread's installed
    loop (asyncio.set_event_loop before it is running), and only create+install a new
    loop as a last resort. Behavior is unchanged when a loop is running or installed; only
    the truly-no-loop construction case changes (was: crash on 3.12).
  • tests/test_event_channel.py: asyncio.get_event_loop().run_until_complete(...)asyncio.run(...).

Verification

  • Regression tests in tests/test_rpc_contract.py: construct-without-loop and
    reuse-installed-loop (both fail on develop, pass with the fix).
  • The 6 previously-failing tests pass. Full non-network suite: 516 passed, 0 failed.
  • ruff + mypy clean on changed files.

Scope

Three files: 1 production (pyisolate/_internal/rpc_protocol.py) + 2 tests
(tests/test_rpc_contract.py, tests/test_event_channel.py). No public API change;
no behavior change when a running or installed loop is present.

pollockjj added 28 commits April 7, 2026 19:21
…l absent

When pyisolate is installed from PyPI (non-editable), _pyisolate_source_path()
resolves to site-packages/ which has no pyproject.toml, causing pixi install
to fail. Now checks for pyproject.toml existence and falls back to a
version-pinned PyPI dependency matching the uv code path pattern.
Remove 3 non-production ][ DIAG: log statements from the RPC
deserialization path that printed on every type-tagged object crossing
the wire.
Add _internal/pixi_provisioner.py that downloads, caches, and
integrity-verifies the pixi binary from prefix-dev GitHub releases.
Supports Linux x86_64/aarch64, macOS x86_64/arm64, Windows x86_64.
Version pinned to 0.67.0. 11 tests covering platform detection,
checksum verification, cache hit, and full download path.
Replace shutil.which("pixi") checks in environment.py and
environment_conda.py with ensure_pixi() from pixi_provisioner.
The conda backend now auto-downloads, caches, and verifies the pixi
binary on first use. Users never need to install pixi manually.
…ilable

_deserialize_json_tensor previously returned raw dicts when torch was
absent. Now falls back to numpy arrays. Also registers TensorValue
deserializer in the child bootstrap for sealed workers without torch.
Add TestPlatformCoverage class with parametrized tests for all 5
platform combinations, Windows binary name (pixi.exe), and URL
construction verification against GitHub release naming convention.
Sealed workers never run adapter.register_serializers() because the
adapter can't be rehydrated. This caused ndarray results to be wrapped
as RemoteObjectHandle instead of crossing the wire as inline data.

Register ndarray as a data_type serializer directly in
SealedNodeExtension.__init__. The serializer converts ndarray to
TensorValue dict format, which the host's existing TensorValue
fast-path in rpc_transports.py reconstructs as torch.Tensor.
Add extra_index_urls field to ExtensionConfig and plumb through
install_dependencies() as --extra-index-url args to uv pip install.
Enables packages from non-standard PyPI indexes (e.g., fbxsdkpy from
gitlab.inria.fr) to resolve during isolated venv provisioning.
Copilot AI review requested due to automatic review settings June 6, 2026 11:10

Copilot AI 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.

Pull request overview

This PR updates PyIsolate’s async initialization and tests to avoid Python 3.12+ failures caused by removed implicit main-thread event loop creation.

Changes:

  • Adjusts AsyncRPC.__init__ event-loop acquisition to avoid asyncio.get_event_loop() raising when constructed outside a running loop.
  • Updates synchronous event-channel tests to use asyncio.run(...) instead of get_event_loop().run_until_complete(...).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
pyisolate/_internal/rpc_protocol.py Changes how AsyncRPC determines/creates its default event loop during construction.
tests/test_event_channel.py Replaces deprecated sync-loop driving with asyncio.run(...) in unit tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pyisolate/_internal/rpc_protocol.py Outdated
Comment on lines +171 to +175
try:
self.default_loop = asyncio.get_running_loop()
except RuntimeError:
self.default_loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.default_loop)
Comment on lines +78 to +79
asyncio.run(bridge.dispatch("progress", {"value": 1}))
asyncio.run(bridge.dispatch("preview", {"image": "data"}))
@pollockjj pollockjj force-pushed the fix/event-loop-modern-python branch from db6fb9b to 85ad675 Compare June 6, 2026 12:51
@pollockjj pollockjj requested a review from Copilot June 6, 2026 12:51
@qa-agent-seveneves

Copy link
Copy Markdown

Codex Review -- Round 1 -- KICKOFF

Runner: scripts/run_codex_review.py
PR: #7
Reviewer model: codex review (configured codex CLI default)
Kickoff timestamp (UTC): 2026-06-06T12:51:53Z

Prompt sent to codex review

Review the latest state of https://github.com/pollockjj/pyisolate/pull/7. Output findings using strict P0-P3 priority tags ([P0], [P1], [P2], [P3]) at the start of each finding title. Surface P0/P1/P2 -- these block merge in this repo. Surface P3 with that tag explicitly. Do not surface style nits, formatting, typos, or pre-existing bugs. End your output with a single line either 'APPROVE' (no P0/P1/P2 found) or 'REQUEST_CHANGES' (one or more P0/P1/P2 found). Output nothing else after that line.

Awaiting codex output. The result will be posted as a separate comment on this PR when codex exits.

@qa-agent-seveneves

Copy link
Copy Markdown

Codex Review -- Round 1 -- RESULT

PR: #7
Reviewer model: codex review (configured codex CLI default)
Result timestamp (UTC): 2026-06-06T12:53:39Z
Codex exit code: 0
Duration (s): 104.4
Detected verdict: REQUEST_CHANGES

Prompt sent to codex review

Review the latest state of https://github.com/pollockjj/pyisolate/pull/7. Output findings using strict P0-P3 priority tags ([P0], [P1], [P2], [P3]) at the start of each finding title. Surface P0/P1/P2 -- these block merge in this repo. Surface P3 with that tag explicitly. Do not surface style nits, formatting, typos, or pre-existing bugs. End your output with a single line either 'APPROVE' (no P0/P1/P2 found) or 'REQUEST_CHANGES' (one or more P0/P1/P2 found). Output nothing else after that line.

Verbatim codex review output

This PR was reviewed using codex review with the prompt above and produced the following output:


The patch fixes construction with no current event loop, but it introduces a regression for synchronous callers that pre-set a non-running event loop before constructing AsyncRPC.

Review comment:

  • [P2] Reuse the thread's pre-set event loop — /tmp/mydev-prr/pyisolate/_internal/rpc_protocol.py:174-175
    When AsyncRPC is constructed after synchronous setup has already installed a thread loop via asyncio.set_event_loop(loop) but before that loop is running, get_running_loop() raises and this path creates a separate loop instead. The previous get_event_loop() behavior used the installed loop, so callers that create/set a loop, construct AsyncRPC, then run that loop will now have rpc.run() and inbound dispatch scheduled on a loop that is never driven.

Codex runner stderr (non-evidence)

OpenAI Codex v0.130.0
--------
workdir: /tmp/mydev-prr
model: gpt-5.5
provider: openai
approval: never
sandbox: workspace-write [workdir, /tmp, /home/johnj/.codex/memories]
reasoning effort: high
reasoning summaries: none
session id: 019e9cfd-6f80-71b0-a4a1-2941cafd78c4
--------
user
Review the latest state of https://github.com/pollockjj/pyisolate/pull/7. Output findings using strict P0-P3 priority tags ([P0], [P1], [P2], [P3]) at the start of each finding title. Surface P0/P1/P2 -- these block merge in this repo. Surface P3 with that tag explicitly. Do not surface style nits, formatting, typos, or pre-existing bugs. End your output with a single line either 'APPROVE' (no P0/P1/P2 found) or 'REQUEST_CHANGES' (one or more P0/P1/P2 found). Output nothing else after that line.
exec
/bin/bash -lc 'cat /home/johnj/.codex/plugins/cache/openai-curated/github/edd96568/skills/github/SKILL.md' in /tmp/mydev-prr
 exited 1 in 0ms:
cat: /home/johnj/.codex/plugins/cache/openai-curated/github/edd96568/skills/github/SKILL.md: No such file or directory

exec
/bin/bash -lc 'ls /home/johnj/.codex/plugins/cache/openai-curated/github' in /tmp/mydev-prr
 succeeded in 0ms:
2cb26e7b

exec
/bin/bash -lc 'cat /home/johnj/.codex/plugins/cache/openai-curated/github/2cb26e7b/skills/github/SKILL.md' in /tmp/mydev-prr
 succeeded in 0ms:
---
name: github
description: Triage and orient GitHub repository, pull request, and issue work through the connected GitHub app. Use when the user asks for general GitHub help, wants PR or issue summaries, or needs repository context before choosing a more specific GitHub workflow.
---

# GitHub

## Overview

Use this skill as the umbrella entrypoint for general GitHub work in this plugin. It should decide whether the task stays in repo and PR triage or should be handed off to a more specific review, CI, or publish workflow.

This plugin is intentionally hybrid:

- Prefer the GitHub app from this plugin for repository, issue, pull request, comment, label, reaction, and PR creation workflows.
- Use local `git` and `gh` only when the connector does not cover the job well, especially for current-branch PR discovery, branch creation, commit and push, `gh auth status`, and GitHub Actions log inspection.
- Keep connector state and local checkout context aligned. If the request is about the current branch, resolve the local repo and branch before acting.

Once the intent is clear, route to the specialist skill immediately and do not keep broad GitHub triage in scope longer than needed.

## Connector-First Responsibilities

Handle these directly in this skill when the request does not need a narrower specialist workflow:

- repository orientation once the repo, PR, issue, or local checkout is identified
- recent PR or issue triage
- PR metadata summaries
- PR patch inspection
- PR comments, labels, and reactions
- issue lookup and summarization
- PR creation after a branch is already pushed

Prefer the GitHub app from this plugin for those flows because it provides structured PR, issue, and review-adjacent data without depending on a local checkout. If the repository is not already identifiable from the user request or local git context, ask for the repo instead of pretending there is a repo-search flow that may not exist.

## Routing Rules

1. Resolve the operating context first:
   - If the user provides a repository, PR number, issue number, or URL, use that.
   - If the request is about "this branch" or "the current PR", resolve local git context and use `gh` only as needed to discover the branch PR.
   - If the repository is still ambiguous after local inspection, ask for the repo identifier.
2. Classify the request before taking action:
   - `repo or PR triage`: summarize PRs, issues, patches, comments, labels, reactions, or repository state
   - `review follow-up`: unresolved review threads, requested changes, or inline review feedback
   - `CI debugging`: failing checks, Actions logs, or CI root-cause analysis
   - `publish changes`: create or switch branches, stage changes, commit, push, and open a draft PR
3. Route to the specialist skill as soon as the category is clear:
   - Review comments and requested changes: `../gh-address-comments/SKILL.md`
   - Failing GitHub Actions checks: `../gh-fix-ci/SKILL.md`
   - Commit, push, and open PR: `../yeet/SKILL.md`
4. Keep the hybrid model consistent after routing:
   - connector first for PR and issue data
   - local `git` and `gh` only for the specific gaps the connector does not cover

## Default Workflow

1. Resolve repository and item scope.
2. Gather structured PR or issue context through the GitHub app from this plugin.
3. Decide whether the task stays in connector-backed triage or needs a specialist skill.
4. Route immediately when the work becomes review follow-up, CI debugging, or publish workflow.
5. End with a clear summary of what was inspected, what changed, and what remains.

## Output Expectations

- For triage requests, return a concise summary of the repository, PR, or issue state and the next likely action.
- For mixed requests, tell the user which specialist path you are taking and why.
- For connector-backed write actions, restate the exact PR, issue, label, or reaction target before applying the change.
- Never imply that GitHub Actions logs are available through the connector alone. That remains a `gh` workflow.

## Examples

- "Use GitHub to summarize the open PRs in this repo and tell me what needs attention."
- "Help with this PR."
- "Review the latest comments on PR 482 and tell me what is actionable."
- "Debug the failing checks on this branch."
- "Commit these changes, push them, and open a draft PR."

mcp: codex_apps/github_fetch_pr started
mcp: codex_apps/github_fetch_pr (completed)
exec
/bin/bash -lc 'git status --short' in /tmp/mydev-prr
 succeeded in 216ms:
?? github_issues/100007/

exec
/bin/bash -lc 'git remote -v' in /tmp/mydev-prr
 succeeded in 0ms:
origin	git@github.com:pollockjj/mydevelopment.git (fetch)
origin	git@github.com:pollockjj/mydevelopment.git (push)

mcp: codex_apps/github_fetch_commit started
mcp: codex_apps/github_fetch_commit (completed)
mcp: github/get_file_contents started
mcp: github/get_file_contents (completed)
mcp: github/get_file_contents started
mcp: github/get_file_contents (completed)
mcp: github/get_file_contents started
mcp: github/get_file_contents (completed)
mcp: github/search_code started
mcp: github/search_code (completed)
mcp: github/search_code started
mcp: github/search_code (completed)
mcp: github/get_file_contents started
mcp: github/get_file_contents (completed)
mcp: github/get_file_contents started
mcp: github/get_file_contents (completed)
mcp: github/get_file_contents started
mcp: github/get_file_contents (completed)
mcp: github/get_file_contents started
mcp: github/get_file_contents (completed)
mcp: github/get_file_contents started
mcp: github/get_file_contents (completed)
mcp: github/get_file_contents started
mcp: github/get_file_contents (completed)
mcp: github/get_file_contents started
mcp: github/get_file_contents (completed)
codex
The patch fixes construction with no current event loop, but it introduces a regression for synchronous callers that pre-set a non-running event loop before constructing AsyncRPC.

Review comment:

- [P2] Reuse the thread's pre-set event loop — /tmp/mydev-prr/pyisolate/_internal/rpc_protocol.py:174-175
  When `AsyncRPC` is constructed after synchronous setup has already installed a thread loop via `asyncio.set_event_loop(loop)` but before that loop is running, `get_running_loop()` raises and this path creates a separate loop instead. The previous `get_event_loop()` behavior used the installed loop, so callers that create/set a loop, construct `AsyncRPC`, then run that loop will now have `rpc.run()` and inbound dispatch scheduled on a loop that is never driven.

Copilot AI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment thread pyisolate/_internal/rpc_protocol.py Outdated
Comment on lines +171 to +175
try:
self.default_loop = asyncio.get_running_loop()
except RuntimeError:
self.default_loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.default_loop)
@pollockjj pollockjj requested a review from Copilot June 6, 2026 12:57
@pollockjj pollockjj force-pushed the fix/event-loop-modern-python branch from 85ad675 to b495d9b Compare June 6, 2026 12:57
@qa-agent-seveneves

Copy link
Copy Markdown

Codex Review -- Round 2 -- KICKOFF

Runner: scripts/run_codex_review.py
PR: #7
Reviewer model: codex review (configured codex CLI default)
Kickoff timestamp (UTC): 2026-06-06T12:57:38Z

Prompt sent to codex review

Review the latest state of https://github.com/pollockjj/pyisolate/pull/7. Output findings using strict P0-P3 priority tags ([P0], [P1], [P2], [P3]) at the start of each finding title. Surface P0/P1/P2 -- these block merge in this repo. Surface P3 with that tag explicitly. Do not surface style nits, formatting, typos, or pre-existing bugs. End your output with a single line either 'APPROVE' (no P0/P1/P2 found) or 'REQUEST_CHANGES' (one or more P0/P1/P2 found). Output nothing else after that line.

Awaiting codex output. The result will be posted as a separate comment on this PR when codex exits.

@qa-agent-seveneves

Copy link
Copy Markdown

Codex Review -- Round 2 -- RESULT

PR: #7
Reviewer model: codex review (configured codex CLI default)
Result timestamp (UTC): 2026-06-06T12:59:15Z
Codex exit code: 0
Duration (s): 95.3
Detected verdict: UNKNOWN

Prompt sent to codex review

Review the latest state of https://github.com/pollockjj/pyisolate/pull/7. Output findings using strict P0-P3 priority tags ([P0], [P1], [P2], [P3]) at the start of each finding title. Surface P0/P1/P2 -- these block merge in this repo. Surface P3 with that tag explicitly. Do not surface style nits, formatting, typos, or pre-existing bugs. End your output with a single line either 'APPROVE' (no P0/P1/P2 found) or 'REQUEST_CHANGES' (one or more P0/P1/P2 found). Output nothing else after that line.

Verbatim codex review output

This PR was reviewed using codex review with the prompt above and produced the following output:


No merge-blocking issues or actionable P3 findings were identified in the latest PR state.


Codex runner stderr (non-evidence)

OpenAI Codex v0.130.0
--------
workdir: /tmp/mydev-prr
model: gpt-5.5
provider: openai
approval: never
sandbox: workspace-write [workdir, /tmp, /home/johnj/.codex/memories]
reasoning effort: high
reasoning summaries: none
session id: 019e9d02-b39f-7d72-aece-d641f4702b09
--------
user
Review the latest state of https://github.com/pollockjj/pyisolate/pull/7. Output findings using strict P0-P3 priority tags ([P0], [P1], [P2], [P3]) at the start of each finding title. Surface P0/P1/P2 -- these block merge in this repo. Surface P3 with that tag explicitly. Do not surface style nits, formatting, typos, or pre-existing bugs. End your output with a single line either 'APPROVE' (no P0/P1/P2 found) or 'REQUEST_CHANGES' (one or more P0/P1/P2 found). Output nothing else after that line.
exec
/bin/bash -lc pwd in /tmp/mydev-prr
 succeeded in 0ms:
/tmp/mydev-prr

exec
/bin/bash -lc ls in /tmp/mydev-prr
 succeeded in 0ms:
AGENTS.md
BCS043_HV GaN MMCI vs AccoTest KLM.xlsx
CLAUDE.md
GEMINI.md
MELIAN.md
Untitled.canvas
WHEELS_SELF_HOSTED_RUNNER_PASSDOWN.md
bcs_webform.html
claude
comfyui_manual_8188.log
debug
dev_blue.code-workspace
dev_yellow.code-workspace
equivalence_methods.md
github_issues
infineon_bcs
issue_87_2_p2_1_api.json
issue_87_2_p2_1_dup_api.json
mcp_servers.json
mymultigpu
myprocess_isolation
mywheels
orchestrator
orchestrator_requirements.md
persistent_btree.py
scripts
skills
tests
tools
tools_register.md
trellis2_final_workflow.json

exec
/bin/bash -lc 'cat /home/johnj/.codex/skills/pr-review/SKILL.md' in /tmp/mydev-prr
 succeeded in 0ms:
---
name: pr-review
description: "Iterative codex + Copilot PR review loop, slice-LLM driven. ACTIVATE when: (1) user says 'review the PR', 'kick off PR review', '/pr-review', 'disposition the PR'; (2) a PR has just been opened or had a new commit pushed to its head. Fires codex review (P0-P3 priority tags + verdict line), fires Copilot review via add-reviewer, READS the full Copilot inline-comment array (never trusts the review-body summary), decides every finding REQUIRED / NOT-REQUIRED / INVALID, applies REQUIRED fixes, posts INVALID rationale on the comment thread, then loops until codex P0-P2 list is empty AND no remaining findings about newly-changed code. Codex P0/P1/P2 findings are NEVER allowed to be classified INVALID."
---

# pr-review — codex + Copilot iterative review loop

**Role.** You own the PR. You decide every finding. You apply fixes. You reply to disagreements. The user does not adjudicate review findings — you do.

## Reviewers and what they post

| Reviewer | How fired | What it posts |
|---|---|---|
| **codex** | `python scripts/run_codex_review.py {OWNER/REPO} {PR_NUMBER} {ISSUE_NUMBER} {ROUND}` | Two PR thread comments per round (kickoff + result) as `qa-agent-seveneves[bot]`. Result body has findings tagged `[P0]`/`[P1]`/`[P2]`/`[P3]` and ends with one of: `APPROVE`, `REVIEW CONDUCTED AND NO ISSUES FOUND` (verdict-equivalent to APPROVE), or `REQUEST_CHANGES`. |
| **Copilot** | Fire: `gh pr edit {PR_NUMBER} --repo {OWNER/REPO} --add-reviewer copilot-pull-request-reviewer`. Monitor: `python scripts/run_copilot_poll.py {OWNER/REPO} {PR_NUMBER} {ISSUE_NUMBER} {ROUND} {HEAD_SHA}`. | One review (state COMMENTED) at the current head SHA, plus zero-or-more **inline comments** attached to specific file lines. The review-body summary is unreliable about inline counts. |

## The ONLY way to read Copilot's inline comments

`scripts/run_copilot_poll.py` is the runner. It polls until Copilot's review at the captured `HEAD_SHA` lands, then fetches the inline comments **scoped to that specific review** (via `/pulls/{pr}/reviews/{id}/comments` — review-scoped, not the PR-wide `/pulls/{pr}/comments` endpoint, so cycle N+1 only surfaces what THAT review added). It writes evidence to:

github_issues/{ISSUE_NUMBER}/pr_review_outputs/round{ROUND}_copilot_review.md
github_issues/{ISSUE_NUMBER}/pr_review_outputs/round{ROUND}_copilot_inline.json


and prints `REVIEW_ID`, `REVIEW_STATE`, `INLINE_COUNT`, `EVIDENCE_REVIEW`, `EVIDENCE_INLINE` lines on success.

**Always read `EVIDENCE_INLINE` after the runner exits 0.** Walk every entry: `id`, `path`, `line`, `body`, `in_reply_to_id`. Do NOT report "no actionable inline findings" based on `gh pr view --json latestReviews` body summary — that summary lies (it can say "generated 2 comments" while the body field is empty). The runner's inline-JSON file is the only authoritative source.

## Decision categories — every finding gets one

| Verdict | Meaning | Action |
|---|---|---|
| **REQUIRED** | Real defect or risk inside the PR's intended scope. | Apply the fix. Commit on the PR head branch. Push. |
| **NOT-REQUIRED** | Real, but optional / cosmetic / or genuinely outside the PR's intended scope. | Do nothing. On Copilot cycle #0, no reply needed. On cycle #1+, only worry if it concerns code touched in the most recent fix cycle. |
| **INVALID** | Reviewer is wrong: misunderstands the code, asserts a non-existent behavior, suggests a change that would break a stated invariant, or proposes a fix that defeats the test it claims to strengthen. | Post an inline reply on the comment thread with the technical rationale. Do not apply the change. |

**Hard rule:** A codex `[P0]` / `[P1]` / `[P2]` finding may NOT be classified INVALID. P0/P1/P2 are by definition merge-blocking — if you believe codex is wrong, the burden is on you to either (a) reclassify yourself privately and accept the risk by marking NOT-REQUIRED with explicit written rationale on the PR thread, or (b) apply a fix. You do not get to silently dismiss a codex P0/P1/P2.

Codex `[P3]` findings may be REQUIRED, NOT-REQUIRED, or INVALID.

## Posting an INVALID reply

gh api -X POST /repos/{OWNER/REPO}/pulls/{PR_NUMBER}/comments
-F in_reply_to={comment_id}
-f body="<REJECT rationale: name the false premise; cite the language/library semantics; cite the stated invariant>"


The reply must explain WHY the reviewer is wrong, not just that you disagree. If the reviewer is misunderstanding Python/git/HTTP/etc., cite the spec or stdlib doc. If they're proposing a change that would defeat the test's purpose, name the invariant the test guards.

## Cycle structure

A **cycle** = one round of (codex + Copilot) at the current head SHA, followed by your dispositioning of every finding, followed by any pushes you make.

- **Cycle #0**: the first time both reviewers run on this PR. Every finding warrants classification. NOT-REQUIRED Copilot findings get no reply on cycle #0.
- **Cycle #1+** (re-reviews after you pushed fixes): each Copilot re-review is a **fresh scan** of the entire PR — it will surface new findings, including ones about pre-existing code it didn't mention before. **You are only required to disposition cycle #1+ findings if they concern code that was changed in the most recent fix cycle.** Pre-existing-code findings Copilot just noticed may be ignored. (You did not introduce them in this round; they were tacit-NOT-REQUIRED on cycle #0.)

## The loop

cycle = 0
while True:
1. Capture current head SHA.
2. Fire codex review (round = cycle + 1):
python scripts/run_codex_review.py {OWNER/REPO} {PR} {ISSUE} {cycle+1}
Wait for the result comment to land.
3. Fire Copilot:
gh pr edit {PR} --repo {OWNER/REPO} --add-reviewer copilot-pull-request-reviewer
4. Monitor Copilot via the runner (blocks until review at HEAD lands):
python scripts/run_copilot_poll.py {OWNER/REPO} {PR} {ISSUE} {cycle+1} {HEAD_SHA}
The runner writes evidence files and prints REVIEW_ID / REVIEW_STATE /
INLINE_COUNT / EVIDENCE_REVIEW / EVIDENCE_INLINE on stdout.
5. Read codex result body. Parse [P0]/[P1]/[P2]/[P3] findings.
6. Read EVIDENCE_INLINE (the JSON file written by the Copilot runner).
Walk every entry. The PR-wide /pulls/{PR}/comments endpoint is
allowed as a cross-check but the runner output is canonical.
7. Disposition every finding REQUIRED / NOT-REQUIRED / INVALID per the rules above.
(Cycle #1+: skip dispositioning Copilot findings unrelated to most-recent-fix code.)
8. For each REQUIRED: apply fix, commit on PR head branch.
For each INVALID: post inline reply with rationale.
9. If you committed any fixes in step 8: push to PR head branch.
10. Termination check — break when ALL hold:
a. codex has no [P0]/[P1]/[P2] findings
b. codex has no actionable [P3] (any P3 not classified INVALID/NOT-REQUIRED)
c. on the LATEST Copilot review, no findings concern code changed in the most recent fix cycle
d. every cycle-#0 Copilot finding has been classified (REQUIRED-applied / NOT-REQUIRED-acknowledged / INVALID-replied)
Else: cycle += 1; loop.


## Forbidden

- Reporting "no actionable inline findings" without reading the runner's `EVIDENCE_INLINE` JSON and walking every entry. The `gh pr view --json latestReviews` body is not authoritative.
- Hand-rolling Copilot polling (e.g., `until ... gh api ... reviews ... | grep`) instead of using `scripts/run_copilot_poll.py`. The runner is the contract: it scopes inline comments to the specific review, writes durable evidence, and provides a stable summary the LLM and any downstream tooling can parse.
- Marking any codex `[P0]` / `[P1]` / `[P2]` as INVALID.
- Escalating individual Copilot findings to the user for their decision. The user does not adjudicate findings — that is your job. Showing the user a finding and asking what to do is offload.
- Stopping the loop while any unclassified codex finding or any unclassified Copilot inline comment from the latest cycle remains.
- Stopping the loop after pushing new code without firing a fresh codex + Copilot cycle on the new head SHA. New code = new review.
- Posting routine cycle status (kickoff fired, Copilot requested, fix pushed) to the user. Report only: terminal verdict, INVALID rationales sent, and decision counts per cycle.

## Reporting back

When the loop terminates, report:
- Terminal verdict (PR clean / blocked).
- Final head SHA.
- Per cycle: codex finding counts by priority, Copilot inline-comment counts, your decisions (REQUIRED-applied / NOT-REQUIRED / INVALID-with-reply).
- For every INVALID: the comment URL and one-sentence rationale.
- Mergeability state from `gh pr view --json mergeable,mergeStateStatus`.

That's it. The user takes the merge call.

mcp: codex_apps/github_fetch_pr started
mcp: codex_apps/github_fetch_pr (completed)
exec
/bin/bash -lc 'find . -maxdepth 2 -type d -name pyisolate' in /tmp/mydev-prr
 succeeded in 0ms:
mcp: codex_apps/github_fetch_file started
mcp: codex_apps/github_fetch_file (completed)
mcp: codex_apps/github_fetch_file started
mcp: codex_apps/github_fetch_file (completed)
mcp: codex_apps/github_fetch_file started
mcp: codex_apps/github_fetch_file (completed)
mcp: codex_apps/github_fetch_file started
mcp: codex_apps/github_fetch_file (completed)
mcp: codex_apps/github_get_pr_diff started
mcp: codex_apps/github_get_pr_diff (completed)
mcp: github/get_file_contents started
mcp: github/get_file_contents (completed)
mcp: github/search_code started
mcp: github/search_code (completed)
mcp: github/search_code started
mcp: github/search_code (completed)
mcp: github/list_commits started
mcp: github/list_commits (completed)
mcp: codex_apps/github_fetch_file started
mcp: codex_apps/github_fetch_file (completed)
mcp: codex_apps/github_fetch_file started
mcp: codex_apps/github_fetch_file (completed)
codex
No merge-blocking issues or actionable P3 findings were identified in the latest PR state.

Copilot AI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment on lines +165 to +171
# Acquire the loop without raising when constructed outside a running loop.
# Python >=3.10 deprecated and >=3.12 removed implicit main-thread event loop
# creation, so an eager asyncio.get_event_loop() raised here in sync construction
# paths. Preserve the historical get_event_loop() semantics: prefer the running
# loop, then the thread's installed loop (set via asyncio.set_event_loop, e.g. a
# synchronous caller that constructs the RPC before running its own loop), and only
# create+install a new loop as a last resort. update_event_loop() may replace it.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed — updated the PR description to state the accurate scope (three files: 1 production + 2 tests). Thanks.

AsyncRPC.__init__ eagerly called asyncio.get_event_loop(), which raises
RuntimeError("There is no current event loop") on Python >=3.12 when the RPC is
constructed outside a running loop. The host launches extensions from a synchronous
path (host._launch_with_uds -> AsyncRPC(transport=...)), so this is a real production
crash risk on 3.12, not only a test artifact; it broke every non-torch CI job on
develop ("There is no current event loop in thread 'MainThread'").

AsyncRPC.__init__ now resolves the loop preserving get_event_loop() semantics without
the >=3.12 RuntimeError: prefer the running loop, then the thread's installed loop (set
via asyncio.set_event_loop before it is running), and only create+install a new loop as
a last resort. Behavior is unchanged when a loop is running or installed; only the
truly-no-loop construction case changes (was: crash on 3.12).

- tests/test_rpc_contract.py: regression tests for construct-without-loop and
  reuse-installed-loop.
- tests/test_event_channel.py: asyncio.get_event_loop().run_until_complete(...) -> asyncio.run(...).
@pollockjj pollockjj force-pushed the fix/event-loop-modern-python branch from b495d9b to e572f2c Compare June 7, 2026 12:38
@pollockjj pollockjj requested a review from Copilot June 7, 2026 12:40
@qa-agent-seveneves

Copy link
Copy Markdown

Codex Review -- Round 3 -- KICKOFF

Runner: scripts/run_codex_review.py
PR: #7
Reviewer model: codex review (configured codex CLI default)
Kickoff timestamp (UTC): 2026-06-07T12:40:28Z

Prompt sent to codex review

Review the latest state of https://github.com/pollockjj/pyisolate/pull/7. Output findings using strict P0-P3 priority tags ([P0], [P1], [P2], [P3]) at the start of each finding title. Surface P0/P1/P2 -- these block merge in this repo. Surface P3 with that tag explicitly. Do not surface style nits, formatting, typos, or pre-existing bugs. End your output with a single line either 'APPROVE' (no P0/P1/P2 found) or 'REQUEST_CHANGES' (one or more P0/P1/P2 found). Output nothing else after that line.

Awaiting codex output. The result will be posted as a separate comment on this PR when codex exits.

@qa-agent-seveneves

Copy link
Copy Markdown

Codex Review -- Round 3 -- RESULT

PR: #7
Reviewer model: codex review (configured codex CLI default)
Result timestamp (UTC): 2026-06-07T12:42:05Z
Codex exit code: 0
Duration (s): 95.4
Detected verdict: UNKNOWN

Prompt sent to codex review

Review the latest state of https://github.com/pollockjj/pyisolate/pull/7. Output findings using strict P0-P3 priority tags ([P0], [P1], [P2], [P3]) at the start of each finding title. Surface P0/P1/P2 -- these block merge in this repo. Surface P3 with that tag explicitly. Do not surface style nits, formatting, typos, or pre-existing bugs. End your output with a single line either 'APPROVE' (no P0/P1/P2 found) or 'REQUEST_CHANGES' (one or more P0/P1/P2 found). Output nothing else after that line.

Verbatim codex review output

This PR was reviewed using codex review with the prompt above and produced the following output:


No merge-blocking defects or actionable P3 issues were identified in the latest PR state. The event-loop acquisition now preserves running and pre-set loop behavior while covering the no-current-loop construction case.


Codex runner stderr (non-evidence)

OpenAI Codex v0.130.0
--------
workdir: /tmp/mydev-prr
model: gpt-5.5
provider: openai
approval: never
sandbox: workspace-write [workdir, /tmp, /home/johnj/.codex/memories]
reasoning effort: high
reasoning summaries: none
session id: 019ea219-5a4e-7291-877e-377702c377a4
--------
user
Review the latest state of https://github.com/pollockjj/pyisolate/pull/7. Output findings using strict P0-P3 priority tags ([P0], [P1], [P2], [P3]) at the start of each finding title. Surface P0/P1/P2 -- these block merge in this repo. Surface P3 with that tag explicitly. Do not surface style nits, formatting, typos, or pre-existing bugs. End your output with a single line either 'APPROVE' (no P0/P1/P2 found) or 'REQUEST_CHANGES' (one or more P0/P1/P2 found). Output nothing else after that line.
exec
/bin/bash -lc pwd in /tmp/mydev-prr
 succeeded in 0ms:
/tmp/mydev-prr

exec
/bin/bash -lc ls in /tmp/mydev-prr
 succeeded in 0ms:
AGENTS.md
BCS043_HV GaN MMCI vs AccoTest KLM.xlsx
CLAUDE.md
GEMINI.md
MELIAN.md
Untitled.canvas
WHEELS_SELF_HOSTED_RUNNER_PASSDOWN.md
bcs_webform.html
claude
comfyui_manual_8188.log
debug
dev_blue.code-workspace
dev_yellow.code-workspace
equivalence_methods.md
github_issues
infineon_bcs
issue_87_2_p2_1_api.json
issue_87_2_p2_1_dup_api.json
mcp_servers.json
mymultigpu
myprocess_isolation
mywheels
orchestrator
orchestrator_requirements.md
persistent_btree.py
scripts
skills
tests
tools
tools_register.md
trellis2_final_workflow.json

exec
/bin/bash -lc 'find . -maxdepth 2 -type d -name pyisolate' in /tmp/mydev-prr
 succeeded in 0ms:
mcp: codex_apps/github_fetch_pr started
mcp: codex_apps/github_fetch_pr (completed)
mcp: codex_apps/github_fetch_file started
mcp: codex_apps/github_fetch_file (completed)
mcp: codex_apps/github_fetch_file started
mcp: codex_apps/github_fetch_file (completed)
mcp: github/search_code started
mcp: github/search_code (completed)
exec
/bin/bash -lc 'git clone --depth 1 --branch fix/event-loop-modern-python https://github.com/pollockjj/pyisolate.git pyisolate' in /tmp/mydev-prr
 exited 128 in 0ms:
Cloning into 'pyisolate'...
fatal: unable to access 'https://github.com/pollockjj/pyisolate.git/': Could not resolve host: github.com

mcp: github/search_code started
mcp: github/search_code (completed)
mcp: github/get_file_contents started
mcp: github/get_file_contents (completed)
mcp: github/get_file_contents started
mcp: github/get_file_contents (completed)
mcp: codex_apps/github_fetch_file started
mcp: codex_apps/github_fetch_file (completed)
mcp: github/get_file_contents started
mcp: github/get_file_contents (completed)
mcp: codex_apps/github_fetch_file started
mcp: codex_apps/github_fetch_file (completed)
mcp: codex_apps/github_fetch_file started
mcp: codex_apps/github_fetch_file (completed)
mcp: codex_apps/github_fetch_file started
mcp: codex_apps/github_fetch_file (completed)
codex
No merge-blocking defects or actionable P3 issues were identified in the latest PR state. The event-loop acquisition now preserves running and pre-set loop behavior while covering the no-current-loop construction case.

Copilot AI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

@pollockjj pollockjj force-pushed the develop branch 2 times, most recently from 0e15c5f to 2fda457 Compare June 7, 2026 13:25
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.

2 participants