Skip to content

feat(ingest): distinguish T3 app Codex sessions - #633

Merged
EtanHey merged 13 commits into
mainfrom
feat/d3-t3-provenance
Aug 4, 2026
Merged

feat(ingest): distinguish T3 app Codex sessions#633
EtanHey merged 13 commits into
mainfrom
feat/d3-t3-provenance

Conversation

@EtanHey

@EtanHey EtanHey commented Aug 1, 2026

Copy link
Copy Markdown
Owner

What broke and how it was found

T3-app-initiated Codex transcripts were indistinguishable from ordinary terminal Codex sessions: provenance_class LIKE %t3% returned 0. The explicit discriminator is provider_session_runtime.resume_cursor_json.threadId in the read-only T3 state DB.

Evidence

  • The canonical systems Codex session 019fb03f-0650-7f53-850b-921246951edc has 728 chunks and links to T3 thread 5ca576df-592c-406f-a8f1-7db9c56d36c9.
  • 29 linked Codex sessions were found; 1,228 genuine codex-session rows are tagged t3-app-session.
  • The non-collision fixture proves a plain Codex working on t3layer remains codex-session; schema drift raises an alarm.

Review

This branch was pair-reviewed by a separate agent under the taste gate: smallest correct solution, behavioural tests, and no speculative machinery.

Provenance-column warning

chunks.provenance_class currently serves both as a trust ladder and a source class, with independent writers and no precedence rule. This PR is correct, but enrichment can overwrite its source tag until that collision is resolved. See design collision.

Enrichment and drain are currently disabled with launchctl to stop that decay; re-enable deliberately only after the column question is settled.


Note

Medium Risk
Touches realtime ingestion and offset confirmation for Codex when T3 state is partial or alarming; mis-linkage or prolonged deferral could delay Codex indexing until T3 DB is healthy.

Overview
Adds a T3 runtime linkage path so Codex transcripts started from the T3 Code app are tagged t3-app-session instead of generic codex-session, using read-only queries of provider_session_runtime.resume_cursor_json.threadId in the T3 state DB (with alarms on missing/unreadable schema).

classify_provenance checks that linkage before recon signatures and ordinary Codex rules. The watcher flush loads linked session IDs once per batch; if linkage cannot be resolved, ambiguous Codex lines are deferred (not persisted, offsets not advanced) while other sources still ingest. BatchIndexer keeps deferred entries in the buffer and excludes them from confirmed watermarks.

A new retag_t3_app_provenance script dry-runs or applies batched updates for existing rows, with JSONL rollback and safe restore. The provenance classify report and tests are updated; unit tests isolate BRAINLAYER_T3_STATE_DB.

Reviewed by Cursor Bugbot for commit cdd0a74. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Distinguish T3 app-initiated Codex sessions with a t3-app-session provenance class

  • Adds t3_provenance.py to resolve Codex session IDs linked by the T3 runtime, reading from the provider_session_runtime table in a SQLite state DB.
  • Extends classify_provenance in agent_provenance.py to tag T3-linked Codex sessions as t3-app-session with KEEP policy, taking precedence over recon-agent signature rules.
  • Updates the watcher flush callback in watcher_bridge.py to defer Codex entries when T3 linkage is temporarily unavailable (e.g. DB alarm), preventing misclassification; entries resume on the next flush once linkage resolves.
  • Adds retag_t3_app_provenance.py, a CLI script to back-fill existing Codex chunks to t3-app-session with batched updates, rollback artifact generation, and a separate rollback command.
  • Risk: if the T3 state DB is unreachable, all Codex entries in a flush batch are deferred indefinitely until the DB becomes accessible again.

Macroscope summarized cdd0a74.

Summary by CodeRabbit

  • New Features

    • Added provenance classification for sessions initiated through the T3 application.
    • Added tools to identify, retag, validate, and roll back T3-linked session records with audit artifacts.
    • Added runtime linkage checks and alarm reporting for unavailable or invalid state data.
  • Bug Fixes

    • Deferred unresolved session links safely until they can be processed, preventing premature confirmation.
    • Improved batch failure isolation so unprocessed entries remain available for retry.
    • Preserved existing records when rollback or artifact replacement encounters errors.
  • Tests

    • Expanded coverage for linkage, ingestion, rollback, retry, and failure scenarios.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Your trial has ended. Reactivate Greptile to resume code reviews.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds T3-linked Codex session detection, provenance classification, deferred watcher handling, a retagging and rollback CLI, and tests. Two variable-name mismatches affect the report and watcher paths.

Changes

T3 app provenance

Layer / File(s) Summary
Runtime linkage discovery
src/brainlayer/t3_provenance.py, tests/test_t3_app_provenance.py
The new module parses Codex session IDs and reads linked IDs from validated T3 SQLite runtime state. Tests cover linkage, schema alarms, and precedence.
Ingestion classification and linkage handling
src/brainlayer/agent_provenance.py, src/brainlayer/watcher_bridge.py, src/brainlayer/watcher.py, tests/test_t3_app_provenance.py, tests/test_jsonl_watcher.py
Classification accepts T3 linkage and gives linked sessions T3_APP_SESSION precedence. Watcher flushes retain deferred entries and avoid confirming their watermarks. The unresolved-linkage branch references t3_linked_session_ids although it initializes linked_t3_session_ids.
Report linkage integration
scripts/provenance_classify_report.py, tests/test_agent_provenance.py, tests/conftest.py
The report resolves the configured T3 state database and loads linked IDs once. Its classification call references the undefined name t3_linked_session_ids.
Retagging and rollback operations
scripts/retag_t3_app_provenance.py, tests/test_retag_t3_app_provenance.py
The CLI discovers linked Codex chunks, applies conditional batched updates, writes durable JSONL rollback artifacts, and restores values with concurrency checks.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Watcher
  participant T3StateDB
  participant ProvenanceClassifier
  participant BrainLayerDB
  Watcher->>T3StateDB: load linked Codex session IDs
  T3StateDB-->>Watcher: linked IDs or linkage alarm
  Watcher->>ProvenanceClassifier: classify message with linked IDs
  ProvenanceClassifier-->>Watcher: T3 or existing provenance
  Watcher->>BrainLayerDB: persist confirmed message
Loading

Poem

I’m a rabbit with a tidy trail,
T3 links now mark each tale.
Chunks retag, then roll back right,
Artifacts guard the data tight.
Two names mismatch in flight—
Fix the paths before midnight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.68% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: distinguishing T3 app Codex sessions during ingestion.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/d3-t3-provenance

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Comment thread scripts/retag_t3_app_provenance.py Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 20e0108. Configure here.

Comment thread src/brainlayer/t3_provenance.py

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 20e0108fa0

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread scripts/retag_t3_app_provenance.py Outdated
for batch_start in range(0, len(candidates), batch_size):
batch = candidates[batch_start : batch_start + batch_size]
connection.executemany(
"UPDATE chunks SET provenance_class = ? WHERE id = ?",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Guard retags with the selected provenance

When the watcher or enrichment changes a candidate after _candidates() reads it but before this batch executes, the unconditional update overwrites that newer provenance with t3-app-session. The busy timeout only serializes individual SQLite statements, while each batch commit leaves a race across the scan-to-update interval; add the expected codex-session value to the WHERE clause or hold the repository's writer lock for the operation.

AGENTS.md reference: AGENTS.md:L28-L30

Useful? React with 👍 / 👎.

Comment thread scripts/retag_t3_app_provenance.py Outdated
else {}
)
existing.update(dict(candidates))
with path.open("w", encoding="utf-8") as artifact:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve existing rollback artifacts atomically

When --apply is rerun with an existing artifact, this "w" open truncates the only rollback record before the preserved entries are rewritten. A crash, interruption, or ENOSPC during that rewrite can therefore destroy recovery data for rows changed by the earlier run even though the merge behavior explicitly supports artifact reuse; write a temporary file and atomically replace the artifact instead.

AGENTS.md reference: AGENTS.md:L5-L8

Useful? React with 👍 / 👎.

Comment thread scripts/retag_t3_app_provenance.py Outdated
pre_restore_path.parent.mkdir(parents=True, exist_ok=True)
with pre_restore_path.open("x", encoding="utf-8") as artifact:
for chunk_id, _ in candidates:
artifact.write(json.dumps({"id": chunk_id, "provenance_class": current_values[chunk_id]}) + "\n")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle deleted chunks in pre-restore snapshots

If any chunk recorded in an older rollback artifact has since been deleted, current_values has no entry for that ID and this lookup raises KeyError. Because the pre-restore file has already been opened with "x", the failed rollback also leaves an empty or partial artifact that blocks retry, so later rollback of the remaining rows cannot proceed without manual cleanup; skip absent IDs or represent their absence explicitly.

AGENTS.md reference: AGENTS.md:L5-L8

Useful? React with 👍 / 👎.

@EtanHey

EtanHey commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

VERDICT: ITERATE
TASTE: The runtime-link discriminator is direct and appropriately small (20e0108f:src/brainlayer/t3_provenance.py:37-45, 20e0108f:src/brainlayer/t3_provenance.py:48-86). The surrounding hot-path lookup and rollback framework are working-but-scenic: classification reopens and rescans the live T3 DB once per emitted chunk (20e0108f:src/brainlayer/watcher_bridge.py:376-422), while the added recovery machinery is not atomic or concurrency-safe. CodeRabbit status: SKIPPED — rate-limited; manual red-team/blue-team fallback performed.
TESTS: Clean baseline/final: 18 passed across test_t3_app_provenance.py, test_retag_t3_app_provenance.py, and test_agent_provenance.py; Ruff check/format passed. Mutation 1 disabled the T3 precedence return and 4/6 T3 feature tests died. Mutation 2 removed the exact codex-session candidate guard and the retag test died (3 candidates/retags instead of 1). Mutation 3 disabled only_null_prior_values and the rollback test died (2 restored instead of 1). Scratch reproductions additionally measured 5 T3 DB opens for 5 classifications, both retag and rollback overwriting a newer ladder value, a missing rollback row raising KeyError and leaving a zero-byte pre-restore artifact, and a simulated write failure truncating a reused rollback artifact to zero bytes. The GitHub Python 3.11 job fails during collection in untouched MCP code (Server.list_tools); this diff does not touch that path.
MISSED BY FIRST REVIEW: The apply-side race was noted by automated review, but rollback has the same unguarded clobber: UPDATE chunks SET provenance_class = ? WHERE id = ? overwrites enrichment changes made after retagging (20e0108f:scripts/retag_t3_app_provenance.py:153-159). Under the known two-writer collision, this is not safe. Also confirmed the post-review findings for per-chunk DB scans, non-atomic artifact reuse, and deleted-row rollback failure.
BLOCKERS: 1) Load linked T3 session IDs once per watcher flush and pass/reuse that set instead of opening/scanning the DB per chunk. 2) Guard retag updates with expected codex-session and rollback updates with expected t3-app-session, and report actual matched/changed rows (20e0108f:scripts/retag_t3_app_provenance.py:91-101, 20e0108f:scripts/retag_t3_app_provenance.py:153-163). 3) Write rollback and pre-restore artifacts through fsynced temporary files plus atomic replace; skip/report missing IDs without leaving an empty artifact that blocks retry (20e0108f:scripts/retag_t3_app_provenance.py:37-52, 20e0108f:scripts/retag_t3_app_provenance.py:126-152).

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Your trial has ended. Reactivate Greptile to resume code reviews.

@cursor

cursor Bot commented Aug 1, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_c56bff01-49fd-4531-a378-51d94e69a48a)

@EtanHey

EtanHey commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@cursor @BugBot review

@cursor

cursor Bot commented Aug 1, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_4d748502-2014-4a2e-9860-6aa39a776b64)

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Your trial has ended. Reactivate Greptile to resume code reviews.

@EtanHey

EtanHey commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

@codex review

CI compatibility follow-up: constrain MCP to the supported 1.x API range after MCP 2.0 removed Server.list_tools.

@cursor

cursor Bot commented Aug 1, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_5db5e816-8ed7-4cde-a982-9766c34634e5)

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Your trial has ended. Reactivate Greptile to resume code reviews.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 17e293b630

ℹ️ About Codex in GitHub

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

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

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

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

Comment on lines +150 to +153
is_t3_app_initiated_codex_session(
source_file,
state_db=t3_state_db,
linked_session_ids=t3_linked_session_ids,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Cache T3 linkage for the report scan

When a T3 state DB exists, scripts/provenance_classify_report.py:32-37 calls classify_provenance once for every chunk without supplying t3_linked_session_ids, so every Codex chunk reaching this branch reopens and rescans provider_session_runtime. Since a single session can contain hundreds of chunks, the canonical report now performs O(C×R) SQLite work and may classify one session inconsistently if T3 state changes mid-scan; load the linked IDs once in build_report and pass that set to each classification.

Useful? React with 👍 / 👎.

@cursor

cursor Bot commented Aug 2, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_4966663d-fa30-4043-b672-dc9ec824ebf9)

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Your trial has ended. Reactivate Greptile to resume code reviews.

@EtanHey

EtanHey commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Routed by @bl-cleanup (lead). You did not write this; the author is not reviewing it. This PR was quarantined as CONFLICTING and is now MERGEABLE at aeaa5f01.

The pin question is settled — do not re-litigate it, but do verify the reasoning holds:

  • main = mcp>=2.0.0,<3.0.0; this branch previously had mcp>=1.0.0,<2.
  • The branch's own commit 17e293b6 is titled "fix: cap MCP SDK below breaking v2" — a deliberate cap, which makes it look like the considered decision. It is not.
  • Timeline: cap 17e293b6 at 2026-08-01 15:11 UTC; migration 8c42619e (PR fix: migrate MCP server to SDK v2 #636, "migrate MCP server to SDK v2") at 19:59 UTC, merged 20:12 UTC. The cap came ~5h before the migration and was superseded by it. Reverting to the cap would undo fix: migrate MCP server to SDK v2 #636 and reintroduce the API drift it fixed.

Where the real risk is — not the pin: this branch predates the SDK v2 migration entirely, so any MCP SDK call site it touches could still be v1. The worker reports no v1 call sites survived; please verify that independently rather than accept it.

Also check: this branch retags Codex sessions as t3-app-session in provenance_class — the column being split in lane R1-A because it has two writers and no precedence. That collision is explicitly out of scope here; D3 should land on the existing column. Flag scope creep, don't fix it.

Full gate on the branch: 3680 passed, 9 skipped, 1 xfailed in 9m09s, plus Ruff/format/compile clean.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aeaa5f01b6

ℹ️ About Codex in GitHub

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

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

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

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

Comment on lines +79 to +80
resume_cursor = json.loads(resume_cursor_json)
except (TypeError, json.JSONDecodeError) as exc:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Treat nullable T3 cursors as unlinked

When a Codex runtime exists before it has a resume cursor, resume_cursor_json can legitimately be NULL (the T3 schema leaves it nullable, and src/brainlayer/ingest/t3.py:280-286 already treats an absent cursor as no provider session). Passing that value to json.loads raises the fatal t3_runtime_linkage_invalid alarm; because watcher_bridge.py:352-353 loads all linked IDs before every flush, one such runtime prevents ingestion of every source, not merely the unlinked T3 session. Skip null/empty cursors while retaining the alarm for genuinely malformed non-empty JSON.

AGENTS.md reference: AGENTS.md:L5-L8

Useful? React with 👍 / 👎.

@EtanHey

EtanHey commented Aug 3, 2026

Copy link
Copy Markdown
Owner Author

Routed independent review — status: ITERATE resolved locally, awaiting push

Posting the routed verdict here so it is visible on the PR rather than only on the sprint board. This is not a GitHub approval — every fleet agent authenticates as EtanHey, who authors this PR, and GitHub refuses self-review. The approving review must come from a human.

Reviewer: @review-633 — independent, did not author this branch.
Fix HEAD (local, unpushed): 3fefbcb8e5420961bec0e4e1127e644a2377d35c
Fix diff SHA-256 (vs pushed aeaa5f01): 973bb208ca7de3c579b8e21ee813ffcebecef38e37210069e50439cee8f3c1a4

⚠️ The pushed head aeaa5f01 still contains the defect. The fix exists only locally because the repo-wide pre-push gate was broken all of 08-02 (now fixed in #640, which must merge first).

The blocking finding — F1

BrainLayerAlarm subclasses BaseException, so it escapes _do_flush's except Exception (watcher.py:899). watcher_bridge.py:353 calls t3_app_codex_session_ids() unconditionally at the top of every flush, before any entry is inspected — so on T3 schema drift it halts ingestion for every source, not just T3 classification. Plain ~/.claude sessions stop ingesting because a Codex-provenance helper disliked a third-party table.

And the trigger is wider than a t3layer release. The guard is if t3_state_db.exists() — file present, not usable. All three of these pass it and then kill the flush: T3 installed but table not yet created; zero-byte state.sqlite from an interrupted first run; older schema without resume_cursor_json.

Local fix 3fefbcb8 wraps the call site in except BrainLayerAlarm — narrowing the blast radius without weakening the alarm elsewhere, which is the right shape.

What the reviewer verified independently, not on claim

  • The pin question is settled and this PR does not touch it. git diff origin/main...HEAD --stat -- pyproject.tomlempty.
  • "No v1 MCP SDK call sites survived" — confirmed by four checks plus a live JSON-RPC probe against the built server (mcp 2.0.0): initialize → OK, tools/list → 5 tools, brain_search → dispatched.
  • TDD red-green confirmed behaviourally — removed only the T3 branch, kept imports and kwargs, → 5 failed.
  • §6 DB-copy migration gate: GREEN. 5,500-row fixture deliberately >1 batch; 5,200 retagged; 50 NULL rows untouched; rollback restored the distribution byte-identically. So "no NULL is overwritten" is an observed outcome, not a reading of SQL predicates. Production opened read-only; zero production writes — I verified the t3 distribution is unchanged from my 13:00 baseline.

Out of scope, deliberately

The provenance_class two-writer collision is lane R1-A. D3 lands on the existing column and its tags will be overwritten by enrichment later — expected, not a reason to change D3.

@cursor

cursor Bot commented Aug 3, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_ffdb6cca-9c6e-467b-970c-77d662b38e48)

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Your trial has ended. Reactivate Greptile to resume code reviews.

policies: Counter[str] = Counter()
visibility: Counter[str] = Counter()
total = 0
t3_state_db = Path(os.environ.get("BRAINLAYER_T3_STATE_DB", DEFAULT_T3_STATE_DB)).expanduser()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 High scripts/provenance_classify_report.py:32

build_report uses os.environ.get("BRAINLAYER_T3_STATE_DB", DEFAULT_T3_STATE_DB), so when the env var is set but empty, the value is Path('') — the current directory. That path exists, so line 33 calls t3_app_codex_session_ids on it, which fails to open a directory as SQLite and raises a fatal alarm. This diverges from is_t3_app_initiated_codex_session, which guards the same lookup with or DEFAULT_T3_STATE_DB. An empty-but-present env var therefore crashes the report instead of falling back to the default DB.

-    t3_state_db = Path(os.environ.get("BRAINLAYER_T3_STATE_DB", DEFAULT_T3_STATE_DB)).expanduser()
+    t3_state_db = Path(os.environ.get("BRAINLAYER_T3_STATE_DB") or DEFAULT_T3_STATE_DB).expanduser()
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @scripts/provenance_classify_report.py around line 32:

`build_report` uses `os.environ.get("BRAINLAYER_T3_STATE_DB", DEFAULT_T3_STATE_DB)`, so when the env var is set but empty, the value is `Path('')` — the current directory. That path exists, so line 33 calls `t3_app_codex_session_ids` on it, which fails to open a directory as SQLite and raises a fatal alarm. This diverges from `is_t3_app_initiated_codex_session`, which guards the same lookup with `or DEFAULT_T3_STATE_DB`. An empty-but-present env var therefore crashes the report instead of falling back to the default DB.

Comment thread src/brainlayer/watcher_bridge.py

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3fefbcb8e5

ℹ️ About Codex in GitHub

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

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

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

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

Comment on lines +356 to +357
except BrainLayerAlarm:
linked_t3_session_ids = set()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Defer Codex writes after a T3 lookup alarm

When the T3 state database is temporarily unreadable, malformed, or still being initialized, this new handler catches the alarm and substitutes an empty linkage set. Every Codex entry in that flush is then persisted as codex-session and its source offset is confirmed, so it is never retried after the T3 state recovers; continue processing non-Codex entries, but defer Codex entries whose provenance cannot be determined. The fresh evidence in this commit is the new alarm catch that converts the previously reported failure into a permanent misclassification.

AGENTS.md reference: AGENTS.md:L5-L8

Useful? React with 👍 / 👎.

Choose recovery shape (a): when the once-per-flush T3 linkage lookup alarms, source-only provenance classification defers Codex entries without persisting or confirming their offsets, while non-Codex entries in the same flush continue normally. A later pass after runtime repair can then apply T3 precedence. This avoids adding an unresolved schema marker or widening the retag migration over legitimate provenance classes.

Add a real temp-queue regression with recon-signature Codex content that forces the alarm, proves plain Claude persistence and confirmation remain live, then proves repaired linkage replays as t3-app-session.
@cursor

cursor Bot commented Aug 3, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_965c4af6-b60a-401d-bbb8-d696e241832e)

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Your trial has ended. Reactivate Greptile to resume code reviews.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 47bb3a3787

ℹ️ About Codex in GitHub

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

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

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

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

source_file,
base_content_class,
content=clean_content,
t3_linked_session_ids=linked_t3_session_ids,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve T3 provenance through deduplication

When a T3-linked transcript contains text that exact- or near-duplicates an existing ordinary Codex chunk, this classification is discarded: the direct path passes an incoming dict without provenance_class to merge_duplicate_chunk (watcher_bridge.py:554-581), while the arbitrated path reaches _insert_or_merge_chunk, whose duplicate merge also leaves the canonical provenance unchanged (drain.py:348-377). The surviving row therefore remains codex-session with the earlier source file, and the retag migration cannot recover it because that source points to the unlinked session; either prevent cross-provenance deduplication or preserve both origins when merging.

AGENTS.md reference: AGENTS.md:L5-L8

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🤖 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 `@scripts/retag_t3_app_provenance.py`:
- Around line 98-123: Update the apply flow around the retagging operation to
require enrichment workers to be stopped before bulk updates, either by adding
the established preflight check or documenting the operational requirement.
Preserve the existing PRAGMA wal_checkpoint(FULL) calls before and after the
batch updates, and verify whether an FTS trigger on chunks causes reindexing so
the migration’s operational cost is accounted for.
- Around line 30-36: Remove the contradictory provenance_class check in the
_candidates comprehension, retaining only the condition that expresses which
records should be selected. If PROVENANCE_RANK is intended to gate replacement,
compare the rank of T3_APP_SESSION with the existing provenance_class rank
instead, ensuring future additions to PROVENANCE_RANK do not make --apply
silently return no candidates.
- Around line 217-234: Update main’s argument validation before the
rollback/apply branch so --rollback-artifact is required whenever the command
runs, including with --apply, and report the omission through parser.error
instead of allowing retag_t3_app_chunks to raise ValueError. Preserve the
existing rollback validation behavior and normal execution when the argument is
provided.
- Around line 158-170: Update the current_values lookup in the rollback flow to
query candidates in chunks rather than binding every candidate in one SELECT
statement. Use a chunk size capped at 900 (or otherwise below the lowest
supported SQLite parameter limit), execute the existing query for each chunk,
and merge all returned rows into current_values while preserving the
empty-candidates behavior.

In `@src/brainlayer/agent_provenance.py`:
- Around line 149-157: Update the watcher classification flow around
classify_provenance in watcher_bridge.py to explicitly catch BrainLayerAlarm,
which inherits from BaseException, when T3 linkage resolution performs SQLite
I/O. Ensure T3 schema or connection failures are handled by the existing watcher
error path, or preload t3_linked_session_ids before classification so those
failures cannot escape.

In `@src/brainlayer/t3_provenance.py`:
- Around line 56-62: The read-only connection setup must handle live WAL
databases without making every linkage attempt unavailable. In the T3
database-opening flow around sqlite3.connect, verify the actual journal mode and
either read a temporary database copy when WAL is used or add an immutable=1
fallback; when both approaches fail, treat the result as no linkage instead of
raising t3_runtime_unavailable via raise_alarm.

In `@src/brainlayer/watcher_bridge.py`:
- Around line 410-414: In the watcher processing flow around
t3_linkage_resolved, track deferred Codex-provenance entries separately from
ordinary skipped entries, incrementing the counter before continuing. Update
emit_watcher_flush to report the deferred count and add one per-flush log
message so repeated deferrals remain observable without logging each entry.

In `@tests/test_agent_provenance.py`:
- Around line 286-291: Update the monkeypatch in the provenance report test to
use the default raising=True behavior by removing raising=False. After
build_report, assert the expected tag distribution in report so the test
verifies the loaded session-ID set is forwarded to classify_provenance, while
retaining the existing total_chunks and calls assertions.

In `@tests/test_retag_t3_app_provenance.py`:
- Around line 224-230: Update the test around _write_rollback_artifact to patch
a module-local replace symbol rather than retag_module.os.replace, preventing
process-wide os.replace mutation; expose os.replace as a module-level imported
name in scripts.retag_t3_app_provenance and have the implementation use it. Also
remove the unused **kwargs from the wrapper near line 179, since
_atomic_write_jsonl accepts only positional parameters.

In `@tests/test_t3_app_provenance.py`:
- Around line 14-38: Update the SQLite connection usage in _state_db and the
analogous connection blocks at the referenced locations to wrap sqlite3.connect
with contextlib.closing, while retaining the transaction context manager for
commit behavior. Follow the existing closing pattern already used nearby so
every connection is explicitly closed.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 530e9e92-9a32-4664-b25c-543bd5f6f9e7

📥 Commits

Reviewing files that changed from the base of the PR and between c99fabb and 47bb3a3.

📒 Files selected for processing (9)
  • scripts/provenance_classify_report.py
  • scripts/retag_t3_app_provenance.py
  • src/brainlayer/agent_provenance.py
  • src/brainlayer/t3_provenance.py
  • src/brainlayer/watcher_bridge.py
  • tests/conftest.py
  • tests/test_agent_provenance.py
  • tests/test_retag_t3_app_provenance.py
  • tests/test_t3_app_provenance.py
📜 Review details
⏰ Context from checks skipped due to timeout. (3)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.12)
🧰 Additional context used
📓 Path-based instructions (2)
src/brainlayer/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/brainlayer/**/*.py: Treat retrieval correctness, write safety, and MCP stability as critical-path concerns; prioritize regressions in search quality, lock handling, and tool contracts over style refactors.
Use paths.py:get_db_path() for database path resolution rather than hardcoding paths; the canonical database is ~/.local/share/brainlayer/brainlayer.db.
Ensure concurrent database access retries SQLITE_BUSY, uses one connection per worker, permits one writer at a time, and does not run brain_digest concurrently with other MCP work.
Run secret scrubbing in watcher_bridge.py before chunk persistence; redact provider-prefixed and labeled high-entropy secrets and record unlabeled high-entropy tokens in quarantine metadata.
Preserve ai_code, stack_trace, and user_message verbatim; skip noise, summarize build logs, retain only structure for directory listings, use AST-aware tree-sitter chunking, never split stack traces, and mask large tool output.
Do not change the source denylist semantics: provider sessions and ordinary Claude subagents ingest by default; only exact brain-worker subagents and wf_* workflow paths are excluded, without deleting source JSONL.
For bulk database operations, stop enrichment workers, run PRAGMA wal_checkpoint(FULL) before and after, drop and recreate FTS triggers around large deletes, batch deletes in 5–10K chunks, checkpoint every three batches, and never delete large datasets while the FTS delete trigger is active.
Respect chunk lifecycle fields superseded_by, aggregated_into, and archived_at; exclude lifecycle-managed chunks from default search, support include_archived=True, use safety gates for personal data in brain_supersede, and implement brain_archive as a timestamped soft delete.
Use Groq as the primary enrichment backend, Gemini as fallback, and Ollama as the offline last resort; honor BRAINLAYER_ENRICH_BACKEND and BRAINLAYER_ENRICH_RATE.
The real-time watcher must persist offsets, dete...

Files:

  • src/brainlayer/agent_provenance.py
  • src/brainlayer/t3_provenance.py
  • src/brainlayer/watcher_bridge.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Run pytest before claiming behavior changes are safe; avoid running real-production-DB tests tests/test_vector_store.py and tests/test_engine.py as part of worker pre-push checks unless deliberately performing production-DB checks.

Files:

  • tests/conftest.py
  • tests/test_agent_provenance.py
  • tests/test_t3_app_provenance.py
  • tests/test_retag_t3_app_provenance.py
🪛 ast-grep (0.45.0)
src/brainlayer/t3_provenance.py

[warning] 20-20: XPath query is request-/variable-derived; use parameterized XPath to prevent injection.
Context: _CODEX_SESSION_ID_RE.findall(Path(source_file).stem)
Note: [CWE-643] Improper Neutralization of Data within XPath Expressions ('XPath Injection').

(xpath-injection-python)

scripts/retag_t3_app_provenance.py

[info] 46-46: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"id": chunk_id, "provenance_class": provenance_class})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 234-234: use jsonify instead of json.dumps for JSON output
Context: json.dumps(report, sort_keys=True)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

tests/test_t3_app_provenance.py

[info] 47-47: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"threadId": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 62-62: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"threadId": session_id})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 74-74: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"threadId": session_id})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 90-90: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"threadId": session_id})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 119-119: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"threadId": session_id})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 253-253: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"threadId": session_id})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 271-271: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"threadId": session_id})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

tests/test_retag_t3_app_provenance.py

[info] 23-23: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"threadId": session_id})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 104-104: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"id": "prior-null", "provenance_class": None})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 105-105: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"id": "prior-codex", "provenance_class": "codex-session"})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 175-175: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"id": "app-1", "provenance_class": "codex-session"})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 203-203: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"id": "deleted", "provenance_class": "codex-session"})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 220-220: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"id": "old", "provenance_class": "codex-session"})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🔇 Additional comments (10)
src/brainlayer/t3_provenance.py (2)

19-48: LGTM!


55-97: 🩺 Stability & Availability

No change needed. raise_alarm() is annotated with typing.NoReturn, so the surrounding control flow is provable.

tests/test_t3_app_provenance.py (1)

186-265: LGTM!

src/brainlayer/agent_provenance.py (1)

142-143: The line-range change details state that src/brainlayer/watcher_bridge.py and scripts/provenance_classify_report.py reference an "undefined name t3_linked_session_ids". That is incorrect. t3_linked_session_ids is the keyword parameter declared here at line 143. Both call sites pass it as a keyword argument and assign the locally defined linked_t3_session_ids to it. No name error exists.

src/brainlayer/watcher_bridge.py (2)

353-359: LGTM!


515-520: LGTM!

scripts/provenance_classify_report.py (1)

8-8: LGTM!

Also applies to: 22-22, 32-43

tests/conftest.py (1)

90-93: LGTM!

scripts/retag_t3_app_provenance.py (2)

39-73: LGTM!


113-124: 🗄️ Data Integrity & Integration

No change needed.

This script targets Python 3.11, where sqlite3.Cursor.rowcount after executemany reports the total affected rows for these UPDATE operations.

			> Likely an incorrect or invalid review comment.

Comment on lines +30 to +36
return [
(chunk_id, provenance_class)
for chunk_id, source_file, provenance_class in rows
if codex_session_id_from_source(source_file) in linked_session_ids
and provenance_class == "codex-session"
and provenance_class not in PROVENANCE_RANK
]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Remove the contradictory candidate condition.

Lines 34 and 35 test the same value against two conditions that can both hold only when "codex-session" is absent from PROVENANCE_RANK. Today it is absent, so line 35 is always true and dead. If a future change adds "codex-session" to PROVENANCE_RANK, _candidates returns an empty list, and --apply becomes a silent no-op that reports success.

Keep one condition and state the intent explicitly.

♻️ Proposed change
     return [
         (chunk_id, provenance_class)
         for chunk_id, source_file, provenance_class in rows
-        if codex_session_id_from_source(source_file) in linked_session_ids
-        and provenance_class == "codex-session"
-        and provenance_class not in PROVENANCE_RANK
+        # Only the neutral "codex-session" tag is safe to replace; any ranked or
+        # manually assigned provenance is left untouched.
+        if provenance_class == "codex-session"
+        and codex_session_id_from_source(source_file) in linked_session_ids
     ]

If PROVENANCE_RANK is meant to gate the replacement instead, compare the rank of T3_APP_SESSION against the rank of the existing value.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return [
(chunk_id, provenance_class)
for chunk_id, source_file, provenance_class in rows
if codex_session_id_from_source(source_file) in linked_session_ids
and provenance_class == "codex-session"
and provenance_class not in PROVENANCE_RANK
]
return [
(chunk_id, provenance_class)
for chunk_id, source_file, provenance_class in rows
# Only the neutral "codex-session" tag is safe to replace; any ranked or
# manually assigned provenance is left untouched.
if provenance_class == "codex-session"
and codex_session_id_from_source(source_file) in linked_session_ids
]
🤖 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/retag_t3_app_provenance.py` around lines 30 - 36, Remove the
contradictory provenance_class check in the _candidates comprehension, retaining
only the condition that expresses which records should be selected. If
PROVENANCE_RANK is intended to gate replacement, compare the rank of
T3_APP_SESSION with the existing provenance_class rank instead, ensuring future
additions to PROVENANCE_RANK do not make --apply silently return no candidates.

Comment on lines +98 to +123
if apply:
connection.execute("PRAGMA busy_timeout = 30000")
candidates = _candidates(connection, linked_session_ids)
report = {
"linked_sessions": len(linked_session_ids),
"candidate_chunks": len(candidates),
"matched_chunks": 0,
"retagged_chunks": 0,
}
if not apply:
return report

artifact_path = Path(rollback_artifact).expanduser()
_write_rollback_artifact(artifact_path, candidates)
connection.execute("PRAGMA wal_checkpoint(FULL)")
for batch_start in range(0, len(candidates), batch_size):
batch = candidates[batch_start : batch_start + batch_size]
cursor = connection.executemany(
"UPDATE chunks SET provenance_class = ? WHERE id = ? AND provenance_class = ?",
[(T3_APP_SESSION, chunk_id, "codex-session") for chunk_id, _ in batch],
)
report["matched_chunks"] += cursor.rowcount
connection.commit()
if ((batch_start // batch_size) + 1) % 3 == 0:
connection.execute("PRAGMA wal_checkpoint(FULL)")
connection.execute("PRAGMA wal_checkpoint(FULL)")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Operational note: enrichment workers and FTS triggers during the retag.

The function updates chunks.provenance_class in batches and checkpoints the WAL. It does not stop enrichment workers first. The PR objectives state that chunks.provenance_class has multiple writers and no precedence rule, so an enrichment pass that runs during or after the retag can overwrite the T3 tag. The guarded UPDATE ... AND provenance_class = 'codex-session' protects the write itself, but it does not protect the result afterwards.

Two recommendations:

  1. Document that operators must stop enrichment workers before --apply and restart them after, or add a preflight check that fails when workers are running.
  2. Confirm whether an FTS update trigger exists on chunks. If it does, each batch reindexes every touched row, which changes the cost profile of the migration.

Do you want me to open an issue to track a precedence rule for chunks.provenance_class writers?

As per coding guidelines: "For bulk database operations, stop enrichment workers, run PRAGMA wal_checkpoint(FULL) before and after".

🤖 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/retag_t3_app_provenance.py` around lines 98 - 123, Update the apply
flow around the retagging operation to require enrichment workers to be stopped
before bulk updates, either by adding the established preflight check or
documenting the operational requirement. Preserve the existing PRAGMA
wal_checkpoint(FULL) calls before and after the batch updates, and verify
whether an FTS trigger on chunks causes reindexing so the migration’s
operational cost is accounted for.

Source: Coding guidelines

Comment on lines +158 to +170
current_values = (
{
chunk_id: provenance_class
for chunk_id, provenance_class in connection.execute(
"SELECT id, provenance_class FROM chunks WHERE id IN ({})".format(
", ".join("?" for _ in candidates)
),
[chunk_id for chunk_id, _ in candidates],
)
}
if candidates
else {}
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

The rollback lookup exceeds the SQLite parameter limit.

Lines 162-165 create one ? placeholder for every candidate in the artifact. SQLite limits bound parameters per statement through SQLITE_MAX_VARIABLE_NUMBER: 999 before SQLite 3.32 and 32766 from 3.32. A rollback artifact produced by a real retag can hold more rows than that. The statement then raises sqlite3.OperationalError: too many SQL variables, and the entire rollback fails before any row is restored. batch_size does not apply to this lookup.

Read the current values in chunks of batch_size.

🐛 Proposed fix
-        current_values = (
-            {
-                chunk_id: provenance_class
-                for chunk_id, provenance_class in connection.execute(
-                    "SELECT id, provenance_class FROM chunks WHERE id IN ({})".format(
-                        ", ".join("?" for _ in candidates)
-                    ),
-                    [chunk_id for chunk_id, _ in candidates],
-                )
-            }
-            if candidates
-            else {}
-        )
+        current_values: dict[str, str | None] = {}
+        candidate_ids = [chunk_id for chunk_id, _ in candidates]
+        for lookup_start in range(0, len(candidate_ids), batch_size):
+            lookup_ids = candidate_ids[lookup_start : lookup_start + batch_size]
+            placeholders = ", ".join("?" for _ in lookup_ids)
+            current_values.update(
+                dict(
+                    connection.execute(
+                        f"SELECT id, provenance_class FROM chunks WHERE id IN ({placeholders})",
+                        lookup_ids,
+                    )
+                )
+            )

Note that batch_size defaults to 5000, which still exceeds the 999 limit on SQLite builds before 3.32. Cap the lookup chunk at 900 if the target SQLite version is not guaranteed.

🤖 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/retag_t3_app_provenance.py` around lines 158 - 170, Update the
current_values lookup in the rollback flow to query candidates in chunks rather
than binding every candidate in one SELECT statement. Use a chunk size capped at
900 (or otherwise below the lowest supported SQLite parameter limit), execute
the existing query for each chunk, and merge all returned rows into
current_values while preserving the empty-candidates behavior.

Comment on lines +217 to +234
if args.rollback:
if args.rollback_artifact is None:
parser.error("--rollback-artifact is required with --rollback")
report: dict[str, Any] = rollback_t3_app_chunks(
db_path=args.db_path,
rollback_artifact=args.rollback_artifact,
only_null_prior_values=args.only_null_prior_values,
pre_restore_artifact=args.pre_restore_artifact,
batch_size=args.batch_size,
)
else:
report = retag_t3_app_chunks(
db_path=args.db_path,
state_db=args.state_db,
apply=args.apply,
rollback_artifact=args.rollback_artifact,
batch_size=args.batch_size,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Report the missing --rollback-artifact as a usage error.

Line 219 uses parser.error for the rollback path. The apply path does not. If a user runs --apply without --rollback-artifact, retag_t3_app_chunks raises ValueError at line 88 and the user sees a traceback. Validate the argument in main so both paths behave the same.

♻️ Proposed change
     else:
+        if args.apply and args.rollback_artifact is None:
+            parser.error("--rollback-artifact is required with --apply")
         report = retag_t3_app_chunks(
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if args.rollback:
if args.rollback_artifact is None:
parser.error("--rollback-artifact is required with --rollback")
report: dict[str, Any] = rollback_t3_app_chunks(
db_path=args.db_path,
rollback_artifact=args.rollback_artifact,
only_null_prior_values=args.only_null_prior_values,
pre_restore_artifact=args.pre_restore_artifact,
batch_size=args.batch_size,
)
else:
report = retag_t3_app_chunks(
db_path=args.db_path,
state_db=args.state_db,
apply=args.apply,
rollback_artifact=args.rollback_artifact,
batch_size=args.batch_size,
)
if args.rollback:
if args.rollback_artifact is None:
parser.error("--rollback-artifact is required with --rollback")
report: dict[str, Any] = rollback_t3_app_chunks(
db_path=args.db_path,
rollback_artifact=args.rollback_artifact,
only_null_prior_values=args.only_null_prior_values,
pre_restore_artifact=args.pre_restore_artifact,
batch_size=args.batch_size,
)
else:
if args.apply and args.rollback_artifact is None:
parser.error("--rollback-artifact is required with --apply")
report = retag_t3_app_chunks(
db_path=args.db_path,
state_db=args.state_db,
apply=args.apply,
rollback_artifact=args.rollback_artifact,
batch_size=args.batch_size,
)
🧰 Tools
🪛 ast-grep (0.45.0)

[info] 234-234: use jsonify instead of json.dumps for JSON output
Context: json.dumps(report, sort_keys=True)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🤖 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/retag_t3_app_provenance.py` around lines 217 - 234, Update main’s
argument validation before the rollback/apply branch so --rollback-artifact is
required whenever the command runs, including with --apply, and report the
omission through parser.error instead of allowing retag_t3_app_chunks to raise
ValueError. Preserve the existing rollback validation behavior and normal
execution when the argument is provided.

Comment on lines +149 to +157
is_t3_app_session = _under_provider_sessions(path, ".codex") and (
is_t3_app_initiated_codex_session(
source_file,
state_db=t3_state_db,
linked_session_ids=t3_linked_session_ids,
)
)
if is_t3_app_session:
return ProvenanceDecision(T3_APP_SESSION, "KEEP", "T3 runtime cursor links Codex session")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# List every classify_provenance call site and show whether T3 arguments are passed.
rg -nP -C 6 '\bclassify_provenance\s*\(' --type=py -g '!tests/**'
# Show which of those modules handle BrainLayerAlarm.
rg -nP -C 3 'except\s+BrainLayerAlarm' --type=py

Repository: EtanHey/brainlayer

Length of output: 156


🏁 Script executed:

#!/bin/bash
set -euo pipefail
printf 'tracked python files around agent_provenance:\n'
git ls-files 'src/brainlayer/**/*.py' | sed -n '1,200p'
printf '\nSearch classify_provenance (all tracked files):\n'
rg -n -C 4 'classify_provenance\s*\(' . --glob '*.py' | sed -n '1,220p' || true
printf '\nSearch BrainLayerAlarm handling (all tracked files):\n'
rg -n -C 4 'except\s+BrainLayerAlarm|--\|--\|.*BrainLayerAlarm|--' . --glob '*.py' | sed -n '1,240p' || true

Repository: EtanHey/brainlayer

Length of output: 32820


🏁 Script executed:

#!/bin/bash
set -euo pipefail
printf 'agent_provenance outline:\n'
ast-grep outline src/brainlayer/agent_provenance.py --view expanded | sed -n '1,240p' || true
printf '\nagent_provenance lines 1-240:\n'
cat -n src/brainlayer/agent_provenance.py | sed -n '1,240p'
printf '\nWatcher relevant lines 360-540:\n'
cat -n src/brainlayer/watcher_bridge.py | sed -n '360,540p'

Repository: EtanHey/brainlayer

Length of output: 19383


🏁 Script executed:

#!/bin/bash
set -euo pipefail
printf 'Scripts provenance call context:\n'
cat -n scripts/provenance_classify_report.py | sed -n '1,90p'
printf '\nNon-test classify_provenance calls outside tests/scripts:\n'
rg -n -C 3 'classify_provenance\s*\(' src scripts --glob '*.py' | rg -v 'tests' | sed -n '1,220p' || true
printf '\nSearch BrainLayerAlarm definitions/usages:\n'
rg -n -C 4 'class BrainLayerAlarm|BrainLayerAlarm' src scripts tests --glob '*.py' | sed -n '1,300p' || true

Repository: EtanHey/brainlayer

Length of output: 24477


🏁 Script executed:

#!/bin/bash
set -euo pipefail
printf 't3_provenance relevant lines:\n'
fd -a -i '^t3_provenance.py$' . | while read -r f; do
  echo "----- ${f#"$PWD/"} -----"
  wc -l "$f"
  cat -n "$f" | sed -n '1,220p'
done
printf '\nInspector for BrainLayerAlarm inheritance:\n'
python3 - <<'PY'
import ast
from pathlib import Path
for p in Path("src/brainlayer").rglob("alarm.py"):
    print(p)
    tree = ast.parse(p.read_text())
    for node in ast.walk(tree):
        if isinstance(node, ast.ClassDef) and node.name == "BrainLayerAlarm":
            bases = [ast.unparse(n) for n in node.bases]
            print("bases:", bases)
            for subnode in ast.walk(node):
                if isinstance(subnode, ast.ClassDef):
                    print("nested:", subnode.name, ast.unparse(subnode.bases))
PY

Repository: EtanHey/brainlayer

Length of output: 5007


Handle BrainLayerAlarm around classify_provenance in watcher_bridge.py.

The watcher caller at src/brainlayer/watcher_bridge.py:410-411 now runs SQLite I/O when t3_linked_session_ids is missing from env resolution. Since BrainLayerAlarm inherits from BaseException, an existing except Exception block around watcher classification/can not catch T3 schema or connection failures. Catch it explicitly or ensure this path preloads T3 linkage IDs.

🤖 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/brainlayer/agent_provenance.py` around lines 149 - 157, Update the
watcher classification flow around classify_provenance in watcher_bridge.py to
explicitly catch BrainLayerAlarm, which inherits from BaseException, when T3
linkage resolution performs SQLite I/O. Ensure T3 schema or connection failures
are handled by the existing watcher error path, or preload t3_linked_session_ids
before classification so those failures cannot escape.

Source: Coding guidelines

Comment on lines +56 to +62
connection = sqlite3.connect(f"{path.absolute().as_uri()}?mode=ro&immutable=0", uri=True, timeout=1.0)
except sqlite3.Error as exc:
raise_alarm(
"t3_runtime_unavailable",
"could not open T3 runtime state read-only",
{"path": str(path), "error": str(exc)},
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Read-only opening of a live WAL database can fail.

The T3 state database belongs to another application. If that application uses WAL journaling, SQLite needs write access to the -shm and -wal files to attach a reader, and needs write access to recover a hot journal. With mode=ro, SQLite then returns SQLITE_READONLY or SQLITE_CANTOPEN. This raises t3_runtime_unavailable, and flush_to_db in src/brainlayer/watcher_bridge.py then defers every Codex entry for as long as the condition persists.

Verify the journal mode of the real T3 database. If it is WAL, copy the database to a temporary path before reading, or open with mode=ro plus a fallback to immutable=1 and treat a failure as "no linkage" rather than an alarm.

🤖 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/brainlayer/t3_provenance.py` around lines 56 - 62, The read-only
connection setup must handle live WAL databases without making every linkage
attempt unavailable. In the T3 database-opening flow around sqlite3.connect,
verify the actual journal mode and either read a temporary database copy when
WAL is used or add an immutable=1 fallback; when both approaches fail, treat the
result as no linkage instead of raising t3_runtime_unavailable via raise_alarm.

Comment on lines +410 to +414
if not t3_linkage_resolved:
source_only_provenance = classify_provenance(source_file, t3_linked_session_ids=set())
if source_only_provenance.provenance_tag == "codex-session":
# Do not persist or confirm ambiguous Codex provenance; a later pass can replay this offset.
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Record the deferral. A silent stop to Codex ingestion is not observable.

The branch defers Codex entries without any counter or log. skipped stays unchanged, and emit_watcher_flush at line 722 reports only chunks_indexed and chunks_skipped. If the T3 state database stays unusable, Codex ingestion stops indefinitely and every flush still looks healthy.

Count the deferred entries and log once per flush.

🔭 Proposed change
         t3_linkage_resolved = True
         try:
             linked_t3_session_ids = t3_app_codex_session_ids(t3_state_db) if t3_state_db.exists() else set()
         except BrainLayerAlarm:
             linked_t3_session_ids = set()
             t3_linkage_resolved = False
+        deferred_codex_entries = 0
             if not t3_linkage_resolved:
                 source_only_provenance = classify_provenance(source_file, t3_linked_session_ids=set())
                 if source_only_provenance.provenance_tag == "codex-session":
                     # Do not persist or confirm ambiguous Codex provenance; a later pass can replay this offset.
+                    deferred_codex_entries += 1
                     continue
+        if deferred_codex_entries:
+            logger.warning(
+                "T3 linkage unresolved at %s; deferred %d Codex entries for replay",
+                t3_state_db,
+                deferred_codex_entries,
+            )
🤖 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/brainlayer/watcher_bridge.py` around lines 410 - 414, In the watcher
processing flow around t3_linkage_resolved, track deferred Codex-provenance
entries separately from ordinary skipped entries, incrementing the counter
before continuing. Update emit_watcher_flush to report the deferred count and
add one per-flush log message so repeated deferrals remain observable without
logging each entry.

Comment on lines +286 to +291
monkeypatch.setattr(provenance_classify_report, "t3_app_codex_session_ids", load_once, raising=False)

report = provenance_classify_report.build_report(db_path)

assert report["total_chunks"] == 3
assert calls == [state_db]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Drop raising=False and assert the resulting tags.

scripts/provenance_classify_report.py imports t3_app_codex_session_ids at module level, so the attribute always exists. raising=False makes the patch silently do nothing if that import is renamed or removed, and the test would still pass. Use the default raising=True.

Also assert the tag distribution. The current assertions pass even if build_report stops forwarding the loaded set to classify_provenance.

💚 Proposed change
-    monkeypatch.setattr(provenance_classify_report, "t3_app_codex_session_ids", load_once, raising=False)
+    monkeypatch.setattr(provenance_classify_report, "t3_app_codex_session_ids", load_once)
 
     report = provenance_classify_report.build_report(db_path)
 
     assert report["total_chunks"] == 3
+    assert report["tags"] == {"codex-session": 3}
     assert calls == [state_db]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
monkeypatch.setattr(provenance_classify_report, "t3_app_codex_session_ids", load_once, raising=False)
report = provenance_classify_report.build_report(db_path)
assert report["total_chunks"] == 3
assert calls == [state_db]
monkeypatch.setattr(provenance_classify_report, "t3_app_codex_session_ids", load_once)
report = provenance_classify_report.build_report(db_path)
assert report["total_chunks"] == 3
assert report["tags"] == {"codex-session": 3}
assert calls == [state_db]
🤖 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_agent_provenance.py` around lines 286 - 291, Update the
monkeypatch in the provenance report test to use the default raising=True
behavior by removing raising=False. After build_report, assert the expected tag
distribution in report so the test verifies the loaded session-ID set is
forwarded to classify_provenance, while retaining the existing total_chunks and
calls assertions.

Comment on lines +224 to +230
def fail_replace(source: str, destination: str) -> None:
raise OSError("simulated replace failure")

monkeypatch.setattr(retag_module.os, "replace", fail_replace)

with pytest.raises(OSError, match="simulated replace failure"):
retag_module._write_rollback_artifact(artifact, [("new", "codex-session")])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The patch replaces os.replace process-wide.

retag_module.os is the global os module object, not a module-local alias. Line 227 therefore replaces os.replace for the whole interpreter while the test runs. Any other code that calls os.replace during the test fails with the simulated error.

Patch the function inside scripts.retag_t3_app_provenance instead, so the scope matches the intent.

💚 Proposed change
-    monkeypatch.setattr(retag_module.os, "replace", fail_replace)
+    monkeypatch.setattr("scripts.retag_t3_app_provenance.os.replace", fail_replace)

That target resolves to the same global attribute. To scope the failure to this module only, import replace into scripts/retag_t3_app_provenance.py as a module-level name and patch that name.

Also drop the unused **kwargs from the wrapper at line 179. _atomic_write_jsonl declares only positional parameters.

🤖 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_retag_t3_app_provenance.py` around lines 224 - 230, Update the
test around _write_rollback_artifact to patch a module-local replace symbol
rather than retag_module.os.replace, preventing process-wide os.replace
mutation; expose os.replace as a module-level imported name in
scripts.retag_t3_app_provenance and have the implementation use it. Also remove
the unused **kwargs from the wrapper near line 179, since _atomic_write_jsonl
accepts only positional parameters.

Comment thread tests/test_t3_app_provenance.py
EtanHey added 2 commits August 3, 2026 21:52
Choose durability shape (a): the flush result now names the exact Codex entries deferred when T3 linkage is unavailable, and BatchIndexer retains only those payloads while confirming unrelated plain Claude work. Confirmations for a source with a deferred entry are withheld so a later record cannot advance the registry across the gap. This keeps the fix inside the existing watcher/indexer contract without adding an unresolved queue schema or widening recovery migrations.
Failure isolation must honor the same deferred-entry contract as normal flushes. Retain deferred payloads, suppress their watermarks, and count only actual callback exceptions as failed inputs so mixed exception/deferral batches cannot drop a consumed Codex record.
@cursor

cursor Bot commented Aug 3, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_cd869dd6-9d58-4900-8acf-d2037cca4f6e)

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Your trial has ended. Reactivate Greptile to resume code reviews.

Comment thread src/brainlayer/watcher.py
self._buffer = retained
self._flush_failures = self._flush_failures + 1 if retained else 0
return len(retained)
self._flush_failures = self._flush_failures + 1 if failed_inputs else 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium brainlayer/watcher.py:991

In _isolate_failed_flush, deferred entries are appended to retained alongside genuinely failing items, and _flush_failures is incremented whenever failed_inputs is nonzero. The quarantine path that drops the buffer after repeated failures only triggers when len(batch) <= 1, but with a deferred entry plus a failing entry the batch size stays at 2, so the failing entry is retried forever and never quarantined. The deferred entry also gets reprocessed on every retry, and new input keeps accumulating in the buffer.

The retain-limit/quarantine check should also apply to multi-item batches where only a subset fails, so persistent failures are quarantined instead of looping indefinitely. Alternatively, exclude deferred entries from the failure count so they don't keep the batch size above the quarantine threshold.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @src/brainlayer/watcher.py around line 991:

In `_isolate_failed_flush`, deferred entries are appended to `retained` alongside genuinely failing items, and `_flush_failures` is incremented whenever `failed_inputs` is nonzero. The quarantine path that drops the buffer after repeated failures only triggers when `len(batch) <= 1`, but with a deferred entry plus a failing entry the batch size stays at 2, so the failing entry is retried forever and never quarantined. The deferred entry also gets reprocessed on every retry, and new input keeps accumulating in the buffer.

The retain-limit/quarantine check should also apply to multi-item batches where only a subset fails, so persistent failures are quarantined instead of looping indefinitely. Alternatively, exclude deferred entries from the failure count so they don't keep the batch size above the quarantine threshold.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cdd0a74270

ℹ️ About Codex in GitHub

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

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

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

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

source_file,
base_content_class,
content=clean_content,
t3_linked_session_ids=linked_t3_session_ids,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve T3 tags through enrichment

When a T3-linked watcher chunk later completes normal enrichment, this classification is lost: the watcher stores t3-app-session in chunks.provenance_class, but _apply_enrichment in enrichment_controller.py:1547-1552 unconditionally derives a trust-lattice value such as RAW-ETAN-DIRECT or AGENT-PARAPHRASE and overwrites that same column. The migration's retagged rows are erased by the same path, so the new distinction survives only until the next enrichment; preserve the agent-origin tag in a separate field or explicitly retain it during enrichment.

AGENTS.md reference: AGENTS.md:L5-L8

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (2)
src/brainlayer/watcher_bridge.py (1)

356-371: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Repeated T3 database reads during failure isolation.

t3_app_codex_session_ids opens a new SQLite connection every time flush_to_db runs. BatchIndexer._isolate_failed_flush in watcher.py calls self.on_flush([item]) once per item after a batch flush fails, so a failed N-item batch triggers N fresh T3 database opens instead of one. This reintroduces the per-chunk T3 database scan pattern the earlier fix removed, just scoped to the failure-isolation path instead of the normal batch path.

Cache the linkage result (with a short TTL, or compute it once per _isolate_failed_flush call and pass it through) so isolation retries do not re-open the T3 database per item.

🤖 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/brainlayer/watcher_bridge.py` around lines 356 - 371, Update the
failure-isolation flow around BatchIndexer._isolate_failed_flush and flush_to_db
so all per-item retries reuse one T3 linkage lookup instead of calling
t3_app_codex_session_ids for each item. Compute the linkage result once per
isolation call and pass it through, or introduce a short-lived cache with
equivalent scope, while preserving the existing BrainLayerAlarm
unresolved-linkage behavior.
src/brainlayer/watcher.py (1)

884-897: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Cap entries deferred across repeated flush cycles.

_do_flush() sets _buffer = deferred_entries and resets _flush_failures, so entries deferred for unresolved T3 linkage are not covered by the retry-limit/quarantine path. Add an age- or count-based limit for retained deferred entries, and quarantine them when the limit is reached.

🤖 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/brainlayer/watcher.py` around lines 884 - 897, Update _do_flush so
entries retained in deferred_entries across repeated flush cycles are tracked by
age or retry count rather than resetting their retry state via _flush_failures.
When the configured limit is reached, quarantine those unresolved entries using
the existing quarantine mechanism, while preserving normal retention for entries
still below the limit and resetting only successful flush state.
🤖 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 `@src/brainlayer/watcher_bridge.py`:
- Around line 356-371: Update the failure-isolation flow around
BatchIndexer._isolate_failed_flush and flush_to_db so all per-item retries reuse
one T3 linkage lookup instead of calling t3_app_codex_session_ids for each item.
Compute the linkage result once per isolation call and pass it through, or
introduce a short-lived cache with equivalent scope, while preserving the
existing BrainLayerAlarm unresolved-linkage behavior.

In `@src/brainlayer/watcher.py`:
- Around line 884-897: Update _do_flush so entries retained in deferred_entries
across repeated flush cycles are tracked by age or retry count rather than
resetting their retry state via _flush_failures. When the configured limit is
reached, quarantine those unresolved entries using the existing quarantine
mechanism, while preserving normal retention for entries still below the limit
and resetting only successful flush state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e3fb9fa6-0271-44c4-83b8-b21eafa5b3b3

📥 Commits

Reviewing files that changed from the base of the PR and between 47bb3a3 and cdd0a74.

📒 Files selected for processing (4)
  • src/brainlayer/watcher.py
  • src/brainlayer/watcher_bridge.py
  • tests/test_jsonl_watcher.py
  • tests/test_t3_app_provenance.py
📜 Review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.11)
  • GitHub Check: Macroscope - Correctness Check
🧰 Additional context used
📓 Path-based instructions (2)
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Run pytest before claiming behavior changes are safe; avoid running real-production-DB tests tests/test_vector_store.py and tests/test_engine.py as part of worker pre-push checks unless deliberately performing production-DB checks.

Files:

  • tests/test_jsonl_watcher.py
  • tests/test_t3_app_provenance.py
src/brainlayer/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/brainlayer/**/*.py: Treat retrieval correctness, write safety, and MCP stability as critical-path concerns; prioritize regressions in search quality, lock handling, and tool contracts over style refactors.
Use paths.py:get_db_path() for database path resolution rather than hardcoding paths; the canonical database is ~/.local/share/brainlayer/brainlayer.db.
Ensure concurrent database access retries SQLITE_BUSY, uses one connection per worker, permits one writer at a time, and does not run brain_digest concurrently with other MCP work.
Run secret scrubbing in watcher_bridge.py before chunk persistence; redact provider-prefixed and labeled high-entropy secrets and record unlabeled high-entropy tokens in quarantine metadata.
Preserve ai_code, stack_trace, and user_message verbatim; skip noise, summarize build logs, retain only structure for directory listings, use AST-aware tree-sitter chunking, never split stack traces, and mask large tool output.
Do not change the source denylist semantics: provider sessions and ordinary Claude subagents ingest by default; only exact brain-worker subagents and wf_* workflow paths are excluded, without deleting source JSONL.
For bulk database operations, stop enrichment workers, run PRAGMA wal_checkpoint(FULL) before and after, drop and recreate FTS triggers around large deletes, batch deletes in 5–10K chunks, checkpoint every three batches, and never delete large datasets while the FTS delete trigger is active.
Respect chunk lifecycle fields superseded_by, aggregated_into, and archived_at; exclude lifecycle-managed chunks from default search, support include_archived=True, use safety gates for personal data in brain_supersede, and implement brain_archive as a timestamped soft delete.
Use Groq as the primary enrichment backend, Gemini as fallback, and Ollama as the offline last resort; honor BRAINLAYER_ENRICH_BACKEND and BRAINLAYER_ENRICH_RATE.
The real-time watcher must persist offsets, dete...

Files:

  • src/brainlayer/watcher_bridge.py
  • src/brainlayer/watcher.py
🪛 ast-grep (0.45.0)
tests/test_jsonl_watcher.py

[info] 756-756: Do not hardcode temporary file or directory names
Context: "/tmp/codex.jsonl"
Note: [CWE-377] Insecure Temporary File.

(hardcoded-tmp-file)


[info] 759-759: Do not hardcode temporary file or directory names
Context: "/tmp/other.jsonl"
Note: [CWE-377] Insecure Temporary File.

(hardcoded-tmp-file)

tests/test_t3_app_provenance.py

[info] 291-291: use jsonify instead of json.dumps for JSON output
Context: json.dumps(entry)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 295-295: use jsonify instead of json.dumps for JSON output
Context: json.dumps(entry)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 308-316: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"type": "user",
"message": {
"content": [{"type": "text", "text": "Keep ordinary Claude ingestion available."}]
},
"timestamp": "2026-08-01T12:00:00Z",
}
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 373-373: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"threadId": session_id})
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🔇 Additional comments (7)
src/brainlayer/watcher_bridge.py (3)

719-739: Log the deferred-entry count per flush.

deferred_entries is now tracked as a list, but neither the log statement at Line 721-727 nor emit_watcher_flush at Line 732-737 reports its size. If the T3 state database stays unusable, Codex ingestion is deferred indefinitely with no observable signal; every flush still looks healthy.

Add the deferred count to the log line and to emit_watcher_flush.

🔭 Proposed change
-        if inserted > 0:
+        if inserted > 0 or deferred_entries:
             logger.info(
-                "Flushed %d chunks (%d skipped) in %.1fms",
+                "Flushed %d chunks (%d skipped, %d deferred) in %.1fms",
                 inserted,
                 skipped,
+                len(deferred_entries),
                 latency_ms,
             )
             emit_watcher_flush(
                 chunks_indexed=inserted,
                 chunks_skipped=skipped,
+                chunks_deferred=len(deferred_entries),
                 latency_ms=latency_ms,
                 source_files=list(source_files_seen),
             )

77-90: LGTM!

Also applies to: 361-371, 741-746


419-424: 🎯 Functional Correctness

No change needed for the classify_provenance keyword argument.

classify_provenance accepts t3_linked_session_ids, so this call does not introduce a TypeError.

			> Likely an incorrect or invalid review comment.
tests/test_t3_app_provenance.py (1)

11-11: LGTM!

Also applies to: 269-390

src/brainlayer/watcher.py (2)

908-935: LGTM!

Also applies to: 967-992


884-897: 🗄️ Data Integrity & Integration

No change needed for on_confirm_batch item filtering.

JSONLWatcher._advance_confirmed_batch only reads item offsets to compute the highest eligible offset per file; deferred entries are not registered in watermarks, so their offsets are not exposed to this path as confirmed progress.

tests/test_jsonl_watcher.py (1)

754-786: LGTM!

@EtanHey
EtanHey merged commit 1466aec into main Aug 4, 2026
7 of 9 checks passed
EtanHey added a commit that referenced this pull request Aug 4, 2026
The 7 admin merges of 2026-08-04 (#645 #644 #643 #641 #633 #605 #602) bypassed CI,
landing import-order and formatting drift on main. Every PR's lint job inherits it.
Rebased onto #647 (949334a), which cleared the watchdog test blocking this push.

ruff check --fix + ruff format only; no behavioural change.
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