Skip to content

perf: delete expired Firestore states concurrently under a bound - #1170

Merged
groupthinking merged 6 commits into
mainfrom
perf/firestore-cleanup-gather
Aug 1, 2026
Merged

perf: delete expired Firestore states concurrently under a bound#1170
groupthinking merged 6 commits into
mainfrom
perf/firestore-cleanup-gather

Conversation

@groupthinking

@groupthinking groupthinking commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Canonical issue

Closes #1169

Outcome

FirestoreStateService.cleanup_old_states() deleted expired documents in a
sequential await loop, so every delete waited for the previous one to
complete — despite a # Delete in batch comment claiming otherwise.

Deletes are now handled by a fixed pool of CLEANUP_DELETE_CONCURRENCY = 16
workers pulling from a shared iterator over the expired documents.

What this does and does not improve (corrected after review — see
this comment):

✅ Improves Wall-clock latency. Up to 16 deletes overlap per latency wave, so N sequential waves collapse to ~ceil(N/16) waves.
❌ Does not change Firestore request count or quota usage. This still issues exactly one delete() RPC per document.

An earlier revision of this description claimed a reduction in "network
round-trips", which wrongly implied reduced request/quota usage. Only a native
batch write would do that; a bounded fan-out does not. The claim has been
reworded throughout.

Two correctness improvements come with it:

  • One failing delete no longer abandons the rest of the backlog. Previously the
    exception propagated out of the loop and every remaining document was skipped.
  • The returned count reflects deletes that actually succeeded, instead of
    being lost along with the propagating exception.

Scope

In scope The delete fan-out inside cleanup_old_states(); four regression tests
Out of scope The query/where clause, caching, singleton lifecycle, the method signature

Two files changed: src/youtube_extension/services/cloud/firestore_state.py and
tests/unit/test_firestore_state.py (tests only).

Design notes

Why a worker pool rather than gather + semaphore? The first revision of
this PR used asyncio.gather() over a generator with a semaphore inside each
coroutine. Review correctly pointed out that gather allocates one task per
document up front
, before the semaphore gates anything — so a large backlog
still cost unbounded task and event-loop memory, and the cleanup query has no
limit. The worker pool bounds both in-flight RPCs and allocated tasks by the
same constant. This is measured, not assumed — see the task-allocation test below.

Pulling from the shared iterator with next() needs no lock: the event loop is
single-threaded and there is no await between taking a document and using it.

Why not Firestore's native AsyncWriteBatch? It would genuinely reduce
request count, which this does not. It also caps at 500 operations, so correct
use requires chunking plus a second error path for partial batch failure. Given
that cleanup is a periodic maintenance job, the latency win here is the valuable
part and the smaller diff is the better trade. Native batching is a reasonable
follow-up if cleanup volume grows enough to make quota the binding constraint.

Why a call-scoped pool rather than the per-instance/loop-keyed semaphore in
#1152?
That PR needed loop-keyed laziness because intelligent_cache.py
constructs its singleton at import time, binding an eagerly-created
asyncio.Semaphore to the wrong event loop. Here _firestore_service is created
lazily inside async def get_firestore_service(), so there is no import-time
loop hazard, and cleanup is a periodic job rather than a hot concurrent path.

Risk

Low. One behavioural change is intentional: cleanup no longer raises when an
individual delete fails. It logs a warning naming the failure count and the first
error, and returns the number of successful deletes. For a maintenance job this
is strictly better than aborting halfway — the previous behaviour left the
collection partially cleaned and discarded the count.

Delete ordering is no longer deterministic. Deletes of distinct documents are
independent, so this has no semantic consequence.

Verification

At head f6c7910e4232403a121e6fd7bd5378b13640eca3:

PYTHONPATH=src pytest tests/unit/test_firestore_state.py
=> 90 passed

ruff check src/.../firestore_state.py tests/unit/test_firestore_state.py
=> 1 error (pre-existing F841 at test_firestore_state.py:530, identical on main)

Non-vacuity — every new test was run against the implementation it replaced
and confirmed to fail.

Against the original sequential loop:

Test Failure
test_cleanup_deletes_overlap_instead_of_running_sequentially AssertionError: deletes never overlapped (peak=1)
test_cleanup_bounds_in_flight_deletes AttributeError: ... has no attribute 'CLEANUP_DELETE_CONCURRENCY'
test_cleanup_failure_does_not_abandon_remaining_deletes RuntimeError: firestore boom propagated, stranding the remaining delete

Against a worker that returns instead of continuing after its own delete
fails -- the defect the first version of this test was too weak to catch, raised
in review and fixed in 7558f03:

>       assert count == doc_count - 1
E       assert 0 == (5 - 1)
FAILED ... ::test_cleanup_failure_does_not_abandon_remaining_deletes

That test previously used 3 documents against a pool of 16, so min(16, 3) = 3
workers each handled exactly one document and the while True continuation
branch never ran. It now narrows the pool to a single worker over a 5-document
backlog, which is the only arrangement that isolates that path -- with 2+ workers
a surviving sibling drains the backlog and the assertion passes regardless.

Against the intermediate gather + semaphore revision, proving the review
finding was real and is now fixed:

AssertionError: cleanup allocated 49 concurrent tasks for 48 documents;
delete tasks are not bounded by the CLEANUP_DELETE_CONCURRENCY worker pool of 16
assert 49 <= (16 + 3)

The overlap test measures peak concurrent in-flight deletes; the old loop pins it
at exactly 1. The allocation test measures peak len(asyncio.all_tasks()), which
is the stricter bound the review asked for.

Against the max(0.001, float(os.getenv(...))) clamping that the configuration
commit originally shipped -- float() accepts inf, and max(0.001, inf) is
inf, so an operator could silently remove the per-delete deadline. Restoring
only that clamping inside the new parser:

FAILED ...::test_float_env_rejects_non_finite_and_non_positive[inf]
FAILED ...::test_float_env_rejects_non_finite_and_non_positive[Infinity]
FAILED ...::test_float_env_rejects_non_finite_and_non_positive[-inf]
FAILED ...::test_float_env_rejects_non_finite_and_non_positive[nan]
FAILED ...::test_float_env_rejects_non_finite_and_non_positive[0]
FAILED ...::test_float_env_rejects_non_finite_and_non_positive[-1]
FAILED ...::test_float_env_rejects_non_finite_and_non_positive[0.0]
E           Failed: DID NOT RAISE ValueError
7 failed, 83 deselected

nan failed in the opposite direction: max(0.001, nan) returns 0.001
because nan > 0.001 is false, so a nan override was silently swallowed
rather than reported. Both are now rejected by a single math.isfinite check.

Production evidence

The module is live, with 4 importers:

src/youtube_extension/services/cloud/cloud_video_processor.py
src/youtube_extension/services/cloud/__init__.py
tests/unit/test_firestore_state.py
tests/unit/test_cloud_video_processor.py

cleanup_old_states is the retention path for the video_processing_state
collection, so its latency grows with exactly the backlog it exists to drain —
the sequential loop was slowest precisely when cleanup mattered most.

Agent handoff

@coderabbitai review

cleanup_old_states() deleted every expired document in a sequential
await loop, despite a "# Delete in batch" comment claiming otherwise.
Cleanup therefore cost N network round-trips and scaled linearly with
the size of the expired backlog.

Deletes are now fanned out with asyncio.gather() under a semaphore
bounded by CLEANUP_DELETE_CONCURRENCY (16), so a large backlog cannot
flood Firestore with unbounded in-flight RPCs. return_exceptions=True
keeps a single failing delete from abandoning deletes already in
flight, and the returned count now reflects deletes that actually
succeeded rather than being lost to a propagating exception.

Adds three regression tests, each verified to fail against the previous
sequential implementation:
- overlap test (old peak in-flight was 1)
- boundedness test (peak never exceeds the configured limit)
- failure-isolation test (old code propagated and skipped the rest)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 1, 2026 20:54
@vercel

vercel Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
v0-uvai Canceled Canceled Aug 1, 2026 9:27pm

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (1)
  • [‘architecture-gap’, ‘bug’, ‘ci-cd’, ‘ci/cd’, ‘copilot-rabbit’, ‘documentation’, ‘duplicate’, ‘enhancement’, ‘frontend’, ‘github_actions’, ‘good first issue’, ‘help wanted’, ‘high-priority’, ‘invalid’, ‘javascript’, ‘ml-model’, ‘needs-triage’, ‘pipeline-critical’, ‘placeholder-code’, ‘priority:high’, ‘python’, ‘python:uv’, ‘question’, ‘styling’, ‘tests’, ‘v0’]

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bcc17b2b-72d4-4b3d-a722-300b878508cb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Improved cleanup of expired saved states by processing deletions concurrently.
    • Cleanup now continues when individual deletions fail and reports unsuccessful operations.
    • Avoided unnecessary processing when there are no expired states to remove.

Walkthrough

cleanup_old_states now skips empty results, deletes expired Firestore documents through 16 bounded workers, continues after individual failures, and returns the number of successful deletions.

Changes

Firestore cleanup

Layer / File(s) Summary
Bounded concurrent deletion flow
src/youtube_extension/services/cloud/firestore_state.py
Adds the concurrency limit and asyncio support. cleanup_old_states uses concurrent workers, handles empty results, counts successful deletions, and logs failures without stopping remaining work.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested labels: copilot-rabbit

Poem

Expired states line up in flight,
Sixteen workers keep the pace right.
Failures are logged, the work moves on,
Success counts shine when cleanup is done.
Firestore grows quiet by dawn.

🚥 Pre-merge checks | ✅ 3 | ❌ 4

❌ Failed checks (1 warning, 3 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the implementation and verification well but omits required rollback details, handoff checklist items, and agent provenance. Add the missing Risk rollback, Verification checklist results, Production evidence applicability, Agent handoff checklist, and required agent-lock manifest.
Linked Issues check ❓ Inconclusive The implementation meets concurrency, bounds, failure isolation, and success-count requirements, but tests are excluded by the !tests/** filter. Include tests/unit/test_firestore_state.py in review or provide reviewable evidence for the required regression tests.
Enforce Copilot Verification ❓ Inconclusive Assessment pending direct verification of GitHub Copilot review and approval metadata. Check the pull request review records for an explicit GitHub Copilot approval; human or CodeRabbit reviews do not satisfy this check.
Require Ai Unit Tests ❓ Inconclusive I need to verify the pull request label and the committed unit-test changes against repository and pull request data. Inspect the PR metadata for copilot-rabbit and compare the committed diff for AI-generated unit tests.
✅ Passed checks (3 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed The reviewed implementation changes only the scoped Firestore delete fan-out; no unrelated code changes are shown.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title clearly and concisely describes the bounded concurrent deletion of expired Firestore states.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/firestore-cleanup-gather
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch perf/firestore-cleanup-gather

Warning

Review ran into problems

🔥 Problems

These MCP integrations need to be re-authenticated in the Integrations settings: Sentry


Linked repositories: Public OSS repositories can only analyze public repositories installed in this organization. No linked repositories were analyzed; skipped groupthinking/uvai-skills.


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.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA f6c7910.
Ensure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice.

Scanned Files

None

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Agent Completion Truth Gate: NOT_APPLICABLE

Evidence agrees.

Machine-readable verdict
{
  "details": {},
  "reasons": [],
  "verdict": "not_applicable"
}

Workflow evidence

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Optimizes Firestore state cleanup by concurrently deleting expired documents while preserving partial-success reporting.

Changes:

  • Adds a 16-delete concurrency limit and partial-failure logging.
  • Adds regression tests for overlap, bounded RPCs, and failures.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
firestore_state.py Implements bounded concurrent deletion.
test_firestore_state.py Tests concurrency and failure behavior.

Comment thread src/youtube_extension/services/cloud/firestore_state.py Outdated
Comment thread src/youtube_extension/services/cloud/firestore_state.py Outdated

Copy link
Copy Markdown
Owner Author

Automated remediation pass — terminal state: HALTED(awaiting_merge_approval)

Correctness / red-team review — PASS. The bounded asyncio.gather + Semaphore(CLEANUP_DELETE_CONCURRENCY) fan-out is correct; return_exceptions=True with count = len(results) - len(failures) reports only successful deletes; the empty-docs early return is safe; and the three new tests are non-vacuous. No code-level findings.

The two red checks are both non-code — nothing in this diff can flip them:

  • Vercel"Canceled from the Vercel Dashboard": a manual cancellation, not a build failure. A re-run (or a no-op push) clears it.
  • Agent completion enforcementreason: missing_trusted_publication / "Trusted evidence blocked": a repo-level trust-policy gate that is unprovisioned, so it hard-fails regardless of PR content. Note agent-completion/truth-gate/pr-1170 passed (not_applicable: all rules passed) on the same commit — the two agent gates disagree. This is the same systemic gate that fix(ci): report Agent Lock gate as neutral when trust policy is unprovisioned #1151 (report Agent Lock gate as neutral when trust policy is unprovisioned) and fix: scope agent gate applicability to real dispatch evidence #1154 (scope agent gate applicability to real dispatch evidence) are written to fix; landing one of those is the real remediation, not a change here.

No merge performed — base main is protected and the publish step is human-gated. Once you accept the two non-code checks above, the staged merge is:

gh pr merge 1170 --repo groupthinking/EventRelay --squash

Generated by Claude Code

Review correctly identified that asyncio.gather() over a comprehension
allocates one task per document *before* the semaphore can gate
anything. Since the cleanup query has no limit, a large expired backlog
would cost unbounded task and event-loop memory even though only 16
deletes reached Firestore at a time.

Replaces the gather-plus-semaphore fan-out with a fixed pool of
CLEANUP_DELETE_CONCURRENCY workers pulling from a shared iterator over
the documents. Pulling with next() is safe without a lock because the
event loop is single-threaded and there is no await between taking a
document and using it. Worker count is min(CONCURRENCY, len(docs)), so
both in-flight RPCs and allocated tasks are bounded by the same
constant.

Failure isolation is preserved: each worker tallies its own exceptions
instead of relying on return_exceptions, so one bad delete still cannot
abandon the remaining backlog.

Adds test_cleanup_bounds_allocated_delete_tasks_not_just_rpcs, which
measures peak len(asyncio.all_tasks()) during cleanup. Against the
previous gather implementation it reports 49 concurrent tasks for 48
documents; with the worker pool it stays within the bound.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@groupthinking

Copy link
Copy Markdown
Owner Author

Both review findings were correct and both are now fixed at db083b6.

1. gather allocated one task per document (bounded RPCs, unbounded tasks)

Accepted in full. asyncio.gather(*(_delete_one(doc) for doc in docs)) schedules
every coroutine as a task before the semaphore gates anything, so the semaphore
only ever bounded in-flight RPCs — not allocated tasks. With no limit on
the cleanup query, that is a real unbounded-memory path.

Replaced with a fixed worker pool of min(CLEANUP_DELETE_CONCURRENCY, len(docs))
workers pulling from a shared iterator, so both quantities are bounded by the same
constant. Pulling with next() needs no lock — the event loop is single-threaded
and there is no await between taking a document and using it. Failure isolation
is preserved by tallying per-worker rather than relying on return_exceptions.

Verified rather than asserted. New test test_cleanup_bounds_allocated_delete_tasks_not_just_rpcs
measures peak len(asyncio.all_tasks()); run against the gather implementation
it reproduces the exact defect you described:

AssertionError: cleanup allocated 49 concurrent tasks for 48 documents;
delete tasks are not bounded by the CLEANUP_DELETE_CONCURRENCY worker pool of 16
assert 49 <= (16 + 3)

2. The performance claim overstated the win

Accepted in full. This does not reduce Firestore request count or quota usage —
it still issues one delete() RPC per document. Only wall-clock latency improves,
by overlapping up to 16 deletes per latency wave.

The description has been reworded throughout: the "N network round-trips → ceil(N/16)"
claim is gone, replaced with an explicit does/does-not table, and the
AsyncWriteBatch comparison now states plainly that a native batch genuinely reduces
request count while a bounded fan-out does not.


118 tests pass; ruff is at parity with main (1 pre-existing F841).

@coderabbitai review — auto-review was skipped on this PR ("Auto reviews are limited
based on label configuration") even though the python label is applied, so this is
an explicit request. Please pay particular attention to the lock-free next() pull
from the shared iterator across concurrent workers, and to whether tallying failures
per-worker preserves the isolation guarantee that return_exceptions=True provided.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline src/youtube_extension/services/cloud/firestore_state.py --items all
ast-grep outline tests/unit/test_firestore_state.py --items all --match 'cleanup|Cleanup'

rg -n -C 12 'CLEANUP_DELETE_CONCURRENCY|async def cleanup_old_states|def cleanup_old_states' \
  src/youtube_extension/services/cloud/firestore_state.py

rg -n -C 10 'test_cleanup_(deletes_overlap|bounds|failure)|all_tasks|cleanup_old_states' \
  tests/unit/test_firestore_state.py

Length of output: 6056


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '== Candidate Firestore state test files =='
fd -i 'firestore.*state|state.*firestore' . -t f

printf '%s\n' '== Cleanup test definitions in Python files =='
rg -n -P --type py -C 8 '^\s*(async\s+)?def\s+test_.*cleanup|asyncio\.all_tasks\s*\(' .

printf '%s\n' '== cleanup_old_states method tail =='
sed -n '312,378p' src/youtube_extension/services/cloud/firestore_state.py

Length of output: 874


🏁 Script executed:

#!/bin/bash
set -u

printf '%s\n' '== Checked-out commit =='
git rev-parse HEAD
git status --short

printf '%s\n' '== PR `#1170` changed files =='
gh pr view 1170 --repo groupthinking/EventRelay --json headRefOid,baseRefOid,files \
  --jq '.headRefOid, .baseRefOid, (.files[] | "\(.path)\t+\(.additions)\t-\(.deletions)")' \
  || true

printf '%s\n' '== Test files available in checkout =='
fd . -t f | rg -n '(^|/)(test|tests?)[^/]*\.py$|_test\.py$' || true

printf '%s\n' '== Complete cleanup_old_states implementation =='
sed -n '312,378p' src/youtube_extension/services/cloud/firestore_state.py

Length of output: 4403


🏁 Script executed:

#!/bin/bash
set -euo pipefail

commit="db083b637feb8a2ff1f0c6cfc90e9399059278e5"
test_path="tests/unit/test_firestore_state.py"

printf '%s\n' '== Cleanup-related test definitions and context =='
git show "${commit}:${test_path}" |
  rg -n -C 16 '^\s*(async\s+)?def\s+test_.*cleanup|asyncio\.all_tasks\s*\(|CLEANUP_DELETE_CONCURRENCY|cleanup_old_states\s*\('

printf '%s\n' '== PR test-file diff =='
git diff --no-ext-diff --unified=20 "${commit}^" "${commit}" -- "${test_path}"

Length of output: 15882


@groupthinking I completed the focused review of db083b6. I found no code-level findings.

The lock-free next(pending) access is safe. Each worker executes next(pending) without an await, so asyncio cannot switch to another worker during iterator advancement.

The worker pool bounds both resources. asyncio.gather() receives at most min(CLEANUP_DELETE_CONCURRENCY, len(docs)) worker coroutines. Each worker issues at most one delete RPC at a time.

The per-worker exception handling preserves failure isolation. A delete that raises an Exception is recorded, and that worker continues with the next document. The returned count includes only successful deletes. CancelledError and other BaseException control-flow exceptions still propagate, which is correct.

The focused tests cover overlap, in-flight RPC bounds, allocated-task bounds, and continued deletion after an ordinary failure. The task-allocation test would detect the prior gather()-per-document implementation.


✏️ Learnings added
Learnt from: groupthinking
URL: https://github.com/groupthinking/EventRelay/pull/1170

Timestamp: 2026-08-01T21:04:17.240Z
Learning: In `src/youtube_extension/services/cloud/firestore_state.py`, `FirestoreStateService.cleanup_old_states()` uses a fixed asyncio worker pool. Workers safely pull from a shared iterator without a lock because `next(pending)` executes before any `await`. Each worker records ordinary `Exception` failures and continues, while the method returns only the number of successful deletes. `asyncio.CancelledError` remains propagating control flow.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

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

Inline comments:
In `@src/youtube_extension/services/cloud/firestore_state.py`:
- Around line 358-363: Strengthen
test_cleanup_failure_does_not_abandon_remaining_deletes by creating more
documents than CLEANUP_DELETE_CONCURRENCY, or overriding that constant to a
small value, so a worker must continue consuming pending documents after its
delete fails. Arrange a failure and assert that all subsequent documents are
still attempted and deleted, preserving the existing failure-isolation
assertions.
- Around line 30-34: Update the comment above CLEANUP_DELETE_CONCURRENCY to
describe the fixed worker pool and shared-iterator approach used by
cleanup_old_states(), not a semaphore. Replace the hardcoded value with an
os.getenv()-based configuration lookup, preserving a sensible default of 16 and
converting the environment value to an integer for worker-count usage.
- Around line 345-357: Update the _delete_worker function’s
doc.reference.delete() call to pass an explicit timeout value, using the
surrounding cleanup configuration or established timeout constant. Preserve the
existing success counting and exception collection behavior so timed-out deletes
are tallied as failures.
🪄 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: Repository YAML (base), Repository UI (inherited), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6046d70a-2b0b-48a8-b027-38a1239497a2

📥 Commits

Reviewing files that changed from the base of the PR and between ad7e2c1 and db083b6.

⛔ Files ignored due to path filters (1)
  • tests/unit/test_firestore_state.py is excluded by !tests/**
📒 Files selected for processing (1)
  • src/youtube_extension/services/cloud/firestore_state.py
📜 Review details
⏰ Context from checks skipped due to timeout. (5)
  • GitHub Check: Generate and Upload Coverage
  • GitHub Check: test
  • GitHub Check: trivy
  • GitHub Check: Security Scan - python
  • GitHub Check: Security Scan - javascript
⚠️ CI failures not shown inline (17)

GitHub Actions: Secret Scan / 0_gitleaks (working tree).txt: perf: delete expired Firestore states concurrently under a bound

Conclusion: failure

View job details

##[group]Run gitleaks detect --no-git --config .gitleaks.toml --redact --verbose --exit-code 1
 �[36;1mgitleaks detect --no-git --config .gitleaks.toml --redact --verbose --exit-code 1�[0m
 shell: /usr/bin/bash -e {0}
 ##[endgroup]
     ○
     │╲
     │ ○
     ○ ░
     ░    gitleaks
 Finding:     ...gz", hash = "sha256:�[1;3;mREDACTED�[0m, size = 401824, upl...
 ***REDACTED_SECRET_ASSIGNMENT***
 RuleID:      square-access-token
 Entropy:     3.884400
 File:        uv.lock
 Line:        5129
 Fingerprint: uv.lock:square-access-***REDACTED_SECRET_ASSIGNMENT***
 �[90m9:02PM�[0m �[32mINF�[0m scan completed in 5.88s
 �[90m9:02PM�[0m �[31mWRN�[0m leaks found: 1
 ##[error]Process completed with exit code 1.

GitHub Actions: PR Governance / Canonical issue and evidence: perf: delete expired Firestore states concurrently under a bound

Conclusion: failure

View job details

##[group]Run actions/github-script@v8
 with:
   script: const pr = context.payload.pull_request;
const runUrl =
  `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
async function publish(conclusion, title, summary) {
  await github.rest.checks.create({
    owner: context.repo.owner,
    repo: context.repo.repo,
    name: "PR Governance",
    head_sha: pr.head.sha,
    status: "completed",
    conclusion,
    details_url: runUrl,
    output: {
      title,
      summary: summary.slice(0, 60000)
    }
  });
  if (conclusion === "failure") {
    core.setFailed(summary);
  }
}
if (pr.draft) {
  await publish(
    "neutral",
    "Governance deferred for draft PR",
    `Draft PR #${pr.number} is not enforced. The Check is bound to exact head ${pr.head.sha}.`
  );
  return;
}
const body = pr.body || "";
function getSectionContent(text, heading) {
  const escapedHeading = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  const pattern = new RegExp(
    escapedHeading + "\\s*\\n([\\s\\S]*?)(?=\\n## |$)",
    "i"
  );
  const match = text.match(pattern);
  if (!match) return null;
  return match[1].replace(/<!--[\s\S]*?-->/g, "").trim();
}
const placeholderPatterns = [
  /^Describe the user or operational result this PR produces\.?$/i,
  /^List exact automated and manual checks, tied to the current head SHA\.?$/i,
  /^Provide the Vercel preview, production deployment, runtime evidence, or state why production evidence is not applicable\.?$/i,
  /^-\s*Risk level:\s*low\s*\/\s*medium\s*\/\s*high\s*$/i,
  /^-\s*Failure mode:\s*$/i,
  /^-\s*Rollback:\s*$/i,
  /^-\s*\[\s\]\s*(Focused tests|Required CI|Review threads resolved)\s*$/i,
  /^(Closes?|Fix(?:es|ed)?|Resolves?)\s+#\s*$/i
];
function hasMeaningfulContent(content) {
  if (content === null) return false;
  const meaningfulLines = content
    .split(/\r?\n/)
    .map(line => line.trim())
    .filter(Boolean)
    .filter(line => !placeholderPatterns.some(pattern => pattern.test...

GitHub Actions: Secret Scan / gitleaks (working tree): perf: delete expired Firestore states concurrently under a bound

Conclusion: failure

View job details

##[group]Run gitleaks detect --no-git --config .gitleaks.toml --redact --verbose --exit-code 1
 �[36;1mgitleaks detect --no-git --config .gitleaks.toml --redact --verbose --exit-code 1�[0m
 shell: /usr/bin/bash -e {0}
 ##[endgroup]
     ○
     │╲
     │ ○
     ○ ░
     ░    gitleaks
 Finding:     ...gz", hash = "sha256:�[1;3;mREDACTED�[0m, size = 401824, upl...
 ***REDACTED_SECRET_ASSIGNMENT***
 RuleID:      square-access-token
 Entropy:     3.884400
 File:        uv.lock
 Line:        5129
 Fingerprint: uv.lock:square-access-***REDACTED_SECRET_ASSIGNMENT***
 �[90m9:02PM�[0m �[32mINF�[0m scan completed in 5.88s
 �[90m9:02PM�[0m �[31mWRN�[0m leaks found: 1
 ##[error]Process completed with exit code 1.

GitHub Actions: PR Governance / 0_Canonical issue and evidence.txt: perf: delete expired Firestore states concurrently under a bound

Conclusion: failure

View job details

##[group]Run actions/github-script@v8
 with:
   script: const pr = context.payload.pull_request;
const runUrl =
  `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
async function publish(conclusion, title, summary) {
  await github.rest.checks.create({
    owner: context.repo.owner,
    repo: context.repo.repo,
    name: "PR Governance",
    head_sha: pr.head.sha,
    status: "completed",
    conclusion,
    details_url: runUrl,
    output: {
      title,
      summary: summary.slice(0, 60000)
    }
  });
  if (conclusion === "failure") {
    core.setFailed(summary);
  }
}
if (pr.draft) {
  await publish(
    "neutral",
    "Governance deferred for draft PR",
    `Draft PR #${pr.number} is not enforced. The Check is bound to exact head ${pr.head.sha}.`
  );
  return;
}
const body = pr.body || "";
function getSectionContent(text, heading) {
  const escapedHeading = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  const pattern = new RegExp(
    escapedHeading + "\\s*\\n([\\s\\S]*?)(?=\\n## |$)",
    "i"
  );
  const match = text.match(pattern);
  if (!match) return null;
  return match[1].replace(/<!--[\s\S]*?-->/g, "").trim();
}
const placeholderPatterns = [
  /^Describe the user or operational result this PR produces\.?$/i,
  /^List exact automated and manual checks, tied to the current head SHA\.?$/i,
  /^Provide the Vercel preview, production deployment, runtime evidence, or state why production evidence is not applicable\.?$/i,
  /^-\s*Risk level:\s*low\s*\/\s*medium\s*\/\s*high\s*$/i,
  /^-\s*Failure mode:\s*$/i,
  /^-\s*Rollback:\s*$/i,
  /^-\s*\[\s\]\s*(Focused tests|Required CI|Review threads resolved)\s*$/i,
  /^(Closes?|Fix(?:es|ed)?|Resolves?)\s+#\s*$/i
];
function hasMeaningfulContent(content) {
  if (content === null) return false;
  const meaningfulLines = content
    .split(/\r?\n/)
    .map(line => line.trim())
    .filter(Boolean)
    .filter(line => !placeholderPatterns.some(pattern => pattern.test...

GitHub Actions: 🔍 Dependency Review / dependency-review: perf: delete expired Firestore states concurrently under a bound

Conclusion: failure

View job details

##[group]Run actions/dependency-review-action@v5
 with:
   fail-on-severity: moderate
   allow-ghsas: GHSA-w5hq-g745-h8pq
   allow-licenses: MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, Python-2.0, BlueOak-1.0.0, MPL-2.0, CC0-1.0, 0BSD
   allow-dependencies-licenses: pkg:npm/caniuse-lite, pkg:npm/@sentry/nextjs, pkg:npm/@sentry/cli, pkg:npm/@sentry/cli-darwin, pkg:npm/@sentry/cli-linux-arm, pkg:npm/@sentry/cli-linux-arm64, pkg:npm/@sentry/cli-linux-i686, pkg:npm/@sentry/cli-linux-x64, pkg:npm/@sentry/cli-win32-arm64, pkg:npm/@sentry/cli-win32-i686, pkg:npm/@sentry/cli-win32-x64, pkg:npm/@sentry/bundler-plugin-core, pkg:npm/@sentry/babel-plugin-component-annotate
   comment-summary-in-pr: always
   repo-***REDACTED_SECRET_ASSIGNMENT***
 ##[endgroup]
 ##[error]Dependency review is not supported on this repository. Please ensure that Dependency graph is enabled, see https://github.com/groupthinking/EventRelay/settings/security_analysis

GitHub Actions: 🔍 Dependency Review / 0_dependency-review.txt: perf: delete expired Firestore states concurrently under a bound

Conclusion: failure

View job details

##[group]Run actions/dependency-review-action@v5
 with:
   fail-on-severity: moderate
   allow-ghsas: GHSA-w5hq-g745-h8pq
   allow-licenses: MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, Python-2.0, BlueOak-1.0.0, MPL-2.0, CC0-1.0, 0BSD
   allow-dependencies-licenses: pkg:npm/caniuse-lite, pkg:npm/@sentry/nextjs, pkg:npm/@sentry/cli, pkg:npm/@sentry/cli-darwin, pkg:npm/@sentry/cli-linux-arm, pkg:npm/@sentry/cli-linux-arm64, pkg:npm/@sentry/cli-linux-i686, pkg:npm/@sentry/cli-linux-x64, pkg:npm/@sentry/cli-win32-arm64, pkg:npm/@sentry/cli-win32-i686, pkg:npm/@sentry/cli-win32-x64, pkg:npm/@sentry/bundler-plugin-core, pkg:npm/@sentry/babel-plugin-component-annotate
   comment-summary-in-pr: always
   repo-***REDACTED_SECRET_ASSIGNMENT***
 ##[endgroup]
 ##[error]Dependency review is not supported on this repository. Please ensure that Dependency graph is enabled, see https://github.com/groupthinking/EventRelay/settings/security_analysis

GitHub Actions: Agent completion enforcement / Agent completion enforcement: perf: delete expired Firestore states concurrently under a bound

Conclusion: failure

View job details

##[group]Run actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
 with:
   script: const fs = require('fs');
const pull = await github.rest.pulls.get({
  owner: context.repo.owner,
  repo: context.repo.repo,
  pull_number: Number(process.env.PR)
});
let verdict = {
  conclusion: 'failure',
  reason: 'verifier_did_not_publish',
  details: {}
};
try {
  verdict = JSON.parse(fs.readFileSync(
    'enforcement-verdict.json', 'utf8'
  ));
} catch (error) {
  core.warning(error.message);
}
const conclusion = verdict.conclusion === 'success'
  ? 'success'
  : 'failure';
const summary = JSON.stringify(verdict);
await github.rest.checks.create({
  owner: context.repo.owner,
  repo: context.repo.repo,
  name: 'Agent completion enforcement',
  head_sha: pull.data.head.sha,
  status: 'completed',
  conclusion,
  output: {
    title: conclusion === 'success'
      ? 'Trusted evidence verified'
      : 'Trusted evidence blocked',
    summary: summary.slice(0, 60000)
  }
});
if (conclusion !== 'success') {
  core.setFailed(verdict.reason || 'trusted evidence blocked');
}
   github-***REDACTED_SECRET_ASSIGNMENT***
   debug: false
   user-agent: actions/github-script
   result-encoding: json
   retries: 0
   retry-exempt-status-codes: 400,401,403,404,422
 env:
   PR: 1170
 ##[endgroup]
 POST /repos/groupthinking/EventRelay/check-runs - 403 with id 4803:349479:66EAA6E:695BE05:6A6E5F1E in 153ms
 RequestError [HttpError]: API rate limit exceeded for installation. If you reach out to GitHub Support for help, please include the request ID 4803:349479:66EAA6E:695BE05:6A6E5F1E and timestamp  UTC. For more on scraping GitHub and how it may affect your rights, please review our Terms of Service (https://docs.github.com/en/site-policy/github-terms/github-terms-of-service) - https://docs.github.com/en/rest/using-the-rest-api/getting-started-with-the-rest-api#rate-limiting
 ##[error]Unhandled error: HttpError: API rate limit exceeded for installation. If you reach out to GitHub Sup...

GitHub Actions: Agent completion enforcement / 0_Agent completion enforcement.txt: perf: delete expired Firestore states concurrently under a bound

Conclusion: failure

View job details

##[group]Run actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
 with:
   script: const fs = require('fs');
const pull = await github.rest.pulls.get({
  owner: context.repo.owner,
  repo: context.repo.repo,
  pull_number: Number(process.env.PR)
});
let verdict = {
  conclusion: 'failure',
  reason: 'verifier_did_not_publish',
  details: {}
};
try {
  verdict = JSON.parse(fs.readFileSync(
    'enforcement-verdict.json', 'utf8'
  ));
} catch (error) {
  core.warning(error.message);
}
const conclusion = verdict.conclusion === 'success'
  ? 'success'
  : 'failure';
const summary = JSON.stringify(verdict);
await github.rest.checks.create({
  owner: context.repo.owner,
  repo: context.repo.repo,
  name: 'Agent completion enforcement',
  head_sha: pull.data.head.sha,
  status: 'completed',
  conclusion,
  output: {
    title: conclusion === 'success'
      ? 'Trusted evidence verified'
      : 'Trusted evidence blocked',
    summary: summary.slice(0, 60000)
  }
});
if (conclusion !== 'success') {
  core.setFailed(verdict.reason || 'trusted evidence blocked');
}
   github-***REDACTED_SECRET_ASSIGNMENT***
   debug: false
   user-agent: actions/github-script
   result-encoding: json
   retries: 0
   retry-exempt-status-codes: 400,401,403,404,422
 env:
   PR: 1170
 ##[endgroup]
 POST /repos/groupthinking/EventRelay/check-runs - 403 with id 4803:349479:66EAA6E:695BE05:6A6E5F1E in 153ms
 RequestError [HttpError]: API rate limit exceeded for installation. If you reach out to GitHub Support for help, please include the request ID 4803:349479:66EAA6E:695BE05:6A6E5F1E and timestamp  UTC. For more on scraping GitHub and how it may affect your rights, please review our Terms of Service (https://docs.github.com/en/site-policy/github-terms/github-terms-of-service) - https://docs.github.com/en/rest/using-the-rest-api/getting-started-with-the-rest-api#rate-limiting
 ##[error]Unhandled error: HttpError: API rate limit exceeded for installation. If you reach out to GitHub Sup...

GitHub Actions: PR Checks / agent-completion_truth-gate: perf: delete expired Firestore states concurrently under a bound

Conclusion: failure

View job details

##[group]Run actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
 with:
   script: const owner = context.repo.owner;
const repo = context.repo.repo;
const runUrl = context.serverUrl + '/' + owner + '/' + repo +
  '/actions/runs/' + context.runId;
let prNumber = Number(process.env.INPUT_PR_NUMBER || 0);
if (!prNumber && context.payload.pull_request) {
  prNumber = context.payload.pull_request.number;
}
if (!prNumber) {
  core.setOutput('pr_number', '');
  core.setOutput('head_sha', '');
  core.setOutput('base_sha', '');
  return;
}
if (context.payload.pull_request &&
    context.payload.pull_request.head) {
  core.setOutput('pr_number', String(prNumber));
  core.setOutput(
    'head_sha',
    context.payload.pull_request.head.sha
  );
}
const pr = (await github.rest.pulls.get({
  owner,
  repo,
  pull_number: prNumber
})).data;
core.setOutput('pr_number', String(prNumber));
core.setOutput('head_sha', pr.head.sha);
core.setOutput('base_sha', pr.base.sha);
const gateContext =
  'agent-completion/truth-gate/pr-' + prNumber;
const statuses = await github.paginate(
  github.rest.repos.listCommitStatusesForRef,
  {owner, repo, ref: pr.head.sha, per_page: 100}
);
const gateStatuses = statuses.filter(status =>
  status.context === gateContext
);
if (gateStatuses.length >= 998) {
  core.setOutput('pending_status_id', '');
  core.setFailed(
    'status_capacity_exhausted: push a new head or complete `#874`'
  );
  return;
}
const pendingStatus = await github.rest.repos.createCommitStatus({
  owner,
  repo,
  sha: pr.head.sha,
  state: 'pending',
  context: gateContext,
  description: 'collecting repository evidence',
  target_url: runUrl
});
core.setOutput(
  'pending_status_id',
  String(pendingStatus.data.id)
);
   github-***REDACTED_SECRET_ASSIGNMENT***
   debug: false
   user-agent: actions/github-script
   result-encoding: json
   retries: 0
   retry-exempt-status-codes: 400,401,403,404,422
 env:
   INPUT_PR_NUMBER:
 ##[endgroup]
 GET /repos/groupthinking/Even...

GitHub Actions: PR Checks / agent-completion_truth-gate: perf: delete expired Firestore states concurrently under a bound

Conclusion: failure

View job details

##[group]Run actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
 with:
   script: const fs = require('fs');
const owner = context.repo.owner;
const repo = context.repo.repo;
const marker = '<!-- agent-completion-truth-gate:v1 -->';
const runUrlPrefix = context.serverUrl + '/' + owner + '/' +
  repo + '/actions/runs/';
const runUrl = runUrlPrefix + context.runId;
const gateContext = 'agent-completion/truth-gate/pr-' +
  process.env.PR_NUMBER;
function gateStatusDisposition(
  status,
  expectedPendingId,
  currentRunUrl,
  targetPrefix
) {
  if (!/^\d+$/.test(String(expectedPendingId || '')) ||
      !status || !/^\d+$/.test(String(status.id || ''))) {
    return 'fail_closed';
  }
  const target = String(
    (status && status.target_url) || ''
  );
  const expectedId = BigInt(String(expectedPendingId));
  const statusId = BigInt(String(status.id));
  function validRunTarget(targetUrl) {
    const value = String(targetUrl || '');
    if (!value.startsWith(targetPrefix)) {
      return false;
    }
    const suffix = value.slice(targetPrefix.length);
    return /^\d+$/.test(suffix);
  }
  function statusOwnerId(candidate) {
    if (candidate.state === 'pending') {
      return BigInt(String(candidate.id));
    }
    const owner = String(candidate.description || '').match(
      /^gate-owner:(\d+)(?:\s|$)/
    );
    return owner ? BigInt(owner[1]) : null;
  }
  if (!validRunTarget(currentRunUrl) ||
      !validRunTarget(target)) {
    return 'fail_closed';
  }
  const ownerId = statusOwnerId(status);
  if (ownerId === null) {
    return 'fail_closed';
  }
  if (ownerId === expectedId && target === currentRunUrl) {
    if (statusId === expectedId &&
        status.state === 'pending') {
      return 'current_pending';
    }
    if (['failure', 'error'].includes(status.state)) {
      return 'already_failed';
    }
    if (status.state === 'success') {
      return 'already_succeeded';
    }
    return 'fail_closed';
  }
  if (target === currentRunUrl) {...

GitHub Actions: PR Checks / agent-completion_truth-gate: perf: delete expired Firestore states concurrently under a bound

Conclusion: failure

View job details

##[group]Run actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
 with:
   name: agent-completion-verdict-1170
   path: gate-input.json
gate-verdict.json
   if-no-files-found: error
   compression-level: 6
   overwrite: false
   include-hidden-files: false
   archive: true
 ##[endgroup]
 Multiple search paths detected. Calculating the least common ancestor of all paths
 The least common ancestor is /home/runner/work/EventRelay/EventRelay. This will be the root directory of the artifact
 ##[error]No files were found with the provided path: gate-input.json

GitHub Actions: PR Checks / agent-completion_truth-gate: perf: delete expired Firestore states concurrently under a bound

Conclusion: failure

View job details

##[group]Run actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
 with:
   script: const owner = context.repo.owner;
const repo = context.repo.repo;
const runUrlPrefix = context.serverUrl + '/' + owner + '/' +
  repo + '/actions/runs/';
const runUrl = runUrlPrefix + context.runId;
const gateContext = 'agent-completion/truth-gate/pr-' +
  process.env.PR_NUMBER;
function gateStatusDisposition(
  status,
  expectedPendingId,
  currentRunUrl,
  targetPrefix
) {
  if (!/^\d+$/.test(String(expectedPendingId || '')) ||
      !status || !/^\d+$/.test(String(status.id || ''))) {
    return 'fail_closed';
  }
  const target = String(
    (status && status.target_url) || ''
  );
  const expectedId = BigInt(String(expectedPendingId));
  const statusId = BigInt(String(status.id));
  function validRunTarget(targetUrl) {
    const value = String(targetUrl || '');
    if (!value.startsWith(targetPrefix)) {
      return false;
    }
    const suffix = value.slice(targetPrefix.length);
    return /^\d+$/.test(suffix);
  }
  function statusOwnerId(candidate) {
    if (candidate.state === 'pending') {
      return BigInt(String(candidate.id));
    }
    const owner = String(candidate.description || '').match(
      /^gate-owner:(\d+)(?:\s|$)/
    );
    return owner ? BigInt(owner[1]) : null;
  }
  if (!validRunTarget(currentRunUrl) ||
      !validRunTarget(target)) {
    return 'fail_closed';
  }
  const ownerId = statusOwnerId(status);
  if (ownerId === null) {
    return 'fail_closed';
  }
  if (ownerId === expectedId && target === currentRunUrl) {
    if (statusId === expectedId &&
        status.state === 'pending') {
      return 'current_pending';
    }
    if (['failure', 'error'].includes(status.state)) {
      return 'already_failed';
    }
    if (status.state === 'success') {
      return 'already_succeeded';
    }
    return 'fail_closed';
  }
  if (target === currentRunUrl) {
    return 'fail_closed';
  }
  if (ownerId > expectedId) {
    return 'successor';...

GitHub Actions: PR Checks / validate: perf: delete expired Firestore states concurrently under a bound

Conclusion: failure

View job details

##[group]Run actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
 with:
   script: const pr = context.payload.pull_request;
const findings = [];
if (pr.title.length < 10) {
  findings.push('❌ PR title too short (minimum 10 characters)');
}
if (!/^(?:⚡\s*)?(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?:/i.test(pr.title)) {
  findings.push('⚠️ PR title should follow conventional commits format');
}
if (!pr.body || pr.body.length < 20) {
  findings.push('❌ PR description is required (minimum 20 characters)');
}
const totalChanges = (pr.additions || 0) + (pr.deletions || 0);
if (totalChanges > 500) {
  findings.push('⚠️ Large PR detected (' + totalChanges + ' lines changed)');
}
const marker = '<!-- pr-validation:v1 -->';
const comments = await github.paginate(
  github.rest.issues.listComments,
  {owner: context.repo.owner, repo: context.repo.repo, issue_number: pr.number, per_page: 100}
);
const existing = comments.find(comment =>
  comment.user &&
  comment.user.login === 'github-actions[bot]' &&
  comment.body && comment.body.includes(marker)
);
if (findings.length === 0) {
  if (existing) {
    await github.rest.issues.updateComment({
      owner: context.repo.owner,
      repo: context.repo.repo,
      comment_id: existing.id,
      body: marker + '\n## 🔍 PR Validation\n\n' +
        '✅ Current validation passed.'
    });
  }
  return;
}
const body = marker + '\n## 🔍 PR Validation\n\n' + findings.join('\n');
if (existing) {
  await github.rest.issues.updateComment({
    owner: context.repo.owner,
    repo: context.repo.repo,
    comment_id: existing.id,
    body
  });
} else {
  await github.rest.issues.createComment({
    owner: context.repo.owner,
    repo: context.repo.repo,
    issue_number: pr.number,
    body
  });
}
if (findings.some(finding => finding.startsWith('❌'))) {
  core.setFailed('PR validation failed');
}
   github-***REDACTED_SECRET_ASSIGNMENT***
   debug: false
   user-agent: actions/github-script
   resu...

GitHub Actions: PR Checks / agent-completion_truth-gate: perf: delete expired Firestore states concurrently under a bound

Conclusion: failure

View job details

##[group]Run exit 1
 �[36;1mexit 1�[0m
 shell: /usr/bin/bash -e {0}
 ##[endgroup]
 ##[error]Process completed with exit code 1.

GitHub Actions: PR Checks / 2_validate.txt: perf: delete expired Firestore states concurrently under a bound

Conclusion: failure

View job details

##[group]Run actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
 with:
   script: const pr = context.payload.pull_request;
const findings = [];
if (pr.title.length < 10) {
  findings.push('❌ PR title too short (minimum 10 characters)');
}
if (!/^(?:⚡\s*)?(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?:/i.test(pr.title)) {
  findings.push('⚠️ PR title should follow conventional commits format');
}
if (!pr.body || pr.body.length < 20) {
  findings.push('❌ PR description is required (minimum 20 characters)');
}
const totalChanges = (pr.additions || 0) + (pr.deletions || 0);
if (totalChanges > 500) {
  findings.push('⚠️ Large PR detected (' + totalChanges + ' lines changed)');
}
const marker = '<!-- pr-validation:v1 -->';
const comments = await github.paginate(
  github.rest.issues.listComments,
  {owner: context.repo.owner, repo: context.repo.repo, issue_number: pr.number, per_page: 100}
);
const existing = comments.find(comment =>
  comment.user &&
  comment.user.login === 'github-actions[bot]' &&
  comment.body && comment.body.includes(marker)
);
if (findings.length === 0) {
  if (existing) {
    await github.rest.issues.updateComment({
      owner: context.repo.owner,
      repo: context.repo.repo,
      comment_id: existing.id,
      body: marker + '\n## 🔍 PR Validation\n\n' +
        '✅ Current validation passed.'
    });
  }
  return;
}
const body = marker + '\n## 🔍 PR Validation\n\n' + findings.join('\n');
if (existing) {
  await github.rest.issues.updateComment({
    owner: context.repo.owner,
    repo: context.repo.repo,
    comment_id: existing.id,
    body
  });
} else {
  await github.rest.issues.createComment({
    owner: context.repo.owner,
    repo: context.repo.repo,
    issue_number: pr.number,
    body
  });
}
if (findings.some(finding => finding.startsWith('❌'))) {
  core.setFailed('PR validation failed');
}
   github-***REDACTED_SECRET_ASSIGNMENT***
   debug: false
   user-agent: actions/github-script
   resu...

GitHub Actions: PR Checks / 0_agent-completion_truth-gate.txt: perf: delete expired Firestore states concurrently under a bound

Conclusion: failure

View job details

##[group]Run actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
 with:
   script: const owner = context.repo.owner;
const repo = context.repo.repo;
const runUrl = context.serverUrl + '/' + owner + '/' + repo +
  '/actions/runs/' + context.runId;
let prNumber = Number(process.env.INPUT_PR_NUMBER || 0);
if (!prNumber && context.payload.pull_request) {
  prNumber = context.payload.pull_request.number;
}
if (!prNumber) {
  core.setOutput('pr_number', '');
  core.setOutput('head_sha', '');
  core.setOutput('base_sha', '');
  return;
}
if (context.payload.pull_request &&
    context.payload.pull_request.head) {
  core.setOutput('pr_number', String(prNumber));
  core.setOutput(
    'head_sha',
    context.payload.pull_request.head.sha
  );
}
const pr = (await github.rest.pulls.get({
  owner,
  repo,
  pull_number: prNumber
})).data;
core.setOutput('pr_number', String(prNumber));
core.setOutput('head_sha', pr.head.sha);
core.setOutput('base_sha', pr.base.sha);
const gateContext =
  'agent-completion/truth-gate/pr-' + prNumber;
const statuses = await github.paginate(
  github.rest.repos.listCommitStatusesForRef,
  {owner, repo, ref: pr.head.sha, per_page: 100}
);
const gateStatuses = statuses.filter(status =>
  status.context === gateContext
);
if (gateStatuses.length >= 998) {
  core.setOutput('pending_status_id', '');
  core.setFailed(
    'status_capacity_exhausted: push a new head or complete `#874`'
  );
  return;
}
const pendingStatus = await github.rest.repos.createCommitStatus({
  owner,
  repo,
  sha: pr.head.sha,
  state: 'pending',
  context: gateContext,
  description: 'collecting repository evidence',
  target_url: runUrl
});
core.setOutput(
  'pending_status_id',
  String(pendingStatus.data.id)
);
   github-***REDACTED_SECRET_ASSIGNMENT***
   debug: false
   user-agent: actions/github-script
   result-encoding: json
   retries: 0
   retry-exempt-status-codes: 400,401,403,404,422
 env:
   INPUT_PR_NUMBER:
 ##[endgroup]
 GET /repos/groupthinking/Even...

Commit Status: Vercel: Vercel

Conclusion: failure

Canceled from the Vercel Dashboard
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{py,js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{py,js,jsx,ts,tsx}: Use Python 3.9+ and Node 18+ for development
Never hardcode API keys, database URLs, or secrets in code
Make minimal, surgical changes and avoid deleting working code unless fixing security issues

Files:

  • src/youtube_extension/services/cloud/firestore_state.py
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.py: Always use type hints for Python functions
Use Black formatter with 88 character line length for Python code
Follow PEP 8 conventions for Python code
Use descriptive variable names and add docstrings to all public functions in Python
Group imports in Python: standard library, third-party, local
Use SQLAlchemy ORM and never write raw SQL queries
Use environment variables via os.getenv() or pydantic-settings to access configuration
Wrap database operations in try-except blocks and use context managers or FastAPI dependencies for connection cleanup
Implement comprehensive error handling with proper logging in all functions
Version APIs using /api/v1/ prefix for stability
Use JSON-RPC 2.0 protocol for all MCP communication
Follow the single-flow workflow: YouTube link → context extraction → agent dispatch → outputs
Use context managers for resource management in Python code
Implement comprehensive input validation and sanitize outputs for security
Use parameterized queries and SQLAlchemy ORM to prevent SQL injection
Define API request/response models using Pydantic for FastAPI endpoints
Store the single unified workflow as the only workflow; never introduce alternate flows or manual triggers
Use SQLAlchemy with connection pooling for database connections and manage sessions with context managers
Provide sensible defaults for non-sensitive configuration in Python settings
Maintain backward compatibility and do not break existing API endpoints
Include comprehensive logging for debugging in MCP implementations

Files:

  • src/youtube_extension/services/cloud/firestore_state.py

⚙️ CodeRabbit configuration file

Python backend code. Check for type hints, proper exception handling, async context manager usage, and potential blocking calls in async functions. Flag any bare except clauses or missing timeout parameters on network calls. CRITICAL: Flag any file that contains placeholder/stub implementations — especially in unified_ai_sdk. Any class or function that says "TODO: Replace with production implementation" or returns mock/fake data must be flagged as a blocking issue. Flag any code generation output that reaches users without AST validation or syntax checking.

Files:

  • src/youtube_extension/services/cloud/firestore_state.py
**/*.{py,js,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Maintain >80% code coverage for new features

Files:

  • src/youtube_extension/services/cloud/firestore_state.py
**/*.{py,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{py,ts,tsx}: Keep frontend and backend data models synchronized using matching Pydantic (backend) and TypeScript (frontend) interfaces
Use type-safe interfaces for backend-frontend data exchange

**/*.{py,ts,tsx}: Production code must use real behavior only: no mock delays, fake data, or simulated responses.
Maintain strict type safety: mypy strict mode for Python and TypeScript strict mode for the frontend.
Name events using the <domain>.<entity>.<action> format.

Files:

  • src/youtube_extension/services/cloud/firestore_state.py
**/*

📄 CodeRabbit inference engine (Custom checks)

**/*: Strictly verify that GitHub Copilot has explicitly reviewed and approved the pull request; human approvals alone must not satisfy this check.
Before allowing a merge, require the copilot-rabbit label and AI-generated unit tests committed alongside the code changes; fail the check if either is missing.

For Vercel-specific work, include https://vercel.com/docs/llms-full.txt in the AI assistant context set.

Files:

  • src/youtube_extension/services/cloud/firestore_state.py
**/*.{py,pyw}

📄 CodeRabbit inference engine (AGENTS.md)

Write Python code to remain compatible with Linux and Windows where possible, including correct handling of asyncio event loops.

Files:

  • src/youtube_extension/services/cloud/firestore_state.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: Format Python code with Black using an 88-character line length.
Sort Python imports with isort using the Black profile.
Run Ruff with E, W, F, I, B, C4, and UP rules; E501 is ignored.
Use mypy strict mode; untyped function definitions are disallowed.
Target Python 3.9 or newer.
Validate backend inputs with Pydantic and sanitize subprocess arguments.
Use Anthropic SDK features thinking={"type": "adaptive"} and output_config={"effort": "..."} with anthropic>=0.105.0; do not add TypeError fallbacks for these parameters.

Use the service container dependency-injection pattern in backend/containers/.

Files:

  • src/youtube_extension/services/cloud/firestore_state.py
**/*.{py,ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not commit secrets; store keys and credentials in gitignored .env files.

Files:

  • src/youtube_extension/services/cloud/firestore_state.py
**/*.{py,pyi}

📄 CodeRabbit inference engine (GEMINI.md)

**/*.{py,pyi}: Use Black formatting with an 88-character line length for Python code.
Use Ruff with rules E, W, F, I, B, C4, and UP for Python linting.
Use strict mypy type checking; do not define untyped functions.
Target Python 3.9 or newer.
Validate Python inputs with Pydantic.
Sanitize subprocess arguments before execution.
Do not add mock delays or fake data to production Python code; production runs in REAL_MODE_ONLY.
Keep secrets out of Python source code; load keys and credentials from environment variables instead.
Use absolute imports that resolve with PYTHONPATH=src in the Python backend.

Files:

  • src/youtube_extension/services/cloud/firestore_state.py
**/*.{py,pyi,ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

**/*.{py,pyi,ts,tsx}: Preserve the single workflow: YouTube link → transcript → events → agents → outputs; do not introduce alternative flows or manual triggers that bypass it.
Use event names following <domain>.<entity>.<action>, such as youtube.video.captured.
Make surgical, precise changes and do not delete working code without justification.

Files:

  • src/youtube_extension/services/cloud/firestore_state.py
🔍 Remote MCP GitHub Copilot, Linear

Additional review context

  • PR #1170 is groupthinking/EventRelay, head db083b6, based on main; it changes only firestore_state.py and its unit tests.
  • The linked issue requires bounded concurrent deletes, failure isolation, successful-delete counting, and non-vacuous regression tests. Linear issue GRV-215 remains in Triage.
  • The final implementation creates at most 16 worker tasks, each issuing one delete at a time. Shared-iterator access occurs without an await, and ordinary delete exceptions are recorded while workers continue.
  • Prior review correctly identified that the initial gather-plus-semaphore design bounded RPCs but still allocated one task per document; commit db083b6 replaced it with the worker pool.
  • Test coverage nuance: the failure-isolation test uses only three documents, so min(16, 3) workers process them concurrently. It proves a later document is not abandoned, but does not specifically prove that the same worker continues processing after a failure; a backlog larger than 16 or a patched concurrency of 1 would cover that path more directly.
  • Pre-existing watchpoint: states are written with ISO-format string created_at, while cleanup compares created_at against a numeric Unix timestamp. This mismatch exists on main and is outside this PR’s stated scope.
  • The constant’s comment still says deletes are fanned out “under a semaphore,” although the final code uses a worker pool.
  • At retrieval time, build and Python/frontend lint checks passed, while test/coverage checks were still running; several governance/security-related checks were red. The PR discussion attributes the agent-completion failure to repository trust-policy provisioning rather than code.
🔇 Additional comments (1)
src/youtube_extension/services/cloud/firestore_state.py (1)

10-10: LGTM!

Comment thread src/youtube_extension/services/cloud/firestore_state.py Outdated
Comment thread src/youtube_extension/services/cloud/firestore_state.py
Comment thread src/youtube_extension/services/cloud/firestore_state.py

Copy link
Copy Markdown
Owner Author

Automated remediation pass (re-scan of head db083b6) — terminal state: HALTED(awaiting_merge_approval)

Correcting the earlier pass, which reported only 2 red checks — there are 6, and none is caused by this diff. The code is review-clean (both Copilot findings resolved, CodeRabbit's focused re-review found nothing, tests non-vacuous and passing).

Failing check Actual cause Introduced by this PR?
gitleaks (working tree) square-access-token rule matched a sha256: integrity hash at uv.lock:5129 — not a Square token. uv.lock is not in this PR's diff (only firestore_state.py + its test changed), and gitleaks --no-git scans the whole tree, so this is red on every PR. Pre-existing. No
validate API rate limit exceeded for installation — HTTP 403, x-ratelimit-remaining: 0 / used: 5000. No
Canonical issue and evidence Same 403 installation rate-limit (GET issue #1169). No
Agent completion enforcement Same 403 installation rate-limit. No
agent-completion/truth-gate Same 403 rate-limit — its own verdict comment is NOT_APPLICABLE / evidence agrees. No
dependency-review Snapshot warning only; its comment reports "✅ No vulnerabilities or license issues". No dependency change in this diff. No

What this means: four of the six red checks are the repo's own governance workflows failing on a shared GitHub App-installation API rate-limit (5000/hr exhausted), not on anything in the code. A re-run after the quota resets should clear those four. gitleaks needs a repo-level decision (allowlist the uv.lock sha256 false-positive in .gitleaks.toml, or scope the scan to changed files) — out of scope for this perf PR, and I'm deliberately not editing a secret-scanner allowlist autonomously.

No merge performedmain is protected, no automerge label, publish is human-gated. Staged command once you accept the non-code checks above:

gh pr merge 1170 --repo groupthinking/EventRelay --squash

Generated by Claude Code

The failure-isolation test used 3 documents against a pool of
CLEANUP_DELETE_CONCURRENCY=16, so min(16, 3) = 3 workers each handled
exactly one document. The `while True` continuation path was never taken,
which made the test vacuous: a worker that returned on its first exception
instead of continuing to drain the shared iterator would still have
satisfied every assertion.

Narrow the pool to a single worker against a 5-document backlog so the
worker whose delete raises must keep pulling. Verified non-vacuous: with
`return` added to the worker's except branch the test fails with
`assert 0 == (5 - 1)`.

Also correct the CLEANUP_DELETE_CONCURRENCY comment, which still described
a semaphore-gated fan-out rather than the fixed worker pool that replaced it.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@groupthinking

Copy link
Copy Markdown
Owner Author

Thanks — finding 1 was a real defect and I've fixed it. Findings 2 and 3 I'm
deferring, with reasons below.

# Finding Verdict Where
1 Failure-isolation test never exercises worker continuation Accepted — fixed 7558f03
2 Stale CLEANUP_DELETE_CONCURRENCY comment (says "semaphore") Accepted — fixed 7558f03
3 Make CLEANUP_DELETE_CONCURRENCY env-configurable ⏭️ Deferred #1180
4 Explicit timeout on doc.reference.delete() ⏭️ Deferred #1180

1. Test was vacuous — you were right, and it was worse than "weak"

The test used 3 documents against CLEANUP_DELETE_CONCURRENCY = 16, so
min(16, 3) = 3 workers each pulled exactly one document and returned on the
next StopIteration. The while True continuation branch was never
executed
. A worker written like this:

except Exception as exc:
    failures.append(exc)
    return          # abandons the rest of its queue

would have satisfied every assertion in that test.

Fixed by narrowing the pool to a single worker over a 5-document backlog, so the
worker that fails is the only one that can drain the remainder:

with patch.object(_mod, "CLEANUP_DELETE_CONCURRENCY", 1):
    count = await svc.cleanup_old_states(days=7)

assert count == doc_count - 1
for doc in docs:
    doc.reference.delete.assert_awaited_once()

Verified non-vacuous. With the return mutation above applied to the worker:

>       assert count == doc_count - 1
E       assert 0 == (5 - 1)
FAILED tests/unit/test_firestore_state.py::TestCleanupOldStates::test_cleanup_failure_does_not_abandon_remaining_deletes

I picked pool size 1 rather than "pool 2, docs 9" deliberately: with 2+ workers a
surviving sibling drains the backlog anyway, so all documents are still attempted
and the assertion stays green even for the broken worker. One worker is the only
arrangement that isolates the continuation path.

2. Comment corrected

It still described the semaphore-gated fan-out from the previous revision. Now
describes the fixed pool and states that sizing the pool bounds in-flight RPCs
and allocated tasks by the same constant.

3 & 4. Deferred to #1180 — reasons

Env-configurable constant. The equivalent constant merged in ad7e2c1
(TAG_WRITE_CONCURRENCY = 8 in intelligent_cache.py) is also a plain module
constant; making only this one configurable is inconsistent. And a bare
int(os.getenv(...)) raises ValueError at import time on a malformed
value, turning a typo into an unimportable module — it needs a validating parse
with a fallback, which is its own change with its own tests.

Explicit delete timeout. Your comment suggested using "the surrounding
cleanup configuration or established timeout constant" — there isn't one:
grep -n 'timeout\|TIMEOUT' firestore_state.py returns nothing. So this means
inventing a deadline policy, layered on top of the Firestore async client's own
retry/deadline config; choosing a value blind risks reclassifying slow-but-
successful deletes as tallied failures. The pre-existing sequential loop had no
timeout either, so it isn't a regression from this PR. The right fix is one
documented deadline applied to all Firestore RPCs in the service, not just
cleanup.

Both are captured in #1180 with acceptance criteria.


Validation at 7558f03

118 passed in 2.45s      # test_firestore_state.py + test_cloud_video_processor.py
ruff: 1 error            # F841 at test_firestore_state.py:528 — pre-existing on main

@coderabbitai review

Two things I'd like checked on this commit:

  1. Does the single-worker arrangement fully cover the continuation path, or is
    there a failure mode reachable only with 2+ workers that I've now stopped
    testing? The concurrency bounds are covered separately by
    test_cleanup_bounds_allocated_delete_tasks_not_just_rpcs.
  2. patch.object(_mod, "CLEANUP_DELETE_CONCURRENCY", 1) relies on
    cleanup_old_states reading the module global at call time rather than
    binding it at import. Please confirm that's actually true of the current code.

If you're satisfied, please @coderabbitai resolve so the blocking review clears.

@groupthinking
groupthinking marked this pull request as draft August 1, 2026 21:14
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '== Checked-out commit =='
git rev-parse HEAD

printf '%s\n' '== Source structure =='
ast-grep outline src/youtube_extension/services/cloud/firestore_state.py --items all --match 'cleanup|CLEANUP'
printf '%s\n' '== Test structure =='
ast-grep outline tests/unit/test_firestore_state.py --items all --match 'cleanup|Cleanup'

printf '%s\n' '== Cleanup implementation =='
rg -n -C 18 'CLEANUP_DELETE_CONCURRENCY|async def cleanup_old_states' \
  src/youtube_extension/services/cloud/firestore_state.py

printf '%s\n' '== Relevant cleanup tests =='
rg -n -C 22 'test_cleanup_(failure_does_not_abandon_remaining_deletes|bounds_allocated_delete_tasks_not_just_rpcs|deletes_overlap|bounds)|patch\.object\(_mod, "CLEANUP_DELETE_CONCURRENCY"' \
  tests/unit/test_firestore_state.py

printf '%s\n' '== Diff for commit 7558f03 when available =='
git show --no-ext-diff --format=fuller --stat 7558f03
git diff --no-ext-diff --unified=30 7558f03^ 7558f03 -- \
  src/youtube_extension/services/cloud/firestore_state.py \
  tests/unit/test_firestore_state.py

Length of output: 6719


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '== Candidate Firestore state test files =='
fd -i 'test_firestore_state.py' . -t f
fd -i '*firestore*state*.py' . -t f

printf '%s\n' '== Cleanup test definitions and patch sites =='
rg -n -P --type py -C 24 \
  '^\s*async\s+def\s+test_cleanup|patch\.object\(\s*_mod\s*,\s*"CLEANUP_DELETE_CONCURRENCY"|cleanup_old_states\s*\(' \
  .

printf '%s\n' '== Commit diff for all Python test files =='
git diff --no-ext-diff --unified=40 7558f03^ 7558f03 -- '*.py'

Length of output: 1153


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '== Candidate test files =='
fd -i 'firestore' . -t f | rg -n '(^|/)(test|tests?)[^/]*\.py$|_test\.py$'
fd -i 'state' . -t f | rg -n '(^|/)(test|tests?)[^/]*\.py$|_test\.py$'

printf '%s\n' '== Cleanup test definitions and patch sites =='
rg -n -P --type py -C 28 \
  '^\s*async\s+def\s+test_.*cleanup|patch\.object\(\s*_mod\s*,\s*"CLEANUP_DELETE_CONCURRENCY"|cleanup_old_states\s*\(' \
  .

printf '%s\n' '== Commit diff for Python files =='
git diff --no-ext-diff --unified=40 7558f03^ 7558f03 -- '*.py'

Length of output: 885


🏁 Script executed:

#!/bin/bash
set -u

printf '%s\n' '== Python test paths in HEAD =='
git ls-tree -r --name-only HEAD | rg -n '(^|/)(test[^/]*|[^/]*_test)\.py$' || true

printf '%s\n' '== Paths changed by 7558f03 =='
git diff-tree --no-commit-id --name-status -r 7558f03

printf '%s\n' '== All paths containing firestore or cleanup =='
git ls-tree -r --name-only HEAD | rg -in 'firestore|cleanup' || true

printf '%s\n' '== Cleanup-related test source from Git objects =='
while IFS= read -r path; do
  printf '\n--- %s ---\n' "$path"
  git show "HEAD:$path" | rg -n -C 30 \
    'test_cleanup_failure_does_not_abandon_remaining_deletes|CLEANUP_DELETE_CONCURRENCY|cleanup_old_states' || true
done < <(git ls-tree -r --name-only HEAD | rg '(^|/)(test[^/]*|[^/]*_test)\.py$')

Length of output: 14168


🏁 Script executed:

#!/bin/bash
set -euo pipefail

test_path='tests/unit/test_firestore_state.py'

printf '%s\n' '== Cleanup test blocks at HEAD =='
git show "HEAD:${test_path}" | rg -n -C 38 \
  'test_cleanup_failure_does_not_abandon_remaining_deletes|test_cleanup_bounds_allocated_delete_tasks_not_just_rpcs|test_cleanup_deletes_overlap|patch\.object\(_mod, "CLEANUP_DELETE_CONCURRENCY"|cleanup_old_states\s*\('

printf '%s\n' '== Exact test change in 7558f03 =='
git diff --no-ext-diff --unified=60 7558f03^ 7558f03 -- "${test_path}"

printf '%s\n' '== Import and fixture context =='
git show "HEAD:${test_path}" | sed -n '1,140p'

Length of output: 24458


@groupthinking Verified at 7558f03.

The single-worker test fully covers the worker-continuation path. The first delete fails, and the same worker must continue through four remaining documents. A worker that returns from except Exception fails both the successful-delete count and the per-document await assertions.

A separate failure test with two or more workers is not required for this guarantee. The worker count does not change the exception handler or loop continuation. The existing tests already cover multi-worker overlap, in-flight RPC bounds, and task-allocation bounds.

patch.object(_mod, "CLEANUP_DELETE_CONCURRENCY", 1) is valid. cleanup_old_states() reads the module global when it executes range(min(CLEANUP_DELETE_CONCURRENCY, len(docs))). It does not bind the value at import time.

No remaining code-level finding.

🐇 ✅

[resolve]


✏️ Learnings added
Learnt from: groupthinking
URL: https://github.com/groupthinking/EventRelay/pull/1170

Timestamp: 2026-08-01T21:15:08.246Z
Learning: In `tests/unit/test_firestore_state.py`, `TestCleanupOldStates.test_cleanup_failure_does_not_abandon_remaining_deletes()` patches `youtube_extension.services.cloud.firestore_state.CLEANUP_DELETE_CONCURRENCY` to `1` and uses a five-document backlog with the first delete raising `RuntimeError`. This configuration directly verifies that the same `FirestoreStateService.cleanup_old_states()` worker continues after an ordinary delete failure and attempts every remaining document.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

You are interacting with an AI system.

Copy link
Copy Markdown
Owner Author

@coderabbitai review

Exact-head independent review requested for 7558f0341f8da99452c7ca969b67635358db4662; PR remains draft pending review evidence.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

expected='7558f0341f8da99452c7ca969b67635358db4662'
actual="$(git rev-parse HEAD)"

printf '%s\n' '== Exact head verification =='
printf 'expected: %s\nactual:   %s\n' "$expected" "$actual"
if [ "$actual" != "$expected" ]; then
  printf '%s\n' 'ERROR: checkout does not match the requested commit' >&2
  exit 1
fi

printf '%s\n' '== Pull request files at exact head =='
git diff-tree --no-commit-id --name-status -r "$actual"
git diff --no-ext-diff --check "${actual}^" "$actual"

printf '%s\n' '== Relevant source and test structure =='
ast-grep outline src/youtube_extension/services/cloud/firestore_state.py --items all --match 'CLEANUP|cleanup'
ast-grep outline tests/unit/test_firestore_state.py --items all --match 'Cleanup|cleanup'

printf '%s\n' '== Exact implementation =='
rg -n -C 28 --type py \
  'CLEANUP_DELETE_CONCURRENCY|async def cleanup_old_states|def cleanup_old_states' \
  src/youtube_extension/services/cloud/firestore_state.py

printf '%s\n' '== Exact cleanup tests =='
rg -n -C 35 --type py \
  'async def test_cleanup|CLEANUP_DELETE_CONCURRENCY|asyncio\.all_tasks|cleanup_old_states\s*\(' \
  tests/unit/test_firestore_state.py

printf '%s\n' '== Exact commit diff =='
git diff --no-ext-diff --unified=60 "${actual}^" "$actual" -- \
  src/youtube_extension/services/cloud/firestore_state.py \
  tests/unit/test_firestore_state.py

Length of output: 8595


🏁 Script executed:

#!/bin/bash
set -euo pipefail

commit='7558f0341f8da99452c7ca969b67635358db4662'

printf '%s\n' '== Changed paths at requested commit =='
git diff-tree --no-commit-id --name-status -r "$commit"

printf '%s\n' '== Tracked Firestore cleanup tests =='
git ls-tree -r --name-only "$commit" |
  rg -i '(^|/)(test[^/]*firestore[^/]*\.py|[^/]*firestore[^/]*test\.py|test[^/]*state[^/]*\.py|[^/]*state[^/]*test\.py)$'

printf '%s\n' '== Locate all cleanup test definitions in tracked Python tests =='
while IFS= read -r path; do
  git show "${commit}:${path}" |
    rg -q 'cleanup_old_states|test_cleanup' || continue
  printf '\n--- %s ---\n' "$path"
  git show "${commit}:${path}" |
    rg -n -C 40 \
      'async def test_.*cleanup|def test_.*cleanup|CLEANUP_DELETE_CONCURRENCY|asyncio\.all_tasks|cleanup_old_states\s*\('
done < <(git ls-tree -r --name-only "$commit" | rg '(^|/)(test[^/]*|[^/]*_test)\.py$')

printf '%s\n' '== Parent-to-head diff for changed Python tests =='
while IFS= read -r path; do
  case "$path" in
    *.py)
      printf '\n--- %s ---\n' "$path"
      git diff --no-ext-diff --unified=60 "${commit}^" "$commit" -- "$path"
      ;;
  esac
done < <(git diff-tree --no-commit-id --name-only -r "$commit")

Length of output: 1856


🏁 Script executed:

#!/bin/bash
set -euo pipefail

commit='7558f0341f8da99452c7ca969b67635358db4662'
test_path='tests/unit/test_firestore_state.py'
source_path='src/youtube_extension/services/cloud/firestore_state.py'

printf '%s\n' '== Cleanup tests at requested commit =='
git show "${commit}:${test_path}" |
  rg -n -C 45 \
    'async def test_.*cleanup|def test_.*cleanup|CLEANUP_DELETE_CONCURRENCY|asyncio\.all_tasks|cleanup_old_states\s*\('

printf '%s\n' '== Test module imports and class context =='
git show "${commit}:${test_path}" | sed -n '1,180p'

printf '%s\n' '== Exact parent-to-head test diff =='
git diff --no-ext-diff --unified=80 "${commit}^" "$commit" -- "$test_path"

printf '%s\n' '== Full cleanup implementation from requested commit =='
git show "${commit}:${source_path}" | sed -n '314,376p'

printf '%s\n' '== Parent-to-head source diff =='
git diff --no-ext-diff --unified=80 "${commit}^" "$commit" -- "$source_path"

Length of output: 38232


🏁 Script executed:

#!/bin/bash
set -euo pipefail

head='7558f0341f8da99452c7ca969b67635358db4662'
base="$(git merge-base "$head" main)"

printf '%s\n' '== Pull request range =='
printf 'base: %s\nhead: %s\n' "$base" "$head"

printf '%s\n' '== All changed files from merge base =='
git diff --name-status "$base" "$head"
git diff --no-ext-diff --check "$base" "$head"

printf '%s\n' '== Full source change from merge base =='
git diff --no-ext-diff --unified=80 "$base" "$head" -- \
  src/youtube_extension/services/cloud/firestore_state.py

printf '%s\n' '== Full cleanup-test change from merge base =='
git diff --no-ext-diff --unified=80 "$base" "$head" -- \
  tests/unit/test_firestore_state.py

printf '%s\n' '== Python version constraints =='
fd -HI 'pyproject.toml|setup.cfg|setup.py|tox.ini|\.python-version' . -t f -d 3 \
  -x sh -c 'printf "\n--- %s ---\n" "$1"; sed -n "1,220p" "$1"' sh {}

Length of output: 47624


@groupthinking Independent review completed for exact head 7558f0341f8da99452c7ca969b67635358db4662.

I found no code-level findings.

  • The complete PR range changes only firestore_state.py and test_firestore_state.py.
  • The worker pool bounds worker tasks and in-flight delete RPCs.
  • The lock-free next(pending) call is safe. It executes before any await.
  • The failure handler catches ordinary Exception failures, records them, and continues processing.
  • CancelledError and other BaseException control-flow exceptions propagate.
  • The single-worker regression test directly covers worker continuation after a delete failure.
  • patch.object(_mod, "CLEANUP_DELETE_CONCURRENCY", 1) works because the method reads the module global at call time.

I verified the diff statically. The sandbox could not run the test suite.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Owner Author

@coderabbitai review

Re-review exact head 57fc58d137ecb718980ff62b2de59e7aa4edc65b. The environment-configurable worker limit, explicit Firestore delete timeout, and timeout regression coverage address the two remaining findings.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

expected='57fc58d137ecb718980ff62b2de59e7aa4edc65b'
actual="$(git rev-parse HEAD)"
printf 'expected: %s\nactual:   %s\n' "$expected" "$actual"
test "$actual" = "$expected"

base="$(git merge-base "$actual" main)"
previous='7558f0341f8da99452c7ca969b67635358db4662'

printf '\n== Complete PR range ==\n'
printf 'base: %s\nhead: %s\n' "$base" "$actual"
git diff --no-ext-diff --check "$base" "$actual"
git diff --name-status "$base" "$actual"

printf '\n== Change since prior reviewed head ==\n'
git diff --no-ext-diff --check "$previous" "$actual"
git diff --no-ext-diff --unified=80 "$previous" "$actual" -- \
  src/youtube_extension/services/cloud/firestore_state.py \
  tests/unit/test_firestore_state.py

printf '\n== Source structure ==\n'
ast-grep outline src/youtube_extension/services/cloud/firestore_state.py --items all --match 'CLEANUP|cleanup|timeout'

printf '\n== Implementation context ==\n'
rg -n -C 32 --type py \
  'CLEANUP_DELETE_CONCURRENCY|CLEANUP.*TIMEOUT|async def cleanup_old_states|\.delete\s*\(' \
  src/youtube_extension/services/cloud/firestore_state.py

printf '\n== Test structure and relevant tests ==\n'
ast-grep outline tests/unit/test_firestore_state.py --items all --match 'Cleanup|cleanup|timeout'
rg -n -C 38 --type py \
  'async def test_.*cleanup|CLEANUP_DELETE_CONCURRENCY|CLEANUP.*TIMEOUT|timeout|cleanup_old_states\s*\(' \
  tests/unit/test_firestore_state.py

printf '\n== Environment parsing patterns in repository ==\n'
rg -n -C 5 --type py \
  'os\.getenv\s*\(|os\.environ\.get\s*\(|int\s*\(\s*os\.(getenv|environ)' \
  src/youtube_extension

Length of output: 28971


🏁 Script executed:

#!/bin/bash
set -euo pipefail

commit='57fc58d137ecb718980ff62b2de59e7aa4edc65b'
test_path='tests/unit/test_firestore_state.py'
source_path='src/youtube_extension/services/cloud/firestore_state.py'

printf '%s\n' '== Tracked paths relevant to tests and dependencies =='
git ls-tree -r --name-only "$commit" | rg \
  '(^|/)(test_firestore_state\.py|pyproject\.toml|uv\.lock|requirements[^/]*\.txt|setup\.cfg|setup\.py)$'

printf '%s\n' '== Exact configuration and cleanup tests =='
git show "${commit}:${test_path}" | rg -n -C 45 \
  'CLEANUP_DELETE_CONCURRENCY|CLEANUP_DELETE_TIMEOUT_SECONDS|cleanup_old_states|timeout|environ|reload|importlib'

printf '%s\n' '== Exact test-file diff from prior reviewed head =='
git diff --no-ext-diff --unified=100 \
  '7558f0341f8da99452c7ca969b67635358db4662' "$commit" -- "$test_path"

printf '%s\n' '== Firestore dependency declarations =='
for path in pyproject.toml uv.lock requirements.txt requirements-dev.txt setup.cfg setup.py; do
  if git cat-file -e "${commit}:${path}" 2>/dev/null; then
    printf '\n--- %s ---\n' "$path"
    git show "${commit}:${path}" | rg -n -C 4 'google-cloud-firestore|firestore'
  fi
done

printf '%s\n' '== Local package API declaration, if installed =='
python - <<'PY'
import inspect
try:
    from google.cloud.firestore_v1.async_document import AsyncDocumentReference
    print(inspect.signature(AsyncDocumentReference.delete))
except Exception as exc:
    print(f'Firestore package inspection unavailable: {exc!r}')
PY

Length of output: 30072


@groupthinking I found one code-level issue at exact head 57fc58d137ecb718980ff62b2de59e7aa4edc65b.

CLEANUP_DELETE_TIMEOUT_SECONDS accepts inf. At Line 38, float("inf") succeeds, and max(0.001, inf) remains inf. An operator can set CLEANUP_DELETE_TIMEOUT_SECONDS=inf, which removes the intended delete deadline or causes a downstream gRPC timeout-validation failure.

Validate that the parsed timeout is finite and positive before assigning the module constant. Add regression coverage for inf and invalid values.

You are interacting with an AI system.

CLEANUP_DELETE_TIMEOUT_SECONDS was parsed as
`max(0.001, float(os.getenv(...)))`. `float()` accepts `inf`, `-inf` and
`nan`, and `max(0.001, inf)` returns `inf` unchanged, so setting
CLEANUP_DELETE_TIMEOUT_SECONDS=inf silently removed the per-delete
deadline (or would be rejected downstream by gRPC timeout validation).
`nan` was equally unsafe: it compares false against every bound, so it
was silently swallowed by max() rather than reported.

Replace the clamping with two explicit parsers:

- `_positive_int_env`  - integer >= 1
- `_positive_finite_float_env` - finite float > 0 (math.isfinite)

Out-of-range values now raise at import rather than being silently
coerced, so an operator typo surfaces at startup instead of quietly
changing cleanup behaviour. Blank values (a common artifact of a
compose/Helm template rendering an empty string) fall back to the
default instead of raising.

Adds TestCleanupConfigEnvParsing covering inf/Infinity/-inf/nan/0/
negative/malformed/blank/unset/valid for both parsers, plus an
invariant check on the module defaults.

Non-vacuity proven by mutation: restoring the `max(0.001, value)`
clamping inside the helper fails all 7 non-finite/non-positive cases
with "DID NOT RAISE ValueError".

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@groupthinking

Copy link
Copy Markdown
Owner Author

@coderabbitai Confirmed and fixed at f6c7910e4232403a121e6fd7bd5378b13640eca3.

Verdict

# Finding Verdict Where
1 CLEANUP_DELETE_TIMEOUT_SECONDS accepts inf; max(0.001, inf) leaves inf, removing the delete deadline Valid — fixed f6c7910
1b Add regression coverage for inf and invalid values Done TestCleanupConfigEnvParsing

You were right, and the bug was slightly wider than reported. nan was also
unsafe in the opposite direction: max(0.001, nan) returns 0.001 because
nan > 0.001 is false, so a nan override was silently swallowed rather than
reported. And int("")/float("") raised ValueError at import, which makes the
module unimportable for the very common case of a compose/Helm template rendering
an empty string.

Fix

Replaced the max() clamping with two explicit parsers:

def _positive_finite_float_env(name: str, default: float) -> float:
    raw = os.getenv(name)
    if raw is None or not raw.strip():
        return default                      # blank -> default, not ValueError
    value = float(raw.strip())              # malformed -> fail fast at import
    if not math.isfinite(value) or value <= 0:
        raise ValueError(
            f"{name} must be a positive, finite number of seconds, got {raw!r}"
        )
    return value

math.isfinite rejects inf, -inf and nan in one predicate.
_positive_int_env is the integer analogue (>= 1).

Deliberate choice: raise rather than clamp. Silently coercing an out-of-range
value hides an operator typo and changes cleanup behaviour without any signal.
Failing at import surfaces it in the first deploy log line. This matches the
"invalid numeric values fail fast during startup" contract already stated in the
constant's comment — it just now covers the non-finite cases too.

Non-vacuity — proven by behavioural mutation

Restoring only the old clamping inside the new helper
(return max(0.001, value)), keeping the tests and the rest of the module
untouched:

FAILED ...::test_float_env_rejects_non_finite_and_non_positive[inf]
FAILED ...::test_float_env_rejects_non_finite_and_non_positive[Infinity]
FAILED ...::test_float_env_rejects_non_finite_and_non_positive[-inf]
FAILED ...::test_float_env_rejects_non_finite_and_non_positive[nan]
FAILED ...::test_float_env_rejects_non_finite_and_non_positive[0]
FAILED ...::test_float_env_rejects_non_finite_and_non_positive[-1]
FAILED ...::test_float_env_rejects_non_finite_and_non_positive[0.0]
E           Failed: DID NOT RAISE ValueError
7 failed, 83 deselected

Source restored → 90 passed. ruff at parity with main
(only the pre-existing F841 at test_firestore_state.py:530).

Coverage added

inf, Infinity, -inf, nan, 0, 0.0, -1, abc, "", " ", unset,
12.5 (whitespace-tolerant valid) for the float parser; 0, -4, abc,
1.5, inf, "", 4 for the int parser; plus
test_module_defaults_are_positive_and_finite as a standing invariant on the
shipped defaults.


Two questions before I merge:

  1. Is raising at import the right failure mode here, or would you prefer a
    logger.warning + fall back to the default? I chose raising because a
    too-short timeout would cause every delete to fail at runtime, which is a
    much harder failure to diagnose than an import error.
  2. Anything else outstanding on the worker pool itself at f6c7910?

If this closes it out, please @coderabbitai resolve — your CHANGES_REQUESTED
review is still pinned to db083b6, three commits back.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

expected='f6c7910e4232403a121e6fd7bd5378b13640eca3'
actual="$(git rev-parse HEAD)"
printf '== Exact head ==\nexpected: %s\nactual:   %s\n' "$expected" "$actual"
test "$actual" = "$expected"

previous='57fc58d137ecb718980ff62b2de59e7aa4edc65b'
base="$(git merge-base "$actual" main)"

printf '\n== Changed files: previous review to head ==\n'
git diff --no-ext-diff --check "$previous" "$actual"
git diff --name-status "$previous" "$actual"
git diff --no-ext-diff --unified=100 "$previous" "$actual" -- \
  src/youtube_extension/services/cloud/firestore_state.py \
  tests/unit/test_firestore_state.py

printf '\n== Complete PR changed files ==\n'
git diff --no-ext-diff --check "$base" "$actual"
git diff --name-status "$base" "$actual"

printf '\n== Source structure ==\n'
ast-grep outline src/youtube_extension/services/cloud/firestore_state.py \
  --items all --match 'CLEANUP|positive|cleanup|timeout'

printf '\n== Source implementation ==\n'
rg -n -C 35 --type py \
  'def _positive_|CLEANUP_DELETE_|async def cleanup_old_states|\.delete\s*\(|math\.isfinite|os\.getenv' \
  src/youtube_extension/services/cloud/firestore_state.py

printf '\n== Test structure ==\n'
ast-grep outline tests/unit/test_firestore_state.py \
  --items all --match 'Cleanup|cleanup|Config|config|Env|env|timeout'

printf '\n== Configuration and cleanup tests ==\n'
rg -n -C 45 --type py \
  'TestCleanupConfigEnvParsing|positive_finite|positive_int|CLEANUP_DELETE_|cleanup_old_states|timeout|reload|monkeypatch|patch\.dict' \
  tests/unit/test_firestore_state.py

printf '\n== All cleanup call sites ==\n'
rg -n -C 5 --type py 'cleanup_old_states\s*\(' src tests

Length of output: 39633


🏁 Script executed:

#!/bin/bash
set -euo pipefail

head='f6c7910e4232403a121e6fd7bd5378b13640eca3'
previous='57fc58d137ecb718980ff62b2de59e7aa4edc65b'
test_path='tests/unit/test_firestore_state.py'

printf '%s\n' '== Confirm test blob exists at exact head =='
git cat-file -e "${head}:${test_path}"
git ls-tree -r --name-only "$head" | rg -n '^tests/unit/test_firestore_state\.py$'

printf '%s\n' '== Exact parser and timeout test blocks =='
git show "${head}:${test_path}" | rg -n -C 55 \
  'class TestCleanupConfigEnvParsing|test_float_env_|test_int_env_|test_module_defaults_are_positive_and_finite|test_cleanup_passes_configured_delete_timeout'

printf '%s\n' '== Exact worker-pool test blocks =='
git show "${head}:${test_path}" | rg -n -C 60 \
  'def _tracking_docs|test_cleanup_deletes_overlap|test_cleanup_bounds|test_cleanup_failure_does_not_abandon_remaining_deletes|CLEANUP_DELETE_CONCURRENCY'

printf '%s\n' '== Test changes since prior reviewed revision =='
git diff --no-ext-diff --unified=100 "$previous" "$head" -- "$test_path"

printf '%s\n' '== Cleanup source change since prior reviewed revision =='
git diff --no-ext-diff --unified=100 "$previous" "$head" -- \
  src/youtube_extension/services/cloud/firestore_state.py

Length of output: 48645


@groupthinking Verified at exact head f6c7910e4232403a121e6fd7bd5378b13640eca3.

The inf finding is fixed. _positive_finite_float_env() rejects inf, -inf, nan, zero, and negative values. cleanup_old_states() forwards the validated timeout to every doc.reference.delete() call.

Raising during import is the correct behavior. These values are startup configuration. A warning plus fallback would hide an invalid deployment configuration. It could also make cleanup behavior differ from the declared configuration. Blank and unset values already use the default, which handles templated empty values safely.

The regression coverage is adequate. It tests non-finite values, malformed values, non-positive values, blank and unset fallback, valid trimmed values, and timeout forwarding. The existing tests still cover concurrent deletion, RPC bounds, task-allocation bounds, and single-worker continuation after a failure.

I found no remaining code-level finding in the worker pool or the new configuration parsing. I verified the revision statically. The sandbox did not run the test suite.

🐇 ✅

[resolve]


✏️ Learnings added
Learnt from: groupthinking
URL: https://github.com/groupthinking/EventRelay/pull/1170

Timestamp: 2026-08-01T21:28:05.532Z
Learning: In `src/youtube_extension/services/cloud/firestore_state.py`, `CLEANUP_DELETE_CONCURRENCY` and `CLEANUP_DELETE_TIMEOUT_SECONDS` are startup environment configuration. Blank or unset values use defaults. Invalid numeric values fail during module import rather than being clamped or logged with a fallback. The timeout parser must accept only finite values greater than zero, and the concurrency parser must accept only integers greater than or equal to one.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

@linear-code
linear-code Bot marked this pull request as ready for review August 1, 2026 21:35
@groupthinking
groupthinking merged commit 739a26d into main Aug 1, 2026
45 of 52 checks passed
@groupthinking
groupthinking deleted the perf/firestore-cleanup-gather branch August 1, 2026 21:40
@linear-code

linear-code Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

GRV-218

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf: cleanup_old_states deletes expired Firestore documents sequentially

2 participants