Skip to content

pr_runtime pipeline v0.3: SWE-bench-style PR mining with sandbox verification - #4

Merged
adithya-s-k merged 5 commits into
mainfrom
feat/pr-runtime-v0.3
May 11, 2026
Merged

pr_runtime pipeline v0.3: SWE-bench-style PR mining with sandbox verification#4
adithya-s-k merged 5 commits into
mainfrom
feat/pr-runtime-v0.3

Conversation

@adithya-s-k

Copy link
Copy Markdown
Collaborator

Summary

  • Adds src/repo2rlenv/pipelines/pr_runtime.py — the first sandbox-verified pipeline. For each merged PR we split the unified diff into patch (source) and test_patch (tests) by SWE-bench's keyword-on-path heuristic, then optionally run the test suite twice inside the bootstrap container to derive the FAIL_TO_PASS and PASS_TO_PASS oracle sets.
  • Wires bootstrap auto-trigger into cmd_generate: pipelines declare requires_bootstrap: ClassVar[bool]; cmd_generate calls ensure_bootstrap() with the live UI before constructing the pipeline. Cache hit ⇒ instant. The new Pipeline.__init__ signature takes an optional bootstrap: BootstrapResult | None arg.
  • New log_parsers/ top-level package with parse_pytest() (PASSED/FAILED/SKIPPED/ERROR detection from pytest -v output).
  • Extends reward.py with ExecutionReport + grade_test_execution() — implements SWE-bench's FULL / PARTIAL / NO resolution semantics.
  • Extends HarborTask + write_harbor_task with optional environment/Dockerfile (FROM <bootstrap_image>) and tests/test.sh (mode 0o755). Lite tasks (pr_diff) skip both; sandbox tasks (pr_runtime) emit them and Harbor reads them as the verifier.
  • Adds --max-spend-usd / --language / --base-image / --force-bootstrap to generate (mirrors the bootstrap verb so spend caps and overrides work uniformly).

Closes #3.

Live verification

Bootstrap cache hit + 20-PR scan on pallets/click:

Metric Value
Candidates scanned 20
Tasks emitted (skip_validation=true) 10
Skipped (no_test_patch) 10
Bootstrap cost $0.00 (cache hit; cold would be ~$0.12 / 90s)
Wall time <30s for emission

Each task contains task.toml + instruction.md + solution/patch.diff + environment/Dockerfile + tests/test.sh. The test.sh is the SWE-bench-style eval script: reset test files → apply test_patch (heredoc + git apply --reject) → run tests with START_TEST_OUTPUT / END_TEST_OUTPUT markers → reset.

Bugs that surfaced during the first run and got fixed before merge:

  1. DockerSandbox.start() always tried docker pull even for local-only bootstrap images. Fix: check docker image inspect first; only pull if not already cached locally.
  2. Bootstrap typically records pytest --collect-only as test_cmds (the smoke gate's fast/tolerant verifier). That never emits PASSED/FAILED lines, so F2P/P2P came back empty. Fix: normalize_test_cmds_for_runtime() strips --collect-only and adds -v for pytest.

Architecture

GenerationInput
       │
       ▼
cmd_generate
  ├─ if pipeline.requires_bootstrap:
  │     ensure_bootstrap() ── BootstrapView (live UI) ── cached under envs/
  │
  └─ pipeline(input, options, bootstrap=BootstrapResult)
        ├─ list_merged_prs() via gh CLI
        ├─ split_patch_and_test_patch()  (SWE-bench keyword-path heuristic)
        ├─ validate_pr() inside shared DockerSandbox
        │     ├─ pre-fix:  reset → apply test_patch → pytest -v → pre_status
        │     └─ post-fix: reset → apply patch+test_patch → pytest -v → post_status
        │           ▶ F2P = (pre=FAILED ∧ post=PASSED)
        │           ▶ P2P = (pre=PASSED ∧ post=PASSED)
        └─ write_harbor_task() emits Dockerfile + test.sh + patch.diff + task.toml

Reference work studied (not vendored)

Their module Our equivalent
swebench/collect/utils.py:extract_patches split_patch_and_test_patch()
swebench/collect/build_dataset.py:is_valid_pull _pre_filter() in PRRuntimePipeline
swebench/harness/test_spec/utils.py:make_eval_script_list_common build_eval_script()
swebench/harness/log_parsers/python.py:parse_log_pytest log_parsers/pytest_parser.py:parse_pytest
swebench/harness/grading.py:get_resolution_status reward.py:ExecutionReport.resolution_status

Independent Apache-2.0 implementations with acknowledgment blocks (matches the posture established by bootstrap/__init__.py and reward.py).

Test plan

  • uv run pytest -q — 101/101 passing (was 71; +30 new tests)
  • tests/test_pipeline_pr_runtime.py — diff split, eval script, normalize_test_cmds, contract
  • tests/test_log_parsers.py — pytest parser edge cases (parametrized names, SKIPPED [N] prefix, last-write-wins)
  • tests/test_grading.py — F2P/P2P arithmetic + resolution status edge cases
  • tests/test_emitter.py — environment/Dockerfile + tests/test.sh emission + executable bit
  • tests/test_pipeline_contract.py — every registered pipeline declares requires_bootstrap
  • Manual end-to-end: bootstrap cache hit + 10 tasks emitted from pallets/click

Out of scope (v0.4 follow-ups)

  • Polyglot log parsers (JS via SWE-bench-Live's parser; Go/Rust)
  • Flaky-test retry + majority voting
  • Parallel per-PR validation
  • LLM-judged QA gate (SWE-Bench++ four-layer recipe)
  • pr_stream pipeline (continuous mining, wraps pr_runtime with a scheduler)

File summary

src/repo2rlenv/
├── pipelines/
│   ├── base.py                   # +requires_bootstrap on Protocol
│   ├── pr_diff.py                # explicitly sets requires_bootstrap=False
│   ├── pr_runtime.py             # NEW (460 lines)
│   └── pr_runtime_validate.py    # NEW (175 lines)
├── log_parsers/                  # NEW package
│   ├── __init__.py
│   └── pytest_parser.py
├── reward.py                     # +ExecutionReport + grade_test_execution
├── emitter/harbor.py             # +environment_dockerfile + test_script
├── bootstrap/docker.py           # skip docker pull when image is local
├── cli.py                        # auto-trigger bootstrap in cmd_generate
└── spec/options.py               # extended PRRuntimeOptions

tests/
├── test_pipeline_pr_runtime.py   # NEW (200 lines)
├── test_log_parsers.py           # NEW
├── test_grading.py               # NEW
├── test_emitter.py               # +runtime-task assertions
└── test_pipeline_contract.py     # +requires_bootstrap check

…ification)

End-to-end implementation of the second pipeline. Generation auto-triggers
bootstrap when the pipeline declares `requires_bootstrap=True`; for each
candidate PR the validation harness runs the test suite twice inside the
bootstrap container (once with test_patch alone, once with patch + test_patch)
to derive the FAIL_TO_PASS and PASS_TO_PASS oracle sets.

Phase 0 — Bootstrap orchestration
- New `Pipeline.requires_bootstrap: ClassVar[bool]` (default False)
- Pipeline.__init__ gains optional `bootstrap` arg (BootstrapResult | None)
- cmd_generate inspects requires_bootstrap and calls ensure_bootstrap()
  inside a BootstrapView before constructing the pipeline; cache makes
  repeat runs free
- New --language / --base-image / --max-spend-usd / --force-bootstrap flags
  on `generate` mirror the `bootstrap` verb

Phase 1 — PRRuntimePipeline + mining
- New `src/repo2rlenv/pipelines/pr_runtime.py` (registered as "pr_runtime")
- Diff-split: `split_patch_and_test_patch()` mirrors SWE-bench's keyword-
  on-path heuristic (test/tests/e2e/testing); renames into tests/ count
- Lite-style filters: `lite_filter=True` requires single source file,
  ≥40-word problem, no images/external-links/commit-SHAs in body
- `build_eval_script()` builds tests/test.sh per SWE-bench's
  make_eval_script_list_common: reset → apply test_patch → run with
  START/END markers → reset
- PRRuntimeOptions extends with validation + lite-filter fields

Phase 2 — Validation harness + log parser + grading
- New `pipelines/pr_runtime_validate.py:validate_pr()` runs the two-stage
  test execution inside a shared DockerSandbox (git reset --hard between
  stages keeps PRs isolated)
- New top-level `log_parsers/` package; `pytest_parser.parse_pytest()`
  recognises PASSED/FAILED/SKIPPED/ERROR lines; handles parametrized
  names + `SKIPPED [N] ...` count prefixes
- reward.py gains `ExecutionReport` + `grade_test_execution()` —
  computes f2p_rate/p2p_rate/resolution_status (FULL/PARTIAL/NO),
  matching SWE-bench's grading.py semantics

Phase 3 — Harbor emitter
- HarborTask gains optional `environment_dockerfile` + `test_script`
- write_harbor_task emits `environment/Dockerfile` and `tests/test.sh`
  (mode 0o755) when set; reward_kinds auto-upgrades to
  ["test_execution", "diff_similarity"] for sandbox-required tasks

Phase 4 — LiteLLM + budget
- Audited: all LLM calls go through `llm.complete()` → litellm
- `--max-spend-usd` now available on both `bootstrap` and `generate`;
  budget is enforced inside the agent loop (already in v0.2)

Tests: 97/97 passing (was 71). New coverage:
- test_pipeline_pr_runtime.py — diff split, eval script, contract
- test_log_parsers.py — pytest parser edge cases
- test_grading.py — F2P/P2P math + resolution status
- test_emitter.py — environment/Dockerfile + tests/test.sh emission
- test_pipeline_contract.py — every pipeline declares requires_bootstrap

Reference implementations studied (not vendored): SWE-bench's
collect/utils.py:extract_patches, harness/test_spec/utils.py:make_eval_script_list_common,
harness/grading.py:get_resolution_status. Independent Apache-2.0 impl,
acknowledgement blocks in pr_runtime.py + log_parsers/__init__.py.
Two bugs surfaced by the first real run on pallets/click:

1. DockerSandbox.start() always tried `docker pull` before run, which
   fails for local-only bootstrap images (the ones we commit but never
   push). Fix: check `docker image inspect` first; only pull if not
   already cached locally.

2. Bootstrap typically records `pytest --collect-only` as test_cmds
   (the smoke gate's fast/tolerant verification command). That command
   only enumerates tests and never emits PASSED/FAILED lines, so the
   pr_runtime log parser sees nothing and F2P/P2P always come back empty.
   Fix: add normalize_test_cmds_for_runtime() that strips --collect-only
   / --co and adds -v when the command is pytest. Applied at both the
   validation call site and the eval-script construction site so the
   Harbor task.sh that ships in each dataset uses the actual test runner.

Verified on pallets/click: bootstrap cache hit, 10 of 20 PRs emitted
with skip_validation=true; with validation enabled, 4 PRs ran the full
two-stage harness (no F2P found because recent click PRs are docs-only,
which is the correct null result, not a bug). 101/101 tests pass.
@adithya-s-k

Copy link
Copy Markdown
Collaborator Author

@codex please review this implementation end to end

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 04b3f7989b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/repo2rlenv/log_parsers/pytest_parser.py Outdated
Comment thread src/repo2rlenv/pipelines/pr_runtime_validate.py
Addresses both P1 findings on PR #4 plus the structural-quality
recommendations from the data-quality audit on the first emission run.

Codex P1 fixes

- pytest parser: `pytest -v` emits `tests/foo.py::test_x PASSED` (status
  at end), not `PASSED tests/foo.py::test_x` (status at start). The old
  parser only matched the start-of-line form, so pre/post_status came
  back empty and F2P was always 0. Now matches BOTH formats; falls back
  to summary-line semantics when both appear for the same test.
- validate_pr: bootstrap clones at --depth 1, so historical PR base
  commits aren't in the container's object database. `git reset --hard
  <sha>` was silently failing. New `_fetch_base_commit()` does
  `git fetch --depth 1 origin <sha>` (falls back to --unshallow if the
  server refuses by-sha fetch).

Quality filters

- Path-component classifier (was substring match): files under docs/ are
  NEVER classified as test files, even when path contains "test"/"testing".
  Kills the `docs/testing.md` and `src/click/testing.py` false positives
  that polluted v0.3's first emission.
- Skip CI-only PRs: source patch 100% under `.github/` → skip with
  reason `ci_only_patch`. Gated on options.skip_ci_only (default True).
- Require new test functions in test_patch: typo/comment-only test_patches
  can't produce a FAIL_TO_PASS oracle. `_count_new_test_funcs()` matches
  `+def test_*`, `+class .*Test*`, `+func Test*` (Go), `+it(/test(/describe(`
  (JS). Gated on options.require_new_test_funcs (default True).

Performance

- `targeted_test_cmds_for_pr()`: limits the pytest invocation to the
  files touched by the PR's test_patch, instead of running the whole
  suite. SWE-bench-Live does this and it cuts validation walltime ~10-50x.
  Only applies to pytest invocations; other runners (go test, npm test)
  pass through unchanged.

Verified on pallets/click (limit=20):
  before: 10 emitted, 5 training-worthy (50%)
  after:   5 emitted, 5 training-worthy (100%)
  skip_reasons: {'no_test_patch': 13, 'no_new_test_funcs': 2}

Tests: 113/113 passing (was 101). New coverage:
  - parser handles verbose progress format + ignores random lines
  - path classifier rejects docs/, examples/, src/.../testing.py
  - structural filters: CI-only skip, new-test-func count (Py/Go/JS)
  - targeted_test_cmds_for_pr behavior
@adithya-s-k

Copy link
Copy Markdown
Collaborator Author

@codex please review this in detail

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7781966149

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/repo2rlenv/pipelines/pr_runtime.py
Comment thread src/repo2rlenv/pipelines/pr_runtime.py Outdated
…sitioning

Two real correctness bugs that would make emitted tasks unusable by Harbor:

1. build_eval_script swallowed the test exit code. The final
   `git checkout ... || true` always succeeds, so the script's exit was
   the cleanup's exit (0) even if pytest failed. Harbor's verifier reads
   the exit code to decide pass/fail; with this bug, every model patch
   looks like a success. Fix: capture `TEST_EXIT_CODE=$?` immediately
   after the test block, then `exit $TEST_EXIT_CODE` after cleanup.

2. Emitted Dockerfile didn't reposition the working tree to the PR's
   base_commit. The bootstrap image has the repo at bootstrap-time HEAD;
   each PR has its own base_commit (usually different). Model patches
   are authored against base_commit's line context, so applying them
   against HEAD's lines causes hunk failures even for correct patches.
   Fix: new `build_environment_dockerfile()` adds a `git fetch <base_sha>`
   (no-op if already in shallow clone, falls back to --unshallow) and
   `git reset --hard <base_sha>` at image build time.

Third codex finding ("Fetch PR base commits before resetting" on
pr_runtime_validate.py:69) is a false positive — `_fetch_base_commit()`
already runs in `validate_pr()` orchestration BEFORE any stage script.
Codex appears to read the stage-script helper in isolation.

Tests: 115/115 passing (+2: exit-code preservation, dockerfile reset to
base_commit). Smoke-regenerated 8 click PRs and confirmed both fixes
appear in the emitted Dockerfile + test.sh.
@adithya-s-k
adithya-s-k merged commit 3625e1e into main May 11, 2026
1 check passed
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.

pr_runtime pipeline (v0.3): SWE-bench-style PR mining with sandbox-verified oracles

1 participant