-
Notifications
You must be signed in to change notification settings - Fork 93
Add SWE-bench Pro eval preflight and harness docs #112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Abhishek21g
wants to merge
3
commits into
scaleapi:main
Choose a base branch
from
Abhishek21g:docs/swebench-preflight
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+476
−0
Open
Changes from 1 commit
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| # Golden patch verification | ||
|
|
||
| Issue [#101](https://github.com/scaleapi/SWE-bench_Pro-os/issues/101): running evaluation with | ||
| dataset golden patches can fail on some instances (e.g. NodeBB) with: | ||
|
|
||
| ```text | ||
| [emailer.send] Error: [[error:sendmail-not-found]] | ||
| ``` | ||
|
|
||
| The tests in the log may still pass — the error is logged during email setup, not necessarily | ||
| as a test failure. Always inspect `test_output.txt` / parser output, not only stderr noise. | ||
|
|
||
| ## Recommended workflow | ||
|
|
||
| ### 1. Extract gold patches | ||
|
|
||
| ```bash | ||
| python helper_code/extract_gold_patches.py --output gold_patches.json | ||
| ``` | ||
|
|
||
| ### 2. Preflight | ||
|
|
||
| ```bash | ||
| python scripts/swebench_preflight.py \ | ||
| --patches gold_patches.json \ | ||
| --scripts-dir run_scripts | ||
| ``` | ||
|
|
||
| NodeBB instances will emit a **WARN** about sendmail — expected for #101-class images. | ||
|
|
||
| ### 3. Evaluate one instance (local Docker) | ||
|
|
||
| ```bash | ||
| python swe_bench_pro_eval.py \ | ||
| --raw_sample_path=<csv_with_one_row> \ | ||
| --patch_path=gold_patches.json \ | ||
| --output_dir=out/golden-verify/ \ | ||
| --scripts_dir=run_scripts \ | ||
| --use_local_docker \ | ||
| --num_workers=1 | ||
| ``` | ||
|
|
||
| ### 4. Single-instance smoke (example) | ||
|
|
||
| Instance from #101: | ||
|
|
||
| `instance_NodeBB__NodeBB-04998908ba6721d64eba79ae3b65a351dcfbc5b5-vnan` | ||
|
|
||
| Filter `gold_patches.json` and dataset CSV to this `instance_id` before running step 3. | ||
|
|
||
| ## Interpreting results | ||
|
|
||
| | Signal | Meaning | | ||
| |--------|---------| | ||
| | Parser reports fail-to-pass tests passed | Golden patch valid for grading | | ||
| | sendmail error in logs only | Environment noise (#101) — check parser | | ||
| | `git apply` failure | Patch/instance mismatch — fix harness before trusting scores | | ||
| | Missing `run_scripts/` files | Run preflight — do not publish scores | | ||
|
|
||
| ## Related issues | ||
|
|
||
| - [#93](https://github.com/scaleapi/SWE-bench_Pro-os/issues/93) — git history reward hacking | ||
| - [#108](https://github.com/scaleapi/SWE-bench_Pro-os/issues/108) — determinacy audit |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| # SWE-bench Pro harness | ||
|
|
||
| This document describes the grading harness invariants for SWE-bench Pro OSS evals. | ||
| Run `python scripts/swebench_preflight.py` before `swe_bench_pro_eval.py` to catch | ||
| layout and schema issues early. | ||
|
|
||
| ## Pipeline overview | ||
|
|
||
| 1. **Generate patches** — agent harness (SWE-agent, mini-swe-agent, etc.) writes `.pred` files | ||
| 2. **Gather patches** — `helper_code/gather_patches.py` → JSON array | ||
| 3. **Preflight** — `scripts/swebench_preflight.py` validates JSON + `run_scripts/` | ||
| 4. **Evaluate** — `swe_bench_pro_eval.py` applies patches in Docker/Modal and runs tests | ||
|
|
||
| ## Patch JSON format | ||
|
|
||
| ```json | ||
| [ | ||
| { | ||
| "instance_id": "instance_org__repo-<hash>", | ||
| "patch": "diff --git a/...", | ||
| "prefix": "gold" | ||
| } | ||
| ] | ||
| ``` | ||
|
|
||
| Required keys: `instance_id`, `patch`, `prefix`. Duplicate `instance_id` values are rejected by preflight. | ||
|
|
||
| ## Per-instance run scripts | ||
|
|
||
| For each `instance_id`, the repo ships: | ||
|
|
||
| - `run_scripts/<instance_id>/run_script.sh` — applies patch and runs tests | ||
| - `run_scripts/<instance_id>/parser.py` — parses stdout into pass/fail | ||
|
|
||
| Preflight fails if either file is missing for a patch entry. | ||
|
|
||
| ## Known harness footguns | ||
|
|
||
| | Issue | Symptom | Mitigation | | ||
| |-------|---------|------------| | ||
| | [#101](https://github.com/scaleapi/SWE-bench_Pro-os/issues/101) golden patch / sendmail | `[[error:sendmail-not-found]]` in NodeBB email tests | See [GOLDEN_PATCH.md](GOLDEN_PATCH.md) | | ||
| | [#93](https://github.com/scaleapi/SWE-bench_Pro-os/issues/93) git history leakage | Agent can `git show` future fix commits | Strip future history in Docker images before eval | | ||
| | [#6](https://github.com/scaleapi/SWE-bench_Pro-os/issues/6) bash entrypoint | Manual `bash` breaks eval | Use image entrypoint as documented | | ||
|
|
||
| ## Binary patch hunks | ||
|
|
||
| `swe_bench_pro_eval.py` strips binary diff sections before apply. Preflight warns when | ||
| binary hunks are present so maintainers know patches may be incomplete. | ||
|
|
||
| ## References | ||
|
|
||
| - [SWE-bench Pro paper](https://static.scale.com/uploads/654197dc94d34f66c0f5184e/SWEAP_Eval_Scale%20(9).pdf) | ||
| - [HuggingFace dataset](https://huggingface.co/datasets/ScaleAI/SWE-bench_Pro) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,196 @@ | ||
| #!/usr/bin/env python3 | ||
| """Preflight checks before running SWE-bench Pro evaluation. | ||
|
|
||
| Validates patch JSON, run_scripts layout, and known harness footguns (#101, #93) | ||
| before invoking swe_bench_pro_eval.py. | ||
|
|
||
| Usage: | ||
| python scripts/swebench_preflight.py --patches gold_patches.json --scripts-dir run_scripts | ||
| python scripts/swebench_preflight.py --patches preds.json --scripts-dir run_scripts --json | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import argparse | ||
| import json | ||
| import re | ||
| import sys | ||
| from pathlib import Path | ||
| from typing import Any | ||
|
|
||
| REQUIRED_PATCH_KEYS = ("instance_id", "patch", "prefix") | ||
| KNOWN_WARNINGS: dict[str, str] = { | ||
| "NodeBB": ( | ||
| "NodeBB instances may log sendmail-not-found during email tests when running " | ||
| "golden patches (#101). Tests may still pass; see docs/GOLDEN_PATCH.md." | ||
| ), | ||
| } | ||
|
|
||
|
|
||
| def load_patches(path: Path) -> list[dict[str, Any]]: | ||
| data = json.loads(path.read_text()) | ||
| if not isinstance(data, list): | ||
| raise ValueError(f"{path}: expected JSON array of patch objects") | ||
| return data | ||
|
|
||
|
|
||
| def validate_patches(patches: list[dict[str, Any]]) -> tuple[list[dict[str, Any]], list[str]]: | ||
| gates: list[dict[str, Any]] = [] | ||
| errors: list[str] = [] | ||
| seen: set[str] = set() | ||
|
|
||
| for i, entry in enumerate(patches): | ||
| if not isinstance(entry, dict): | ||
| errors.append(f"entry {i}: expected object") | ||
| continue | ||
| missing = [k for k in REQUIRED_PATCH_KEYS if k not in entry] | ||
| if missing: | ||
| errors.append(f"entry {i}: missing keys {missing}") | ||
| continue | ||
| iid = entry["instance_id"] | ||
| if iid in seen: | ||
| errors.append(f"duplicate instance_id: {iid}") | ||
| seen.add(iid) | ||
| if not str(entry.get("patch", "")).strip(): | ||
| errors.append(f"{iid}: empty patch") | ||
|
|
||
| gates.append( | ||
| { | ||
| "name": "patch_schema", | ||
| "passed": not errors, | ||
| "detail": f"{len(patches)} patch(es), {len(seen)} unique instance_id(s)", | ||
| } | ||
| ) | ||
| return gates, errors | ||
|
|
||
|
|
||
| def validate_run_scripts( | ||
| patches: list[dict[str, Any]], scripts_dir: Path | ||
| ) -> tuple[list[dict[str, Any]], list[str]]: | ||
| gates: list[dict[str, Any]] = [] | ||
| errors: list[str] = [] | ||
| missing_scripts: list[str] = [] | ||
|
|
||
| for entry in patches: | ||
| iid = entry["instance_id"] | ||
| inst_dir = scripts_dir / iid | ||
| run_script = inst_dir / "run_script.sh" | ||
| parser = inst_dir / "parser.py" | ||
| if not run_script.is_file(): | ||
| missing_scripts.append(iid) | ||
| errors.append(f"{iid}: missing {run_script}") | ||
| if not parser.is_file(): | ||
| errors.append(f"{iid}: missing {parser}") | ||
|
|
||
| gates.append( | ||
| { | ||
| "name": "run_scripts_layout", | ||
| "passed": not missing_scripts, | ||
| "detail": ( | ||
| f"all {len(patches)} instance run_scripts present" | ||
| if not missing_scripts | ||
| else f"missing run_script for {len(missing_scripts)} instance(s)" | ||
| ), | ||
| } | ||
| ) | ||
|
greptile-apps[bot] marked this conversation as resolved.
|
||
| return gates, errors | ||
|
|
||
|
|
||
| def collect_known_warnings(patches: list[dict[str, Any]]) -> list[dict[str, Any]]: | ||
| warnings: list[dict[str, Any]] = [] | ||
| for entry in patches: | ||
| iid = entry["instance_id"] | ||
| for needle, message in KNOWN_WARNINGS.items(): | ||
| if needle in iid: | ||
| warnings.append({"instance_id": iid, "warning": message}) | ||
| return warnings | ||
|
|
||
|
|
||
| def check_binary_patch_sections(patches: list[dict[str, Any]]) -> dict[str, Any]: | ||
| binary_hits = 0 | ||
| for entry in patches: | ||
| patch = entry.get("patch", "") | ||
|
greptile-apps[bot] marked this conversation as resolved.
|
||
| if re.search(r"^Binary files .* differ$", patch, re.MULTILINE): | ||
| binary_hits += 1 | ||
| return { | ||
| "name": "binary_patch_sections", | ||
| "passed": True, | ||
| "detail": ( | ||
| f"{binary_hits} patch(es) contain binary hunks (stripped at eval time)" | ||
| if binary_hits | ||
| else "no binary hunks detected" | ||
| ), | ||
| "warn": binary_hits > 0, | ||
| } | ||
|
|
||
|
|
||
| def run_preflight(patches_path: Path, scripts_dir: Path) -> dict[str, Any]: | ||
| patches = load_patches(patches_path) | ||
| gates: list[dict[str, Any]] = [] | ||
| all_errors: list[str] = [] | ||
|
|
||
| g1, e1 = validate_patches(patches) | ||
| gates.extend(g1) | ||
| all_errors.extend(e1) | ||
|
|
||
| g2, e2 = validate_run_scripts(patches, scripts_dir) | ||
| gates.extend(g2) | ||
| all_errors.extend(e2) | ||
|
|
||
| gates.append(check_binary_patch_sections(patches)) | ||
| warnings = collect_known_warnings(patches) | ||
|
|
||
| active = [g for g in gates if not g.get("warn")] | ||
| passed = all(g["passed"] for g in active) | ||
|
|
||
| return { | ||
| "patches": str(patches_path.resolve()), | ||
| "scripts_dir": str(scripts_dir.resolve()), | ||
| "patch_count": len(patches), | ||
| "gates": gates, | ||
| "warnings": warnings, | ||
| "errors": all_errors, | ||
| "passed": passed, | ||
| } | ||
|
|
||
|
|
||
| def main(argv: list[str] | None = None) -> int: | ||
| parser = argparse.ArgumentParser(description="SWE-bench Pro eval preflight") | ||
| parser.add_argument("--patches", type=Path, required=True, help="Patch JSON for eval") | ||
| parser.add_argument( | ||
| "--scripts-dir", | ||
| type=Path, | ||
| default=Path("run_scripts"), | ||
| help="Directory with per-instance run_script.sh (default: run_scripts)", | ||
| ) | ||
| parser.add_argument("--json", action="store_true", help="Emit machine-readable report") | ||
| args = parser.parse_args(argv) | ||
|
|
||
| if not args.patches.is_file(): | ||
| print(f"error: patches file not found: {args.patches}", file=sys.stderr) | ||
| return 1 | ||
| if not args.scripts_dir.is_dir(): | ||
| print(f"error: scripts dir not found: {args.scripts_dir}", file=sys.stderr) | ||
| return 1 | ||
|
|
||
| report = run_preflight(args.patches, args.scripts_dir) | ||
| if args.json: | ||
| print(json.dumps(report, indent=2)) | ||
| else: | ||
| status = "PASS" if report["passed"] else "FAIL" | ||
| print(f"SWE-bench Pro preflight — {status}") | ||
| for gate in report["gates"]: | ||
| mark = "PASS" if gate["passed"] else "FAIL" | ||
| if gate.get("warn"): | ||
| mark = "WARN" | ||
| print(f" [{mark}] {gate['name']}: {gate['detail']}") | ||
| for w in report["warnings"]: | ||
| print(f" [WARN] {w['instance_id']}: {w['warning']}") | ||
| for err in report["errors"]: | ||
| print(f" [ERR] {err}") | ||
|
|
||
| return 0 if report["passed"] else 1 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| raise SystemExit(main()) | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.