Skip to content

bird-agents: pre-copy template into per-task working sqlite (DEV-1509) - #13

Merged
ZmeiGorynych merged 1 commit into
mainfrom
egor/dev-1509-slayer-introspection-submission-table-name-casing-mismatch
May 30, 2026
Merged

ZmeiGorynych merged 1 commit into
mainfrom
egor/dev-1509-slayer-introspection-submission-table-name-casing-mismatch

Conversation

@ZmeiGorynych

Copy link
Copy Markdown
Member

Summary

  • Bug. Cloud-run agent saw OperationalError: no such table: artifactscore from the dry-run gate (agents/_submit.py::_dry_run_sql) and burned 40-100 turns rationalising it as a casing problem. The Linear issue (DEV-1509) framed it as a SLayer-introspection-vs-SQLite casing mismatch; investigation showed that framing is wrong.
  • Actual cause. harness.materialize_task_db only symlinked the template at <id>/<db>_template.sqlite and left the working <id>/<db>.sqlite uncreated. Anything that touched the path RW before the upstream's first reset_and_restore_database (most likely upstream get_db_connection's default-RW sqlite3.connect, which creates the file if missing) left an empty SQLite at db_file_path. The gate's sqlite3.connect("file:<db_file_path>?mode=ro", uri=True) then produced precisely no such table: <name> for every referenced table — empirically reproduced.
  • Fix. materialize_task_db now pre-copies the canonical template to the working <db>.sqlite via shutil.copy2 + os.replace (atomic). The per-task <db>_template.sqlite entry remains a SYMLINK (DEV-1462 storage invariant: 18 templates × N tasks would blow storage). The fast-path idempotence guard additionally checks the SQLite magic header (catches 0-byte AND foreign shapes like LFS pointers). Both the symlink and working-file installs use per-call uuid-suffixed tmp paths + os.replace, so two concurrent materialize_task_db calls on the same instance_id cannot collide.
  • Out of scope. The Linear issue's "third AC bullet" (a casing-regression test on ensure_db_cache) is intentionally skipped — slayer ingest already preserves SQLite-stored casing verbatim and SQLite identifier lookup is case-insensitive. A new test test_slayer_ingest_preserves_table_casing_and_sqlite_case_insensitive pins both invariants as documentation-via-code so future readers understand why the casing framing was rejected.

Tests

15 new tests in tests/test_db_isolation_pre_copy.py. All 9 existing tests/test_db_isolation.py tests still pass. Full non-integration suite: 1514 passed, 94 skipped.

Coverage map:

# Behavior pinned
1, 2 Working file present + byte-equal to template after materialize
3, 4 _dry_run_sql + sqlite3.connect(?mode=ro) succeed against the materialised file
5 Template entry remains a symlink
6 Idempotent fast-path takes no copy when intact
7-9 Rebuild when working file missing / 0-byte / LFS pointer
10, 11 Stale-symlink rebuilds (with and without db_file_path set)
12 Upstream reset_and_restore_database still works after pre-copy
13 Documentation-via-code: slayer ingest preserves casing AND SQLite is case-insensitive
14 Both symlink AND working-file installs use atomic tmp + os.replace
15 Mid-copy shutil.copy2 failure leaves expected_db_file untouched

Test plan

  • pytest tests/test_db_isolation_pre_copy.py tests/test_db_isolation.py -v — 24/24 pass
  • Full non-integration suite: env -u SSH_AUTH_SOCK uv run --extra all --extra dev --extra pydantic-ai pytest — 1514 passed
  • Manual cloud smoke (optional): rerun museum_5 / museum_7 with the same Opus/Sonnet config from 20260530t0958-claudes-slayer-b278f1 and confirm the casing rabbit hole does not reappear

🤖 Generated with Claude Code

The dry-run gate (agents/_submit.py::_dry_run_sql) opens the per-task
LiveSQLBench <db>.sqlite with ?mode=ro. Before this change,
materialize_task_db only symlinked the template and left the working
file uncreated — so anything that touched the path RW first (e.g.
upstream get_db_connection's default-RW sqlite3.connect) left an empty
SQLite there. The gate then returned OperationalError: no such table,
which the agent misdiagnosed as a casing problem and burned 40-100
turns on. Pre-copying the template (atomically via tmp + os.replace)
makes the invariant "when db_file_path is set, the file is a real
SQLite with the template's data" hold from the moment materialize_
task_db returns.

The template entry in the per-task dir stays a symlink (DEV-1462
storage invariant); only the WORKING <db>.sqlite is copied (one per
task instance). The fast-path idempotence guard now also requires the
SQLite magic header so 0-byte and foreign-shape (LFS pointer) files
trigger a rebuild. Both the symlink and working-file installs use
per-call uuid-suffixed tmp paths + os.replace so two concurrent
materialize_task_db calls on the same instance_id cannot collide.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@linear

linear Bot commented May 30, 2026

Copy link
Copy Markdown

DEV-1509

@coderabbitai

coderabbitai Bot commented May 30, 2026 •

Copy link
Copy Markdown
Contributor

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 54 minutes and 18 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: 683ac8f9-b1ec-4726-bc1a-6aa27c696ce6

📥 Commits

Reviewing files that changed from the base of the PR and between 0fb3ad5 and 9d4975f.

📒 Files selected for processing (2)
  • src/bird_interact_agents/harness.py
  • tests/test_db_isolation_pre_copy.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch egor/dev-1509-slayer-introspection-submission-table-name-casing-mismatch

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

@ZmeiGorynych

Copy link
Copy Markdown
Member Author

@coderabbitai review now!

@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor

@ZmeiGorynych Sure, I'll review the changes now!

✅ Actions performed

Full review triggered.

@ZmeiGorynych
ZmeiGorynych merged commit 342221f into main May 30, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant