Skip to content

DEV-1523: postgres benchmark support via DbConnection abstraction - #20

Merged
ZmeiGorynych merged 26 commits into
mainfrom
egor/dev-1523-support-postgres-based-benchmarks
Jun 3, 2026
Merged

DEV-1523: postgres benchmark support via DbConnection abstraction#20
ZmeiGorynych merged 26 commits into
mainfrom
egor/dev-1523-support-postgres-based-benchmarks

Conversation

@ZmeiGorynych

@ZmeiGorynych ZmeiGorynych commented Jun 3, 2026

Copy link
Copy Markdown
Member

Summary

  • DbConnection protocol (db_connection.py) — SqliteDbConnection and PostgresDbConnection (psycopg2-backed, read_only wraps in BEGIN/ROLLBACK); make_db_connection is the single backend-branching factory, keyed on benchmark.db_backend
  • Benchmark descriptorsLIVESQLBENCH_POSTGRES and MINI_INTERACT_POSTGRES added to benchmark.py; new db_backend: Literal["sqlite","postgres"] and per_task_db_isolation: bool fields
  • Harnessexecute_env_action / execute_submit_action dispatch to postgres path; materialize_task_db uses per_task_db_isolation instead of hardcoded dataset strings
  • tolerant_gradermake_executor(benchmark) factory; _multi_sql_execute benchmark kwarg; grade_and_write auto-wires postgres executor
  • _submit.py_dry_run_sql and capture_result_snapshot accept benchmark kwarg and route through DbConnection for postgres; _dry_run_error_message is now backend-agnostic
  • orchestrator_slayer_ingest extracted as patchable helper; _phase1_ingest accepts db_url; phases 3 & 4 skip for postgres (native types)
  • cache.pyfingerprint_of hashes schema text for postgres; ensure_db_cache skips sqlite file check; _build_async constructs postgres db_url from env vars

Test plan

  • tests/test_db_connection.py — 11 unit tests for both backends
  • tests/test_benchmark_postgres.py — 15 tests for new benchmark descriptors
  • tests/test_tolerant_grader_postgres.py — make_executor, _multi_sql_execute, grade_submission with mocked postgres
  • tests/test_harness_postgres_dispatch.py — harness dispatch to postgres path
  • tests/test_otf_postgres.py — fingerprint_of, ensure_db_cache, phase1_ingest, phases 3/4 skip
  • tests/test_submit_postgres.py — _dry_run_sql, capture_result_snapshot, submit_raw_sql wiring
  • Full non-integration suite: 1954 passed, 95 skipped ✅

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added PostgreSQL-backed benchmarks and runtime support (new Postgres variants).
    • New multi-tier tolerant SQL grader with structured miss diagnostics and optional LLM judge.
    • Per-submission inline grading and persistent submission annotations.
  • Enhancements

    • Backend-aware execution/dry-run and unified DB connection handling for SQLite/Postgres.
    • Improved diagnostic snapshots, error messages, and audited-gold consolidation for mini_interact.
  • Tests

    • Expanded test coverage for Postgres paths and grading behaviors.

Add DbConnection protocol with SqliteDbConnection / PostgresDbConnection
implementations; make_db_connection is the single factory that branches on
benchmark.db_backend. Extend benchmark.py with LIVESQLBENCH_POSTGRES and
MINI_INTERACT_POSTGRES; add db_backend / per_task_db_isolation fields.

Harness: dispatch execute_env_action / execute_submit_action to postgres
path for postgres benchmarks; materialize_task_db uses per_task_db_isolation.
tolerant_grader: make_executor factory, _multi_sql_execute benchmark kwarg,
grade_and_write auto-wires postgres executor.
_submit.py: _dry_run_sql and capture_result_snapshot accept benchmark kwarg
and route through DbConnection for postgres; _dry_run_error_message is now
backend-agnostic ("DB error" not "SQLite error").
orchestrator: _slayer_ingest extracted as patchable helper; _phase1_ingest
accepts db_url; phases 3/4 skip for postgres. cache.py: fingerprint_of hashes
schema text for postgres; ensure_db_cache skips sqlite check; _build_async
constructs postgres db_url from env vars.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@linear

linear Bot commented Jun 3, 2026

Copy link
Copy Markdown

DEV-1523

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@ZmeiGorynych, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 28 minutes and 21 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 71255c6b-0631-4ec7-a9bd-b5f0c69a46d2

📥 Commits

Reviewing files that changed from the base of the PR and between 689daed and faa261f.

📒 Files selected for processing (9)
  • src/bird_interact_agents/agents/claude_sdk_otf/agent.py
  • src/bird_interact_agents/agents/claude_sdk_otf_ainteract/agent.py
  • src/bird_interact_agents/cloud/driver.py
  • src/bird_interact_agents/cloud/ray_app.py
  • src/bird_interact_agents/db_connection.py
  • src/bird_interact_agents/slayer_pipeline/orchestrator.py
  • tests/cloud/test_driver.py
  • tests/test_db_connection.py
  • tests/test_tolerant_grader_postgres.py
📝 Walkthrough

Walkthrough

Adds a tolerant SQL grader, Postgres DB backend and benchmark variants, a DB connection abstraction with Postgres/SQLite executors, inline grading orchestration and snapshots, harness/agent/run/orchestrator cache wiring for Postgres, and extensive test coverage.

Changes

DEV-1515 complete stack

Layer / File(s) Summary
Complete change set
src/..., tests/..., scripts/..., pyproject.toml
All edits implementing DEV-1515: benchmark registry updates (Postgres variants and db_backend), DB connection abstraction and factory, tolerant_grader with cascade tiers and miss diagnostics, inline grading orchestration (grade_in_place, implicit annotation), submit/dry-run snapshot backend-aware changes, harness Postgres dispatch and env handling, slayer_otf cache/orchestrator refactors for Postgres, ray app grader wiring, agent dataset allowance and usage payload updates, runtime/reference threading, plus comprehensive tests and dependency extras.

Sequence Diagram(s)

(Skipped — changes are broad and aggregated across many components; no single 3+ component sequential flow exclusively captures the entire PR.)

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • MotleyAI/bird-agents#6: Related changes touching _submit.py submission flow and dry-run/diagnostic wiring.
  • MotleyAI/bird-agents#16: Related script changes for DEV-1515 strict-miss diagnostics and mini_interact path naming.
  • MotleyAI/bird-agents#9: Related updates to framework dataset validation and routing logic used by claude_sdk_otf bindings.

"A rabbit hops through SQL and logs with care,
Sampling rows and judging cases fair;
Postgres paths and snapshots neat,
Miss diagnostics catch what's off-beat,
Hooray—annotations and tests all in the lair!" 🐇✨

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch egor/dev-1523-support-postgres-based-benchmarks

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
.claude/skills/_shared/audit-gold-sql.contract.md (1)

11-33: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add variant_id and primary fields to the schema definition.

The multi-variant mechanics (lines 130-136) introduce variant_id and primary fields, but these are not documented in the top-level JSON schema. Downstream parsers and validators need these fields defined in the schema.

📋 Proposed schema addition
 {
   "instance_id": "<task instance_id>",
+  "variant_id": "<optional: kebab-case slug when multi-variant, e.g. 'labeled_snippet'>",
+  "primary": <optional: true for exactly one variant when multi-variant, false for others>,
   "selected_database": "<task db>",
   "benchmark": "<mini_interact | livesqlbench>",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/skills/_shared/audit-gold-sql.contract.md around lines 11 - 33, The
top-level JSON schema in .claude/skills/_shared/audit-gold-sql.contract.md is
missing the multi-variant fields introduced later; add "variant_id" (string) and
"primary" (boolean) to the top-level object definition so downstream parsers can
validate them. Update the schema block that defines keys like "instance_id",
"selected_database", "benchmark", etc., to include "variant_id" with a short
description (e.g., variant identifier) and "primary" with a description (e.g.,
true for the canonical variant), and ensure these fields are allowed alongside
"original_sol_sql"/"audited_sol_sql" to match the multi-variant mechanics
mentioned in the later lines.
src/bird_interact_agents/agents/_submit.py (1)

238-246: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: capture_result_snapshot call sites missing benchmark parameter.

The capture_result_snapshot function now accepts a benchmark parameter (line 83) to route postgres tasks through the DbConnection abstraction. However, both call sites in _diagnostic_payload (lines 238-246) omit this parameter:

predicted = (
    capture_result_snapshot(
        submitted_sql, db_name, data_path_base, db_file_path=db_file_path,
    )
    if not skip_snapshots else None
)
gold = (
    capture_result_snapshot(
        sol_sql, db_name, data_path_base, db_file_path=db_file_path,
    )
    if not skip_snapshots else None
)

When benchmark is None, the function's postgres conditional (line 96: if getattr(benchmark, "db_backend", "sqlite") == "postgres") will always evaluate to False, causing all snapshots—even for postgres tasks—to fall through to the SQLite path. This will fail for postgres benchmarks or produce incorrect diagnostics.

🐛 Proposed fix to pass benchmark parameter

_diagnostic_payload should derive the benchmark from sample_status.original_data["dataset"] (mirroring the pattern in submit_raw_sql lines 480-486 and submit_slayer_query lines 740-746), then pass it to both snapshot calls:

 def _diagnostic_payload(
     *,
     submitted_sql: str | None,
     sample_status: Any,
     data_path_base: str,
     observation: str | None,
     p1: bool,
     p2: bool,
     json_failed: bool = False,
     translation_failed: bool = False,
     dry_run_failed: bool = False,
     infrastructure_failed: bool = False,
     phase1_observation_audited: str | None = None,
     phase1_observation_original: str | None = None,
 ) -> dict[str, Any]:
     """..."""
     db_name = sample_status.original_data["selected_database"]
     sol_sql = _first_sql(sample_status.original_data.get("sol_sql"))
     pre_phase = getattr(sample_status, "current_phase", 1)
     db_file_path = sample_status.original_data.get("db_file_path")
+    
+    # Resolve benchmark for postgres routing in capture_result_snapshot
+    _dataset = sample_status.original_data.get("dataset", "")
+    _benchmark = None
+    if _dataset:
+        try:
+            _benchmark = _get_benchmark(_dataset)
+        except ValueError:
+            pass
 
     skip_snapshots = json_failed or translation_failed
     predicted = (
         capture_result_snapshot(
             submitted_sql, db_name, data_path_base, db_file_path=db_file_path,
+            benchmark=_benchmark,
         )
         if not skip_snapshots else None
     )
     gold = (
         capture_result_snapshot(
             sol_sql, db_name, data_path_base, db_file_path=db_file_path,
+            benchmark=_benchmark,
         )
         if not skip_snapshots else None
     )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bird_interact_agents/agents/_submit.py` around lines 238 - 246,
_diagnostic_payload is calling capture_result_snapshot for predicted and gold
snapshots but omits the new benchmark parameter, causing postgres tasks to be
treated as sqlite; derive the benchmark the same way other submitters do (read
dataset from sample_status.original_data["dataset"] and obtain the benchmark
object) and pass that benchmark into both capture_result_snapshot calls (the two
invocations in _diagnostic_payload) so the function can route postgres work
through the DbConnection abstraction.
🧹 Nitpick comments (12)
src/bird_interact_agents/cloud/post_run_merge.py (1)

560-564: 💤 Low value

Consider making the audit-report write best-effort.

The OTF merge_report.json write at Line 410-415 intentionally swallows OSError so a logging-side failure can't poison an already-completed merge. This audit write isn't guarded, so a write failure raises after destinations were already mutated.

♻️ Optional: mirror the best-effort write
-    audit_path = downloaded_run_dir / "annotation_merge_report.json"
-    audit_path.write_text(report.model_dump_json(indent=2) + "\n")
-    return report
+    audit_path = downloaded_run_dir / "annotation_merge_report.json"
+    try:
+        audit_path.write_text(report.model_dump_json(indent=2) + "\n")
+    except OSError:
+        pass
+    return report
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bird_interact_agents/cloud/post_run_merge.py` around lines 560 - 564, The
audit write using audit_path.write_text(report.model_dump_json(indent=2) + "\n")
should be best-effort like the OTF merge_report.json write: wrap that call in a
try/except that catches OSError (and optionally Exception) and logs a warning
instead of letting the exception propagate so downstream state stays consistent;
use the existing logging facility in this module (e.g., logger) to emit a clear
message including the target path and error, then continue and return report.
tests/test_tolerant_grader_postgres.py (2)

183-183: 💤 Low value

Use underscore prefix for unused unpacked variable.

The cols variable is unpacked but never used in the assertion. Per Python convention, prefix it with _ to indicate it's intentionally unused.

♻️ Suggested fix
-        rows, cols = _multi_sql_execute(
+        rows, _cols = _multi_sql_execute(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_tolerant_grader_postgres.py` at line 183, The unpacked variable
"cols" returned from _multi_sql_execute is unused; update the unpacking to use a
leading underscore (e.g., "_cols" or simply "_") to follow Python convention for
intentionally unused variables. Locate the call to _multi_sql_execute (the
assignment "rows, cols = _multi_sql_execute(...)") and change "cols" to "_cols"
(or "_") so linters/readers know it's unused, leaving "rows" intact and not
changing any other logic.

156-156: 💤 Low value

Use underscore prefix for unused unpacked variable.

The cols variable is unpacked but never used in the assertion. Per Python convention, prefix it with _ to indicate it's intentionally unused.

♻️ Suggested fix
-    rows, cols = _multi_sql_execute(
+    rows, _cols = _multi_sql_execute(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_tolerant_grader_postgres.py` at line 156, The test unpacks "rows,
cols = _multi_sql_execute(...)" but never uses cols; rename the unused unpacked
variable to "_cols" (or simply "_" ) to follow Python convention and signal it's
intentionally unused. Locate the unpack in the test function where
_multi_sql_execute is called and update "cols" to "_cols" so linter/readers know
the value is ignored.
tests/test_eval_annotation_schema.py (1)

239-240: 💤 Low value

Avoid ambiguous single-letter variable name l.

The variable name l (lowercase L) is easily confused with 1 (digit one) or I (uppercase i). Use a more descriptive name like line.

♻️ Suggested fix
-    indent_lines = [l for l in body_lines[1:-1] if l.strip()]
-    assert all(l.startswith("  ") for l in indent_lines)
+    indent_lines = [line for line in body_lines[1:-1] if line.strip()]
+    assert all(line.startswith("  ") for line in indent_lines)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_eval_annotation_schema.py` around lines 239 - 240, Replace the
ambiguous single-letter variable `l` with a descriptive name `line` in the list
comprehension and assertion: change "indent_lines = [l for l in body_lines[1:-1]
if l.strip()]" to use "line" and update the assertion "assert
all(l.startswith(\"  \") for l in indent_lines)" to "assert
all(line.startswith(\"  \") for line in indent_lines)" so the variable name is
clear and consistent.
tests/test_local_run_cascading.py (2)

79-85: 💤 Low value

Similar cleanup opportunity in FakePass and FakeFail.

The FakePass executor can also drop the unused noqa and be simplified to a direct return, while FakeFail correctly uses the conditional to distinguish predicted vs gold.

♻️ Optional simplification for FakePass
     class FakePass:
-        def __call__(self, sql, *, db_path, conn):  # noqa: ARG002,ARG005  # noqa: ARG002
+        def __call__(self, sql, *, db_path, conn):  # noqa: ARG002
             return ([(1,)], ["a"])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_local_run_cascading.py` around lines 79 - 85, The FakePass and
FakeFail executor implementations can be cleaned up: in FakePass remove the
redundant noqa comments from its __call__ signature and simplify the body to
directly return ([(1,)], ["a"]) without extra annotations; leave FakeFail's
__call__ as-is but ensure its conditional remains to return ([(99,)], ["a"])
when "predicted" is in sql and ([(1,)], ["a"]) otherwise, and also remove any
duplicated noqa tokens from its signature if present.

31-32: 💤 Low value

Clean up noqa comments and simplify the ternary.

The noqa comment has two issues:

  1. It appears twice (# noqa: ARG002,ARG005 # noqa: ARG002)
  2. ARG005 is for unused lambda arguments, not method arguments

Additionally, both branches of the ternary return identical values, making the conditional useless (Ruff RUF034). Since the test simulates a pass scenario where both SQLs produce the same result, you can simplify to a direct return.

♻️ Proposed cleanup
-    class FakeExecutor:
-        def __call__(self, sql, *, db_path, conn):  # noqa: ARG002,ARG005  # noqa: ARG002
-            return ([(1,)], ["a"]) if sql == submitted else ([(1,)], ["a"])
+    class FakeExecutor:
+        def __call__(self, sql, *, db_path, conn):  # noqa: ARG002
+            return ([(1,)], ["a"])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_local_run_cascading.py` around lines 31 - 32, The __call__
method's noqa comment should be cleaned up and the redundant ternary simplified:
remove the duplicated "# noqa" and drop ARG005 (it's not applicable to a
method), leaving a single appropriate noqa if needed; then replace the
conditional return (which returns the same value for both branches) with a
direct unconditional return of ([(1,)], ["a"]) in the __call__ implementation
(the variable submitted is not needed for the result).
src/bird_interact_agents/eval/implicit_annotation.py (1)

37-37: 💤 Low value

Micro-optimization: avoid redundant set() conversion.

Calling set(benchmark_names()) on every invocation creates a new set each time. If benchmark_names() returns a small collection, checking membership directly (benchmark in benchmark_names()) would be simpler and equally fast for the typical 2-3 benchmark case.

♻️ Optional simplification
-    if benchmark in set(benchmark_names()):
+    if benchmark in benchmark_names():
         return get_benchmark(benchmark).data_file
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bird_interact_agents/eval/implicit_annotation.py` at line 37, The
membership check currently wraps benchmark_names() in set(...) causing an
unnecessary allocation each call; change the condition to test membership
directly against the result of benchmark_names() (i.e., remove the set(...)
wrapper) so the code reads a direct "benchmark in benchmark_names()" using the
existing benchmark variable and the benchmark_names() function.
scripts/consolidate_mini_interact_audited.py (1)

23-27: ⚡ Quick win

Use paths.audited_gold_root() instead of computing the path relative to the script file.

As per coding guidelines, all data paths must be resolved via bird_interact_agents.paths.*_root() helpers. This ensures consistency with how the rest of the codebase locates data directories.

🔧 Proposed fix
-ROOT = Path(__file__).resolve().parents[1]
-AUDITED = ROOT / "audited_gold"
+from bird_interact_agents import paths
+
+AUDITED = paths.audited_gold_root()
 
-MINI_INTERACT_OUT = AUDITED / "mini_interact_audited.jsonl"
-LIVESQLBENCH = AUDITED / "livesqlbench_audited.jsonl"
+MINI_INTERACT_OUT = AUDITED / "mini_interact_audited.jsonl"
+LIVESQLBENCH = AUDITED / "livesqlbench_audited.jsonl"

As per coding guidelines: **/*.py files must resolve data paths via bird_interact_agents.paths.*_root() helpers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/consolidate_mini_interact_audited.py` around lines 23 - 27, Replace
the manual path construction (ROOT, AUDITED, MINI_INTERACT_OUT, LIVESQLBENCH)
with the audited-root helper: call
bird_interact_agents.paths.audited_gold_root() to obtain the audited_gold
directory and then derive MINI_INTERACT_OUT and LIVESQLBENCH from that returned
Path; update any references to ROOT/AUDITED to use the helper-returned Path and
remove the relative Path(__file__).resolve().parents[1] logic.
src/bird_interact_agents/cloud/driver.py (1)

725-755: ⚡ Quick win

Consider logging the FileNotFoundError details.

When _cascading_report.emit_cascading_eval_json raises FileNotFoundError (line 750), the error is stored in metrics["cascading_phase1_error"] but not logged. For diagnosability, consider adding a logger.warning(...) before storing the error string so operators can see the issue in logs without inspecting the metrics dict.

📋 Add logging for cascade aggregation errors
     except FileNotFoundError as exc:
         # Aggregator is strict — a missing per-row file raises so we
         # never silently under-count. Surface it as a side-channel entry
         # and leave the in-memory metrics writeable.
+        logger.warning(
+            "[fetch] cascading_phase1 aggregation failed for run %s: %s",
+            dest.name, exc,
+        )
         metrics["cascading_phase1_error"] = str(exc)
         return metrics
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bird_interact_agents/cloud/driver.py` around lines 725 - 755, In
_emit_cascading_phase1_on_fetch, when calling
_cascading_report.emit_cascading_eval_json catch FileNotFoundError and log the
exception details before updating metrics["cascading_phase1_error"];
specifically, add a logger.warning or logger.exception call (including exc info
and context about rows_dir/eval_path) in the except FileNotFoundError block so
the FileNotFoundError raised by _cascading_report.emit_cascading_eval_json is
visible in logs as well as stored in metrics.
src/bird_interact_agents/cloud/ray_app.py (1)

592-668: ⚡ Quick win

Verify _grader_data_dir is always bound when needed.

Line 592 uses locals().get("data_dir") to retrieve the data_dir that was bound at line 543. However, if the try block at line 536 raises an exception before reaching line 543, data_dir will not exist in locals, and _grader_data_dir will be None. The guard at line 614 correctly handles this by raising a RuntimeError, but the variable naming is confusing.

Consider binding _grader_data_dir = None before the outer try block (line 534), then assigning _grader_data_dir = data_dir after line 543 within the try. This makes the scoping explicit and avoids relying on locals().

♻️ Clearer scoping
 log_dir = Path(tempfile.mkdtemp(prefix="cloud_log_"))
 log_tmp = log_dir / "task.log"
 task_start_ts = time.time()
+_grader_data_dir = None

 try:
     with fd_capture(log_tmp):
         try:
             # `cfg["data_dir"]` is the benchmark's container_data_dir...
             data_dir = cfg.get("data_dir") or "/data/mini-interact"
+            _grader_data_dir = data_dir

             row = asyncio.run(
                 _run_one_task_async(
                     ...
                 )
             )
         except Exception as e:  # noqa: BLE001
             row = _build_error_row(iid, database, str(e))
 finally:
     pass

 # DEV-1515: inline grader produces a SubmissionAnnotation per task
-_grader_data_dir = locals().get("data_dir")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bird_interact_agents/cloud/ray_app.py` around lines 592 - 668, The code
relies on locals().get("data_dir") to set _grader_data_dir which can be None if
an earlier exception prevents binding; initialize _grader_data_dir = None before
the outer try, then after you assign data_dir (the binding at/after where
data_dir is created) set _grader_data_dir = data_dir, and replace the
locals().get("data_dir") call with direct use of _grader_data_dir in the grader
block (the call sites include the _grade_one_submission db_path construction and
the RuntimeError guard that checks _grader_data_dir); this makes scoping
explicit and avoids relying on locals().
scripts/dev1515_strict_miss_diagnostics.py (1)

32-40: ⚡ Quick win

Clarify/remove the redundant mini-interact directory in ROOTS["mini_interact"].

tests/test_paths_annotations.py asserts mini-interact (dash) and mini_interact (underscore) are canonicalized to the same on-disk directory under annotations/ (the underscore form, i.e. annotations/mini_interact/...). If the script can assume annotations were written via the path helpers, the paths.annotations_root() / "mini-interact" entry is redundant; if you need to support legacy already-written annotations/mini-interact/..., keep it but document that it’s a backward-compat fallback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/dev1515_strict_miss_diagnostics.py` around lines 32 - 40, The ROOTS
mapping for "mini_interact" contains both paths.annotations_root() /
"mini-interact" and paths.annotations_root() / "mini_interact", which is
redundant if annotations are always created via the path helpers; remove the
dashed entry (paths.annotations_root() / "mini-interact") from
ROOTS["mini_interact"] to keep only paths.annotations_root() / "mini_interact",
or if you must support legacy on-disk names keep the dashed entry but add a
clear comment documenting it as a backward-compatibility fallback; update the
ROOTS definition and accompanying comment near the ROOTS constant and ensure
code that consumes ROOTS relies on paths.annotations_root() canonicalization.
src/bird_interact_agents/agents/_submit.py (1)

114-114: 💤 Low value

Consider adding strict=True to zip() calls for safer column/type pairing.

The zip() calls building column metadata assume col_names and types (or types_) are always the same length. Adding strict=True makes this assumption explicit and will raise ValueError if the lengths mismatch, catching potential logic errors.

♻️ Optional refinement
             return {
                 "columns": [
-                    {"name": n, "type": t} for n, t in zip(col_names, types)
+                    {"name": n, "type": t} for n, t in zip(col_names, types, strict=True)
                 ],
                 ...

(Similarly for line 155 in the SQLite path.)

Also applies to: 155-155

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bird_interact_agents/agents/_submit.py` at line 114, The
list-comprehension that builds column metadata uses zip(col_names, types) which
silently truncates on length mismatch; change it to zip(col_names, types,
strict=True) so a ValueError is raised for length mismatches and prevents silent
mispairing, and apply the same change to the other zip usage that pairs
col_names with types_ in the SQLite path; update the comprehensions that
reference col_names, types and col_names, types_ respectively to include
strict=True.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/bird_interact_agents/db_connection.py`:
- Around line 118-141: The execute() method currently runs
cur.execute("ROLLBACK") in both the except and finally blocks causing a
redundant rollback; remove the rollback logic from the except clause and keep
the single guarded rollback in the finally block (i.e., when self._read_only is
true call cur.execute("ROLLBACK") inside the finally only), preserving
re-raising the psycopg2.Error and the existing try/except around the finally
rollback to avoid swallowing errors; reference symbols: execute,
self._read_only, cur.execute("ROLLBACK").

In `@src/bird_interact_agents/harness.py`:
- Around line 173-179: The current comparison in harness.py iterates sol_sqls
and uses set(map(tuple, ...)) which drops duplicate rows; change the comparison
to use multisets so row multiplicity is preserved (e.g., import Counter from
collections and compare Counter(map(tuple, pred_rows)) == Counter(map(tuple,
gold_rows))). Update the block that calls _run for each gold_sql and sets p1 so
it uses Counter-based equality between pred_rows and gold_rows.

In `@src/bird_interact_agents/run.py`:
- Around line 1070-1094: The grader currently always builds a sqlite path
(per_task_db) and passes it to grade_one_submission, which breaks tasks whose
td["db_backend"] == "postgres"; change the logic around per_task_db to be
backend-aware: inspect task_data/td for db_backend (and preferably
db_connection_uri/db_conn) and if db_backend == "postgres" pass the Postgres
connection URI (td["db_connection_uri"] or an env-provided BIRD_DB_PATH) into
grade_one_submission instead of the sqlite path; otherwise keep the existing
Path(data_dir)/selected_database/... .sqlite construction. Ensure you update the
call-site to pass the connection string/URI (or an explicit db_backend param) so
grade_one_submission can open the correct backend.

In `@src/bird_interact_agents/slayer_otf/cache.py`:
- Around line 329-355: The validation misses Postgres schema files causing
fingerprint_of(db_name=db, data_root=effective_root, benchmark=benchmark) to
later fail; update the ensure_db_cache logic so that when is_postgres is True
you construct schema_path = effective_root / db / f"{db}_schema.txt" (or the
exact schema filename used by fingerprint_of) and append (schema_path, "schema")
to required_files before the existence loop, so the FileNotFoundError is raised
during validation rather than inside fingerprint_of; alternatively, if you
prefer resilience in fingerprint_of, change fingerprint_of to skip hashing the
schema file if it does not exist, but the preferred change is to add schema_path
to required_files in the block that checks is_postgres.

---

Outside diff comments:
In @.claude/skills/_shared/audit-gold-sql.contract.md:
- Around line 11-33: The top-level JSON schema in
.claude/skills/_shared/audit-gold-sql.contract.md is missing the multi-variant
fields introduced later; add "variant_id" (string) and "primary" (boolean) to
the top-level object definition so downstream parsers can validate them. Update
the schema block that defines keys like "instance_id", "selected_database",
"benchmark", etc., to include "variant_id" with a short description (e.g.,
variant identifier) and "primary" with a description (e.g., true for the
canonical variant), and ensure these fields are allowed alongside
"original_sol_sql"/"audited_sol_sql" to match the multi-variant mechanics
mentioned in the later lines.

In `@src/bird_interact_agents/agents/_submit.py`:
- Around line 238-246: _diagnostic_payload is calling capture_result_snapshot
for predicted and gold snapshots but omits the new benchmark parameter, causing
postgres tasks to be treated as sqlite; derive the benchmark the same way other
submitters do (read dataset from sample_status.original_data["dataset"] and
obtain the benchmark object) and pass that benchmark into both
capture_result_snapshot calls (the two invocations in _diagnostic_payload) so
the function can route postgres work through the DbConnection abstraction.

---

Nitpick comments:
In `@scripts/consolidate_mini_interact_audited.py`:
- Around line 23-27: Replace the manual path construction (ROOT, AUDITED,
MINI_INTERACT_OUT, LIVESQLBENCH) with the audited-root helper: call
bird_interact_agents.paths.audited_gold_root() to obtain the audited_gold
directory and then derive MINI_INTERACT_OUT and LIVESQLBENCH from that returned
Path; update any references to ROOT/AUDITED to use the helper-returned Path and
remove the relative Path(__file__).resolve().parents[1] logic.

In `@scripts/dev1515_strict_miss_diagnostics.py`:
- Around line 32-40: The ROOTS mapping for "mini_interact" contains both
paths.annotations_root() / "mini-interact" and paths.annotations_root() /
"mini_interact", which is redundant if annotations are always created via the
path helpers; remove the dashed entry (paths.annotations_root() /
"mini-interact") from ROOTS["mini_interact"] to keep only
paths.annotations_root() / "mini_interact", or if you must support legacy
on-disk names keep the dashed entry but add a clear comment documenting it as a
backward-compatibility fallback; update the ROOTS definition and accompanying
comment near the ROOTS constant and ensure code that consumes ROOTS relies on
paths.annotations_root() canonicalization.

In `@src/bird_interact_agents/agents/_submit.py`:
- Line 114: The list-comprehension that builds column metadata uses
zip(col_names, types) which silently truncates on length mismatch; change it to
zip(col_names, types, strict=True) so a ValueError is raised for length
mismatches and prevents silent mispairing, and apply the same change to the
other zip usage that pairs col_names with types_ in the SQLite path; update the
comprehensions that reference col_names, types and col_names, types_
respectively to include strict=True.

In `@src/bird_interact_agents/cloud/driver.py`:
- Around line 725-755: In _emit_cascading_phase1_on_fetch, when calling
_cascading_report.emit_cascading_eval_json catch FileNotFoundError and log the
exception details before updating metrics["cascading_phase1_error"];
specifically, add a logger.warning or logger.exception call (including exc info
and context about rows_dir/eval_path) in the except FileNotFoundError block so
the FileNotFoundError raised by _cascading_report.emit_cascading_eval_json is
visible in logs as well as stored in metrics.

In `@src/bird_interact_agents/cloud/post_run_merge.py`:
- Around line 560-564: The audit write using
audit_path.write_text(report.model_dump_json(indent=2) + "\n") should be
best-effort like the OTF merge_report.json write: wrap that call in a try/except
that catches OSError (and optionally Exception) and logs a warning instead of
letting the exception propagate so downstream state stays consistent; use the
existing logging facility in this module (e.g., logger) to emit a clear message
including the target path and error, then continue and return report.

In `@src/bird_interact_agents/cloud/ray_app.py`:
- Around line 592-668: The code relies on locals().get("data_dir") to set
_grader_data_dir which can be None if an earlier exception prevents binding;
initialize _grader_data_dir = None before the outer try, then after you assign
data_dir (the binding at/after where data_dir is created) set _grader_data_dir =
data_dir, and replace the locals().get("data_dir") call with direct use of
_grader_data_dir in the grader block (the call sites include the
_grade_one_submission db_path construction and the RuntimeError guard that
checks _grader_data_dir); this makes scoping explicit and avoids relying on
locals().

In `@src/bird_interact_agents/eval/implicit_annotation.py`:
- Line 37: The membership check currently wraps benchmark_names() in set(...)
causing an unnecessary allocation each call; change the condition to test
membership directly against the result of benchmark_names() (i.e., remove the
set(...) wrapper) so the code reads a direct "benchmark in benchmark_names()"
using the existing benchmark variable and the benchmark_names() function.

In `@tests/test_eval_annotation_schema.py`:
- Around line 239-240: Replace the ambiguous single-letter variable `l` with a
descriptive name `line` in the list comprehension and assertion: change
"indent_lines = [l for l in body_lines[1:-1] if l.strip()]" to use "line" and
update the assertion "assert all(l.startswith(\"  \") for l in indent_lines)" to
"assert all(line.startswith(\"  \") for line in indent_lines)" so the variable
name is clear and consistent.

In `@tests/test_local_run_cascading.py`:
- Around line 79-85: The FakePass and FakeFail executor implementations can be
cleaned up: in FakePass remove the redundant noqa comments from its __call__
signature and simplify the body to directly return ([(1,)], ["a"]) without extra
annotations; leave FakeFail's __call__ as-is but ensure its conditional remains
to return ([(99,)], ["a"]) when "predicted" is in sql and ([(1,)], ["a"])
otherwise, and also remove any duplicated noqa tokens from its signature if
present.
- Around line 31-32: The __call__ method's noqa comment should be cleaned up and
the redundant ternary simplified: remove the duplicated "# noqa" and drop ARG005
(it's not applicable to a method), leaving a single appropriate noqa if needed;
then replace the conditional return (which returns the same value for both
branches) with a direct unconditional return of ([(1,)], ["a"]) in the __call__
implementation (the variable submitted is not needed for the result).

In `@tests/test_tolerant_grader_postgres.py`:
- Line 183: The unpacked variable "cols" returned from _multi_sql_execute is
unused; update the unpacking to use a leading underscore (e.g., "_cols" or
simply "_") to follow Python convention for intentionally unused variables.
Locate the call to _multi_sql_execute (the assignment "rows, cols =
_multi_sql_execute(...)") and change "cols" to "_cols" (or "_") so
linters/readers know it's unused, leaving "rows" intact and not changing any
other logic.
- Line 156: The test unpacks "rows, cols = _multi_sql_execute(...)" but never
uses cols; rename the unused unpacked variable to "_cols" (or simply "_" ) to
follow Python convention and signal it's intentionally unused. Locate the unpack
in the test function where _multi_sql_execute is called and update "cols" to
"_cols" so linter/readers know the value is ignored.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8f3b1e48-3ef1-408d-96a8-05fae58b970c

📥 Commits

Reviewing files that changed from the base of the PR and between 81a6751 and 96d407e.

📒 Files selected for processing (106)
  • .claude/skills/_shared/audit-gold-sql.contract.md
  • .claude/skills/annotate-task-submission/SKILL.md
  • .claude/skills/audit-gold-sql-livesqlbench/SKILL.md
  • .claude/skills/audit-gold-sql/SKILL.md
  • .gitignore
  • Dockerfile.cloud
  • README.md
  • analyses/households_failure_analysis_20260531t1008-claudes-slayer-890419.md
  • analyses/museum_failure_analysis_20260531t1013-claudes-slayer-48eb0f.md
  • analyses/raw/cross_cutting_observations.md
  • analyses/raw/households_10.md
  • analyses/raw/households_12.md
  • analyses/raw/households_15.md
  • analyses/raw/households_2.md
  • analyses/raw/museum_10.md
  • analyses/raw/museum_2.md
  • analyses/raw/museum_3.md
  • analyses/raw/museum_4.md
  • analyses/raw/museum_5.md
  • analyses/raw/museum_9.md
  • scripts/consolidate_mini_interact_audited.py
  • scripts/dev1515_cascade_summary.py
  • scripts/dev1515_convert_livesqlbench.py
  • scripts/dev1515_convert_runs.py
  • scripts/dev1515_households_14_multivariant.py
  • scripts/dev1515_reclassify_sufficiency.py
  • scripts/dev1515_remap_failure_classes.py
  • scripts/dev1515_strict_miss_diagnostics.py
  • scripts/generate_annotation_skeletons.py
  • scripts/verify_audited_gold.py
  • src/bird_interact_agents/agents/_submit.py
  • src/bird_interact_agents/agents/agno/agent.py
  • src/bird_interact_agents/agents/claude_sdk/agent.py
  • src/bird_interact_agents/agents/claude_sdk_otf/agent.py
  • src/bird_interact_agents/agents/claude_sdk_otf/prompts.py
  • src/bird_interact_agents/agents/claude_sdk_otf_ainteract/agent.py
  • src/bird_interact_agents/agents/claude_sdk_otf_ainteract/prompts.py
  • src/bird_interact_agents/agents/mcp_agent/agent.py
  • src/bird_interact_agents/agents/pydantic_ai/agent.py
  • src/bird_interact_agents/agents/pydantic_ai_otf_encode/agent.py
  • src/bird_interact_agents/agents/pydantic_ai_recursive/agent.py
  • src/bird_interact_agents/agents/smolagents/agent.py
  • src/bird_interact_agents/benchmark.py
  • src/bird_interact_agents/cloud/_audited_gold_check.py
  • src/bird_interact_agents/cloud/cli.py
  • src/bird_interact_agents/cloud/collation.py
  • src/bird_interact_agents/cloud/driver.py
  • src/bird_interact_agents/cloud/gcs.py
  • src/bird_interact_agents/cloud/image.py
  • src/bird_interact_agents/cloud/post_run_merge.py
  • src/bird_interact_agents/cloud/ray_app.py
  • src/bird_interact_agents/db_connection.py
  • src/bird_interact_agents/eval/__init__.py
  • src/bird_interact_agents/eval/annotate.py
  • src/bird_interact_agents/eval/annotation_io.py
  • src/bird_interact_agents/eval/annotation_schema.py
  • src/bird_interact_agents/eval/cascading_report.py
  • src/bird_interact_agents/eval/grade_in_place.py
  • src/bird_interact_agents/eval/implicit_annotation.py
  • src/bird_interact_agents/eval/regrade.py
  • src/bird_interact_agents/eval/tolerant_grader.py
  • src/bird_interact_agents/harness.py
  • src/bird_interact_agents/paths.py
  • src/bird_interact_agents/results_db.py
  • src/bird_interact_agents/run.py
  • src/bird_interact_agents/slayer_otf/cache.py
  • src/bird_interact_agents/slayer_pipeline/orchestrator.py
  • tests/cloud/test_cli.py
  • tests/cloud/test_collation.py
  • tests/cloud/test_fetch_annotation_merge.py
  • tests/cloud/test_fetch_cascading_phase1.py
  • tests/cloud/test_image_annotations.py
  • tests/cloud/test_inline_grader.py
  • tests/slayer_pipeline/test_orchestrator_conn.py
  • tests/test_benchmark.py
  • tests/test_benchmark_postgres.py
  • tests/test_cascading_report.py
  • tests/test_claude_sdk_otf_agent.py
  • tests/test_claude_sdk_otf_ainteract_agent.py
  • tests/test_claude_sdk_usage.py
  • tests/test_db_connection.py
  • tests/test_dry_run_gate.py
  • tests/test_dual_eval.py
  • tests/test_eval_annotate_cli.py
  • tests/test_eval_annotation_schema.py
  • tests/test_harness_postgres_dispatch.py
  • tests/test_implicit_task_annotation.py
  • tests/test_legacy_field_removal.py
  • tests/test_livesqlbench_audited_gold.py
  • tests/test_local_run_cascading.py
  • tests/test_miss_diagnostics.py
  • tests/test_normalize_sol_sql.py
  • tests/test_otf_postgres.py
  • tests/test_paths.py
  • tests/test_paths_annotations.py
  • tests/test_regrade_cli.py
  • tests/test_results_db.py
  • tests/test_run_local_inline_grader.py
  • tests/test_schema_extension.py
  • tests/test_slayer_otf_cache.py
  • tests/test_submit_postgres.py
  • tests/test_tolerant_grader_comparators.py
  • tests/test_tolerant_grader_multi_sql_conn.py
  • tests/test_tolerant_grader_orchestration.py
  • tests/test_tolerant_grader_postgres.py
  • tests/test_verdict_label_shared.py
💤 Files with no reviewable changes (10)
  • src/bird_interact_agents/agents/pydantic_ai_recursive/agent.py
  • src/bird_interact_agents/agents/pydantic_ai_otf_encode/agent.py
  • src/bird_interact_agents/agents/mcp_agent/agent.py
  • src/bird_interact_agents/agents/agno/agent.py
  • src/bird_interact_agents/agents/pydantic_ai/agent.py
  • src/bird_interact_agents/agents/smolagents/agent.py
  • src/bird_interact_agents/agents/claude_sdk/agent.py
  • src/bird_interact_agents/agents/claude_sdk_otf/agent.py
  • tests/test_claude_sdk_usage.py
  • tests/test_claude_sdk_otf_agent.py

Comment thread src/bird_interact_agents/db_connection.py
Comment thread src/bird_interact_agents/harness.py Outdated
Comment thread src/bird_interact_agents/run.py Outdated
Comment thread src/bird_interact_agents/slayer_otf/cache.py
ZmeiGorynych and others added 3 commits June 3, 2026 09:56
…-annotations-tolerant-grader-post-dev-1478' into egor/dev-1523-support-postgres-based-benchmarks
- db_connection: remove redundant ROLLBACK in except block (finally covers it)
- harness: normalize str sol_sqls to list; use Counter for row comparison
- run: postgres inline grader uses Path(db_name) not a .sqlite path
- cache: add schema_path to required_files for postgres benchmarks
- _submit: thread benchmark through _diagnostic_payload → capture_result_snapshot;
  initialize _benchmark=None before _record closure to fix NameError on early exits
- tolerant_grader: _multi_sql_execute opens shared postgres conn for multi-stmt SQL;
  _pg_executor reuses provided PostgresDbConnection instead of opening a fresh one
- regrade: postgres-aware db_path + wire make_executor for postgres benchmarks
- tests: update spy signatures + mock _open_psycopg2_connection in shared-conn test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Critical:
- PostgresDbConnection.execute: guard fetchall() for non-SELECT (description=None)
- grade_submission: add benchmark= param, thread to _multi_sql_execute for shared-conn logic
- _multi_sql_execute: open shared postgres conn with read_only=False so TEMP TABLE state persists
- grade_in_place / regrade: forward benchmark= to grade_submission

Minor:
- cache.py: URL-escape postgres credentials in connection string
- ray_app.py: bind _grader_data_dir=None before try, assign inside (drop locals().get())
- post_run_merge.py: best-effort OSError on annotation audit write
- driver.py: log FileNotFoundError before storing in metrics
- _submit.py: strict=True on zip(col_names, types)
- implicit_annotation.py: drop redundant set() wrapper on benchmark_names()
- consolidate_mini_interact_audited.py: use paths.audited_gold_root()
- dev1515_strict_miss_diagnostics.py: remove redundant mini_interact underscore root
- tests: fix duplicate noqa, rename l→line, add regression tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/bird_interact_agents/agents/_submit.py (1)

96-124: ⚠️ Potential issue | 🟠 Major

Fix Postgres snapshot bounding — truncation happens after fetchall() loads all rows

In src/bird_interact_agents/agents/_submit.py, the Postgres snapshot truncates rows only after conn.execute(sql) returns, but src/bird_interact_agents/db_connection.py implements PostgresDbConnection.execute() using cur.fetchall(), so _SNAPSHOT_MAX_ROWS doesn’t prevent loading the full result set into memory/latency. The SQLite path is bounded via cur.fetchmany(_SNAPSHOT_MAX_ROWS + 1). Fix by bounding Postgres reads (e.g., cursor fetchmany()/streaming) or rewriting the query with an outer LIMIT for snapshotting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bird_interact_agents/agents/_submit.py` around lines 96 - 124, The
Postgres branch currently calls conn.execute(sql) which (via
PostgresDbConnection.execute) uses cur.fetchall() and loads the entire result
set before truncation; change the Postgres path in the block that checks
getattr(benchmark, "db_backend", "sqlite") == "postgres" so that you bound the
returned rows at source — either (preferred) modify the call to use a
fetchmany/streaming API on the Postgres cursor (invoke a new method on
PostgresDbConnection that uses cur.fetchmany(_SNAPSHOT_MAX_ROWS + 1)) or rewrite
the SQL passed to conn.execute to append a LIMIT _SNAPSHOT_MAX_ROWS+1 for
snapshotting; ensure the subsequent logic still sets truncated, builds sample
using _SNAPSHOT_SAMPLE_SIZE, and returns the same keys ("columns", "row_count",
"row_count_truncated", "sample_rows").
src/bird_interact_agents/cloud/ray_app.py (1)

617-626: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make the inline grader's db_path backend-aware.

Line 623 still hardcodes a SQLite file path. For livesqlbench_postgres, that path will never exist, so the inline grader drops into the exception path and uploads a failed annotation for every task even when execution succeeded. Because the attempt row is still written afterward, cloud fetch will publish incorrect cascading_phase1 results for Postgres runs.

Proposed fix
+        from bird_interact_agents.benchmark import get_benchmark
+
+        bench = get_benchmark(_cloud_benchmark(cfg))
+        grader_db_path = (
+            Path(str(_row_selected_db))
+            if getattr(bench, "db_backend", "sqlite") == "postgres"
+            else Path(_grader_data_dir) / str(_row_selected_db) / f"{_row_selected_db}.sqlite"
+        )
+
         ann_path = _grade_one_submission(
             task_data=task_data,
             submitted_sql=str(_row_submitted_sql),
             rows_dir=annotation_dir,
             run_id=run_id,
-            benchmark=_cloud_benchmark(cfg),
-            db_path=Path(_grader_data_dir)
-                / str(_row_selected_db)
-                / f"{_row_selected_db}.sqlite",
+            benchmark=bench.name,
+            db_path=grader_db_path,
             cost_usd_agent=row.get("usage", {}).get("cost_usd_agent")
                 if isinstance(row.get("usage"), dict) else None,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bird_interact_agents/cloud/ray_app.py` around lines 617 - 626, The inline
grader call to _grade_one_submission currently passes a hardcoded SQLite file
via the db_path parameter (built from _grader_data_dir and _row_selected_db),
which breaks for Postgres backends; change the code around the
_grade_one_submission invocation so db_path is backend-aware: query the backend
via _cloud_benchmark(cfg) (or inspect cfg) and when the benchmark is
"livesqlbench_postgres" (or the Postgres sentinel) supply the appropriate
Postgres connection info (or None/a backend token) instead of a SQLite file,
otherwise keep the existing Path-based SQLite DB; ensure the receiving function
_grade_one_submission (and any callers) handle the alternate Postgres value
correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/bird_interact_agents/eval/regrade.py`:
- Around line 326-329: get and use the canonical benchmark id from
get_benchmark() instead of the raw CLI token: after _bench =
get_benchmark(args.benchmark) assign args.benchmark to the canonical identifier
on _bench (e.g. _bench.name or _bench.canonical_name) so downstream callers like
regrade_run() and the code paths around lines where _is_postgres/_executor are
constructed receive the normalized benchmark name; apply the same change to the
other occurrence noted (around lines 422-425) so annotation/task paths use the
canonical form.

---

Outside diff comments:
In `@src/bird_interact_agents/agents/_submit.py`:
- Around line 96-124: The Postgres branch currently calls conn.execute(sql)
which (via PostgresDbConnection.execute) uses cur.fetchall() and loads the
entire result set before truncation; change the Postgres path in the block that
checks getattr(benchmark, "db_backend", "sqlite") == "postgres" so that you
bound the returned rows at source — either (preferred) modify the call to use a
fetchmany/streaming API on the Postgres cursor (invoke a new method on
PostgresDbConnection that uses cur.fetchmany(_SNAPSHOT_MAX_ROWS + 1)) or rewrite
the SQL passed to conn.execute to append a LIMIT _SNAPSHOT_MAX_ROWS+1 for
snapshotting; ensure the subsequent logic still sets truncated, builds sample
using _SNAPSHOT_SAMPLE_SIZE, and returns the same keys ("columns", "row_count",
"row_count_truncated", "sample_rows").

In `@src/bird_interact_agents/cloud/ray_app.py`:
- Around line 617-626: The inline grader call to _grade_one_submission currently
passes a hardcoded SQLite file via the db_path parameter (built from
_grader_data_dir and _row_selected_db), which breaks for Postgres backends;
change the code around the _grade_one_submission invocation so db_path is
backend-aware: query the backend via _cloud_benchmark(cfg) (or inspect cfg) and
when the benchmark is "livesqlbench_postgres" (or the Postgres sentinel) supply
the appropriate Postgres connection info (or None/a backend token) instead of a
SQLite file, otherwise keep the existing Path-based SQLite DB; ensure the
receiving function _grade_one_submission (and any callers) handle the alternate
Postgres value correctly.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: dd202c2d-d3fa-4a80-a37d-88b80e988444

📥 Commits

Reviewing files that changed from the base of the PR and between 96d407e and f546ce9.

📒 Files selected for processing (28)
  • scripts/consolidate_mini_interact_audited.py
  • scripts/dev1515_convert_runs.py
  • scripts/dev1515_strict_miss_diagnostics.py
  • src/bird_interact_agents/agents/_submit.py
  • src/bird_interact_agents/cloud/driver.py
  • src/bird_interact_agents/cloud/post_run_merge.py
  • src/bird_interact_agents/cloud/ray_app.py
  • src/bird_interact_agents/db_connection.py
  • src/bird_interact_agents/eval/annotate.py
  • src/bird_interact_agents/eval/annotation_schema.py
  • src/bird_interact_agents/eval/cascading_report.py
  • src/bird_interact_agents/eval/grade_in_place.py
  • src/bird_interact_agents/eval/implicit_annotation.py
  • src/bird_interact_agents/eval/regrade.py
  • src/bird_interact_agents/eval/tolerant_grader.py
  • src/bird_interact_agents/harness.py
  • src/bird_interact_agents/run.py
  • src/bird_interact_agents/slayer_otf/cache.py
  • tests/test_cascading_report.py
  • tests/test_dry_run_gate.py
  • tests/test_eval_annotation_schema.py
  • tests/test_local_run_cascading.py
  • tests/test_regrade_cli.py
  • tests/test_run_local_inline_grader.py
  • tests/test_tolerant_grader_comparators.py
  • tests/test_tolerant_grader_orchestration.py
  • tests/test_tolerant_grader_postgres.py
  • tests/test_user_sim_interaction_trajectory.py
💤 Files with no reviewable changes (1)
  • scripts/dev1515_strict_miss_diagnostics.py
🚧 Files skipped from review as they are similar to previous changes (16)
  • tests/test_dry_run_gate.py
  • src/bird_interact_agents/eval/implicit_annotation.py
  • scripts/consolidate_mini_interact_audited.py
  • src/bird_interact_agents/cloud/driver.py
  • tests/test_local_run_cascading.py
  • src/bird_interact_agents/db_connection.py
  • tests/test_cascading_report.py
  • src/bird_interact_agents/eval/grade_in_place.py
  • src/bird_interact_agents/harness.py
  • src/bird_interact_agents/eval/annotation_schema.py
  • scripts/dev1515_convert_runs.py
  • src/bird_interact_agents/slayer_otf/cache.py
  • tests/test_run_local_inline_grader.py
  • src/bird_interact_agents/cloud/post_run_merge.py
  • src/bird_interact_agents/eval/annotate.py
  • src/bird_interact_agents/eval/tolerant_grader.py

Comment thread src/bird_interact_agents/eval/regrade.py
ZmeiGorynych and others added 17 commits June 3, 2026 11:12
Group 1 — cloud grader + snapshot:
- ray_app.py: resolve benchmark object from cfg; use Path(<db>) for postgres
  grader db_path instead of hardcoded SQLite path
- _submit.py: wrap postgres snapshot SQL in SELECT * FROM (...) LIMIT N+1
  so fetchall() is bounded at source instead of loading full result set

Group 2 — read-only enforcement + OTF caller gap:
- db_connection.py: BEGIN READ ONLY instead of bare BEGIN; prevents DML on
  permanent tables at server level even if SQL embeds a COMMIT
- runtime.py: pass benchmark= to ensure_db_cache for postgres OTF path
- reference_build.py: add benchmark= param to ensure_db_reference and
  thread to ensure_db_cache; update test spies to accept benchmark=None

Group 3 — minor / regression fix:
- regrade.py: canonicalize args.benchmark = _bench.name after get_benchmark()
  so task_annotation_ref uses mini_interact (underscore) not mini-interact
- dev1515_strict_miss_diagnostics.py: keep mini_interact (underscore) dir,
  not mini-interact (hyphen) — canonical on-disk form is underscored

Tests: add test_postgres_read_only_uses_begin_read_only regression test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Merge origin/main (claude_sdk_otf_raw agents, db_file_path grader routing)
  and resolve conflicts in ray_app.py and run.py, combining postgres
  backend-awareness (our branch) with the db_file_path per-task sqlite
  isolation path (main).

- harness.py: load_livesqlbench_tasks now accepts dataset_marker param;
  load_benchmark_tasks passes b.dataset_marker so livesqlbench_postgres
  tasks get dataset="livesqlbench_postgres" instead of "livesqlbench".
  Without this, execute_submit_action routed postgres tasks to the SQLite
  path (Codex finding, DEV-1523).

- _submit.py: add strict=True to zip(col_names, types_) in the SQLite
  snapshot path for symmetry with the postgres path at line 120.

- test_benchmark_postgres.py: two new tests pin the dataset_marker
  threading through load_benchmark_tasks → load_livesqlbench_tasks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Pass benchmark= to ensure_db_reference in pydantic_ai_otf_encode so the
  OTF encoder uses the per-benchmark cache/reference roots for postgres tasks
- Fix misleading db_connection.py comment: BEGIN READ ONLY does not prevent
  an embedded COMMIT in a multi-statement string from escaping the transaction
- Clarify harness.py p2=False: correct for one-shot postgres, noted limitation
  for future interactive postgres benchmarks
- Update fake_ensure_db_reference stub in test to accept the new benchmark= kwarg

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tion

harness.py: _pg_execute_submit_action now returns finished=benchmark.one_shot
(not hardcoded True) so mini_interact_postgres tasks in a-/c-interact mode can
continue after a failed or errored submission, matching SQLite interactive
semantics.

cache.py / orchestrator.py: strip BIRD_PG_PASSWORD from the postgresql:// URL
passed to `slayer datasources create`; inject it via PGPASSWORD env var instead
so the credential never appears in subprocess command-line args (visible via ps)
or persists in the datasources/<db>.yaml file written by SLayer.

Tests: five _pg_execute_submit_action finished-flag assertions in
test_benchmark_postgres.py; three password-isolation assertions in
test_otf_postgres.py (PGPASSWORD set, not in URL, _build_async passes
pg_password separately); fake_phase1 stubs in test_slayer_otf_cache.py
updated to accept new pg_password kwarg.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
post_run_merge.py: take origin/main's logger.warning on audit-log write
failure (better than HEAD's silent pass).

ray_app.py: merge backend-aware grader db_path (postgres vs SQLite, HEAD)
with db_file_path support for per-task-isolated SQLite copies (origin/main).

regrade.py: merge postgres db_path carrier (HEAD) with _template.sqlite
fallback for per-task-isolated copies (origin/main).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sol_sqls is a sequence of dependent steps (not independent alternatives).
Replace the per-element loop that opened a fresh connection for each gold
SQL with a single _run_gold_sequence helper that executes all statements
on one shared connection and compares the last result — mirroring
tolerant_grader._multi_sql_execute semantics.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PostgresDbConnection.execute wraps each call in its own BEGIN/ROLLBACK,
so the old _run_gold_sequence loop lost temp-table state between steps.
Add execute_sequence() that issues ONE BEGIN READ ONLY / ROLLBACK for
the whole list, then call it from _run_gold_sequence. Update tests to
expect execute_sequence on the gold connection (not per-stmt execute).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…postgres variants

- _FRAMEWORK_DATASET_MODE_BINDING now accepts both sqlite and postgres dataset
  names per framework (frozenset); _validate_framework_dataset_mode updated
  to match (V3)
- claude_sdk_otf/agent.py and claude_sdk_otf_ainteract/agent.py: relax
  hardcoded single-name check to accept the postgres sibling dataset name (V3)
- pydantic_ai_recursive _resolve_otf_task_storage_dir: pass benchmark= to
  ensure_db_cache so postgres tasks get the correct cache backend (V2)
- Update test stubs in test_one_shot_run.py and test_recursive_runtime_db_root.py
  to accept the new benchmark= kwarg on ensure_db_cache

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… + import cleanup

- harness.load_benchmark_tasks: stamp task["dataset"] = b.dataset_marker for
  non-gold-required benchmarks (mini_interact_postgres was silently falling
  through to the SQLite dispatch path — Codex, DEV-1523)
- tests/test_benchmark_postgres.py: add two new marker-stamp tests for
  mini_interact_postgres and mini_interact backward-compat
- claude_sdk_otf + claude_sdk_otf_ainteract: pass benchmark= to grade_submission
  in the autopsy/cascade path so postgres tasks use the postgres executor
  instead of trying to open a non-existent SQLite file
- db_connection.DbConnection Protocol: declare execute_sequence so callers
  don't need type: ignore[union-attr]; SqliteDbConnection implements it with
  NotImplementedError (postgres-only); harness.py drops the suppression
- slayer_otf/cache._build_async: replace `import os as _os` (inside function)
  with the module-level `os` import, per project import rules
- cloud/driver._build_missing_otf_caches: pass benchmark= to ensure_db_cache
  so OTF cache builds use the right DB backend if ever called for a
  non-SQLite benchmark

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…oting

- pyproject.toml: add psycopg2-binary>=2.9 to cloud and all extras so
  postgres connectivity is available without the mini-interact-agent
  transitive dep
- db_connection.py: PostgresDbConnection.execute / execute_sequence now
  call self._conn.rollback() on psycopg2.Error when read_only=False,
  leaving the connection in a clean state for reuse
- harness.py: fix execute() SQL extraction — strip("'\"") was removing
  ALL consecutive quotes; replace with single-pair outer-quote removal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Users installing narrower extras (pydantic-ai, slayer, claude-sdk) who
choose a postgres-backend benchmark would hit ModuleNotFoundError at
runtime. Adding a dedicated postgres extra makes the dependency opt-in
without requiring cloud or all.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_pg_execute_submit_action returns "SQL execution error: ..." but
classify_submission only matched SQLite/upstream error strings, so
postgres SQL errors were silently bucketed as wrong_result. Add the
postgres error pattern and cover it with a test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rading

Two bugs in _pg_execute_submit_action:

1. Counter(map(tuple, rows)) crashed with TypeError on JSONB/JSON columns
   (psycopg2 returns dict/list). Add _pg_hashable_row() that serialises
   dict/list cells to canonical JSON strings before hashing.

2. conditions["order"] was silently ignored — all comparisons were unordered
   Counter equality, so ordered queries could pass with wrong row order.
   Now branches on conditions.get("order", False), matching ex_base behaviour.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A submitted query such as SELECT pg_sleep(3600) would hang a task worker
indefinitely. Set -c statement_timeout=30000ms (30s default) on the
psycopg2 connection via BIRD_PG_STATEMENT_TIMEOUT env var (0 = no limit).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two correctness bugs:

1. harness: _pg_execute_submit_action with sol_sql=[] called execute_sequence
   on an empty list, which returned ([], no-error); any prediction returning
   empty results then matched gold and got p1=True. Guard: treat empty sol_sql
   as gold_err=True (ungraded), matching SQLite evaluator behaviour.

2. harness: slayer_mcp_stdio_config env copy missed PGPASSWORD when only
   BIRD_PG_PASSWORD was set. Postgres SLayer datasources store a password-free
   URL; the MCP subprocess inherits the env and needs PGPASSWORD to connect.
   Derive PGPASSWORD from BIRD_PG_PASSWORD when not already present.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The persisted SLayer datasource YAML contains a connection URL
(postgresql://user@host:port/db). If BIRD_PG_HOST/PORT/USER changes
after the cache is built, the cached YAML still points at the old
server — the MCP server then fails to authenticate.

Extend _impl_fingerprint_of to include (host:port:user) when benchmark
is postgres, and pass benchmark through the _impl_ok() closure and the
marker-write call. The no-benchmark / SQLite callers in tests are
unaffected (default argument = sqlite path).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…in OTF agents

Postgres submissions were silently graded via sqlite3 because both
claude_sdk_otf and claude_sdk_otf_ainteract agents imported make_executor
but didn't pass it to grade_submission.  The import was there but unused;
default_executor would try to open a non-existent .sqlite file.

Adds two regression tests that verify postgres executor vs sqlite3
dispatch at the grade_submission level.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/test_slayer_otf_cache.py (1)

332-333: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add pg_password parameter to the boom stub for signature compatibility.

The boom stub is missing the pg_password=None parameter that was added to _phase1_ingest. If a Postgres benchmark test calls this code path, the stub will raise a TypeError when ensure_db_cache passes the pg_password keyword argument.

🔧 Proposed fix
-    def boom(db, storage, *, sqlite_path=None, db_url=None):
+    def boom(db, storage, *, sqlite_path=None, db_url=None, pg_password=None):
         raise RuntimeError("orchestrator died")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_slayer_otf_cache.py` around lines 332 - 333, The stub function
boom currently has signature boom(db, storage, *, sqlite_path=None, db_url=None)
but callers (ensure_db_cache/_phase1_ingest) may pass pg_password, causing a
TypeError; update boom to accept pg_password=None (e.g., boom(db, storage, *,
sqlite_path=None, db_url=None, pg_password=None)) and simply ignore it so the
stub remains compatible with Postgres benchmark calls.
🧹 Nitpick comments (2)
src/bird_interact_agents/db_connection.py (1)

150-170: ⚡ Quick win

Clarify Postgres read_only=False semantics: it preserves session state but doesn’t commit writes

PostgresDbConnection.execute/execute_sequence never call commit() when _read_only is False, so any non-TEMP changes will be rolled back when the connection is closed. That matches the only current make_db_connection(..., read_only=False) usage in tolerant_grader._multi_sql_execute, which explicitly relies on keeping TEMP/session state across multiple statements within the call (and expects writes not to persist beyond it).

Update docs/comments (and/or rename the parameter) to make clear that read_only=False means “don’t wrap each call in BEGIN/ROLLBACK for read-only dry-run” rather than “commit writes on success.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bird_interact_agents/db_connection.py` around lines 150 - 170, The
current semantics of PostgresDbConnection.execute / execute_sequence and the
_read_only flag are unclear: _read_only=False does not commit writes but only
avoids wrapping calls in BEGIN/ROLLBACK to preserve session/TEMP state for the
call. Update inline docstrings/comments for PostgresDbConnection.execute and
execute_sequence and the constructor/parameter description of make_db_connection
to state explicitly that _read_only=False preserves session state within the
connection but does not commit persistent changes (writes will be rolled back on
close), and mention tolerant_grader._multi_sql_execute relies on this behavior;
alternatively, rename the parameter to something clearer (e.g.,
preserve_session_state or transactional=False) across these symbols to reflect
the true semantics.
src/bird_interact_agents/cloud/ray_app.py (1)

113-113: 💤 Low value

Redundant import: get_benchmark is already imported at module level (line 27).

This in-function import can be removed since get_benchmark is now imported at the top of the module.

♻️ Suggested fix
 def download_benchmark_data(cfg: dict[str, Any], *, client=None) -> None:
     ...
     prefix = cfg.get("benchmark_data_prefix")
     if not prefix:
         return
-    from bird_interact_agents.benchmark import get_benchmark
-
     b = get_benchmark(_cloud_benchmark(cfg))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bird_interact_agents/cloud/ray_app.py` at line 113, Remove the redundant
in-function import of get_benchmark (the line "from
bird_interact_agents.benchmark import get_benchmark") since get_benchmark is
already imported at module level; locate the in-function import in ray_app.py
(where get_benchmark is imported again inside a function) and delete that line
so the function uses the module-level get_benchmark import.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@tests/test_slayer_otf_cache.py`:
- Around line 332-333: The stub function boom currently has signature boom(db,
storage, *, sqlite_path=None, db_url=None) but callers
(ensure_db_cache/_phase1_ingest) may pass pg_password, causing a TypeError;
update boom to accept pg_password=None (e.g., boom(db, storage, *,
sqlite_path=None, db_url=None, pg_password=None)) and simply ignore it so the
stub remains compatible with Postgres benchmark calls.

---

Nitpick comments:
In `@src/bird_interact_agents/cloud/ray_app.py`:
- Line 113: Remove the redundant in-function import of get_benchmark (the line
"from bird_interact_agents.benchmark import get_benchmark") since get_benchmark
is already imported at module level; locate the in-function import in ray_app.py
(where get_benchmark is imported again inside a function) and delete that line
so the function uses the module-level get_benchmark import.

In `@src/bird_interact_agents/db_connection.py`:
- Around line 150-170: The current semantics of PostgresDbConnection.execute /
execute_sequence and the _read_only flag are unclear: _read_only=False does not
commit writes but only avoids wrapping calls in BEGIN/ROLLBACK to preserve
session/TEMP state for the call. Update inline docstrings/comments for
PostgresDbConnection.execute and execute_sequence and the constructor/parameter
description of make_db_connection to state explicitly that _read_only=False
preserves session state within the connection but does not commit persistent
changes (writes will be rolled back on close), and mention
tolerant_grader._multi_sql_execute relies on this behavior; alternatively,
rename the parameter to something clearer (e.g., preserve_session_state or
transactional=False) across these symbols to reflect the true semantics.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c3841c04-7d66-426f-9e29-28876561852b

📥 Commits

Reviewing files that changed from the base of the PR and between f546ce9 and 689daed.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (30)
  • pyproject.toml
  • scripts/dev1515_strict_miss_diagnostics.py
  • src/bird_interact_agents/agents/_submit.py
  • src/bird_interact_agents/agents/claude_sdk_otf/agent.py
  • src/bird_interact_agents/agents/claude_sdk_otf_ainteract/agent.py
  • src/bird_interact_agents/agents/pydantic_ai_otf_encode/agent.py
  • src/bird_interact_agents/agents/pydantic_ai_recursive/agent.py
  • src/bird_interact_agents/cloud/driver.py
  • src/bird_interact_agents/cloud/ray_app.py
  • src/bird_interact_agents/db_connection.py
  • src/bird_interact_agents/eval/grade_in_place.py
  • src/bird_interact_agents/eval/regrade.py
  • src/bird_interact_agents/harness.py
  • src/bird_interact_agents/run.py
  • src/bird_interact_agents/slayer_otf/cache.py
  • src/bird_interact_agents/slayer_otf/reference_build.py
  • src/bird_interact_agents/slayer_otf/runtime.py
  • src/bird_interact_agents/slayer_pipeline/orchestrator.py
  • tests/test_benchmark_postgres.py
  • tests/test_db_connection.py
  • tests/test_harness_postgres_dispatch.py
  • tests/test_local_run_cascading.py
  • tests/test_one_shot_run.py
  • tests/test_otf_encode_reference_root.py
  • tests/test_otf_postgres.py
  • tests/test_recursive_runtime_db_root.py
  • tests/test_slayer_otf_cache.py
  • tests/test_slayer_otf_reference_build.py
  • tests/test_submit_classification.py
  • tests/test_tolerant_grader_postgres.py
✅ Files skipped from review due to trivial changes (1)
  • tests/test_otf_encode_reference_root.py
🚧 Files skipped from review as they are similar to previous changes (14)
  • src/bird_interact_agents/agents/claude_sdk_otf/agent.py
  • tests/test_one_shot_run.py
  • src/bird_interact_agents/slayer_otf/reference_build.py
  • tests/test_slayer_otf_reference_build.py
  • src/bird_interact_agents/cloud/driver.py
  • tests/test_local_run_cascading.py
  • src/bird_interact_agents/slayer_otf/runtime.py
  • src/bird_interact_agents/agents/pydantic_ai_recursive/agent.py
  • src/bird_interact_agents/run.py
  • tests/test_harness_postgres_dispatch.py
  • src/bird_interact_agents/agents/claude_sdk_otf_ainteract/agent.py
  • src/bird_interact_agents/eval/grade_in_place.py
  • src/bird_interact_agents/slayer_pipeline/orchestrator.py
  • src/bird_interact_agents/slayer_otf/cache.py

ZmeiGorynych and others added 4 commits June 3, 2026 15:28
…alse semantics

ray_app.py:download_benchmark_data imported get_benchmark a second time inside
the function body; the module-level import at line 27 is sufficient.

PostgresDbConnection docstring now explicitly states that read_only=False
preserves session state (TEMP tables) within the connection but never commits
writes — psycopg2 default autocommit=False means the implicit open transaction
is rolled back on close.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… jsonb_meaning_entries

_detect_jsonb_columns was extracted in this PR so tests can monkeypatch it
and verify that postgres builds skip JSONB detection.  But _phase3_jsonb was
still calling jsonb_meaning_entries directly, making the monkeypatch a no-op
and the test trivially passing regardless of the postgres early-return guard.

Now _phase3_jsonb calls _detect_jsonb_columns(meanings_path), so
test_phase3_skipped_for_postgres actually tests what it claims.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s_from_local_env

Both postgres benchmarks added in this PR have container_data_dir set,
making them cloud-submittable.  Without this fix, a cloud submit would
silently use the defaults (localhost:5432 / bird_interact) on the worker
node instead of the user's actual postgres server.

Both the OAuth (claude_sdk) and the standard (API key) branches now include
any BIRD_PG_* vars that are set locally; vars that are absent are not
forwarded (no empty-string keys that would shadow worker defaults).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cur.execute("ROLLBACK") on an aborted psycopg2 connection raises
InFailedSqlTransaction and silently fails (caught by the bare except).
connection.rollback() works in aborted-transaction state and correctly
resets the connection after any error during execute/execute_sequence.

Also add test_postgres_connection_read_only_error_uses_conn_rollback
which specifically exercises the error path, and update the two existing
tests that checked cursor-level ROLLBACK invocations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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