Skip to content

⚡ Bolt: 에이전트 멘션 스윕 PR 조회 N+1 API 병목 해결 - #926

Closed
seonghobae wants to merge 7 commits into
mainfrom
bolt-fix-agent-mention-n-plus-one-11378016381235193798
Closed

⚡ Bolt: 에이전트 멘션 스윕 PR 조회 N+1 API 병목 해결#926
seonghobae wants to merge 7 commits into
mainfrom
bolt-fix-agent-mention-n-plus-one-11378016381235193798

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Outcome

Parallelize organization-wide recent-PR discovery without allowing a bounded mention sweep to wait for irrelevant repository work after reaching its dispatch limit.

Exact identity

  • protected-main base snapshot: 6eb06cdd08c79a06f7b390069d4ffa49e2eb7dba;
  • current source head: 93eae45d78fc56b319a7c9f9ccf600180577c196;
  • source branch: bolt-fix-agent-mention-n-plus-one-11378016381235193798;
  • current hosted workflows: 9 queued; queued evidence is not represented as passing;
  • formal reviews: 1; unresolved review threads: 0.

Root cause and repair

The original bounded ThreadPoolExecutor implementation removed the N+1 API bottleneck and serialized repository-local failures, but the executor context manager still used shutdown(wait=True). When sweep() reached max_dispatches, returning from the consumer did not explicitly close its candidate iterator and could wait for every running or queued repository fetch.

The repaired contract:

  • preserves the serial fast path for zero or one repository;
  • bounds concurrent repository reads to at most ten workers;
  • preserves deterministic repository-order emission and caller-thread error accounting;
  • explicitly closes candidate iteration at the dispatch limit;
  • shares a cancellation event with workers and checks it between page requests;
  • cancels queued futures with shutdown(wait=False, cancel_futures=True);
  • lets normal complete collection perform a full waited shutdown;
  • documents cutoff and invalid-number behavior without exposing raw API failures.

Test-first evidence

  • fail-first standalone reproduction: dispatch returned 1 while the candidate iterator remained open;
  • repaired focused suite: 18 passed;
  • owned module: 195/195 statements and 78/78 branches;
  • public docstrings: 100%;
  • Python compilation: passed.

The hosted full-repository, security, dependency, and supply-chain runs on the current head remain authoritative for integration.

Duplicate lineage

#938 independently reintroduced the same N+1 optimization from the same protected base, but lacks this cancellation contract and currently fails its quality gate. Preserve #938 as duplicate evidence and converge on this older, fully tested branch once its active Jules writer is no longer targeting it.

Acceptance

Keep merge fail-closed until all current-head workflows complete successfully, current semantic review has no valid unresolved finding, qualifying independent human approval exists, live protected-main ancestry is revalidated, and protected-main operational acceptance proves bounded early termination.

scripts/ci/agent_mention_sweep.py의 list_recent_pull_requests에서 리포지토리별 PR 조회 시 발생하는 N+1 순차 API 호출을 concurrent.futures.ThreadPoolExecutor를 활용해 병렬화했습니다.
@google-labs-jules

Copy link
Copy Markdown

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

에이전트 멘션 스윕이 여러 저장소의 최근 PR을 병렬 조회합니다. 단일 저장소는 순차 조회합니다. 조회 결과는 수집 후 반환하며, 페이지네이션과 저장소별 오류 격리는 유지합니다.

Changes

에이전트 멘션 스윕

Layer / File(s) Summary
최근 PR 조회 병렬화
scripts/ci/agent_mention_sweep.py, .jules/bolt.md
여러 저장소를 최대 10개 워커로 조회합니다. 단일 저장소는 순차 처리합니다. PR 결과를 저장한 뒤 수집 순서대로 반환합니다. 페이지네이션과 저장소별 오류 격리는 유지합니다. 관련 오류 메시지와 호출 형식을 정리했습니다.

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

Sequence Diagram(s)

sequenceDiagram
  participant list_recent_pull_requests
  participant ThreadPoolExecutor
  participant GitHub API
  list_recent_pull_requests->>ThreadPoolExecutor: 저장소별 조회 작업 제출
  ThreadPoolExecutor->>GitHub API: 최근 PR 및 페이지 조회
  GitHub API-->>ThreadPoolExecutor: 저장소별 PR 결과
  ThreadPoolExecutor-->>list_recent_pull_requests: 수집된 결과 반환
  list_recent_pull_requests-->>list_recent_pull_requests: 수집 순서대로 PR 생성
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 에이전트 멘션 스윕의 PR 조회 N+1 API 병목을 병렬화로 해결하는 주요 변경 사항을 명확하게 설명합니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bolt-fix-agent-mention-n-plus-one-11378016381235193798

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

Inline comments:
In `@scripts/ci/agent_mention_sweep.py`:
- Line 158: fetch_repo_pulls 함수에 docstring을 추가해 다중 저장소 처리와 저장소별 오류 격리 동작을 간단히
설명하십시오.
- Around line 211-214: Update the worker flow around fetch_repo_pulls so workers
return pull-request results and exceptions instead of invoking on_error
directly; have the caller thread process those results in repository order and
invoke on_error serially. Update fetch_repo_pulls’s docstring to document this
behavior, and add tests covering concurrent errors and exact failure-counter
values.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ff2c489a-6b53-443c-ad23-f99b89c68e63

📥 Commits

Reviewing files that changed from the base of the PR and between 6eb06cd and 0d6a30e.

📒 Files selected for processing (2)
  • .jules/bolt.md
  • scripts/ci/agent_mention_sweep.py

Comment thread scripts/ci/agent_mention_sweep.py Outdated
Comment thread scripts/ci/agent_mention_sweep.py Outdated
seonghobae and others added 4 commits August 10, 2026 21:32
scripts/ci/agent_mention_sweep.py의 list_recent_pull_requests에서 리포지토리별 PR 조회 시 발생하는 N+1 순차 API 호출을 concurrent.futures.ThreadPoolExecutor를 활용해 병렬화했습니다.
이에 따라 테스트 시나리오(tests/test_agent_mention_sweep.py)에도 예외 처리 브랜치 등 커버리지를 만족하는 테스트를 추가했습니다.
scripts/ci/agent_mention_sweep.py의 list_recent_pull_requests에서 리포지토리별 PR 조회 시 발생하는 N+1 순차 API 호출을 concurrent.futures.ThreadPoolExecutor를 활용해 병렬화했습니다.
이에 따라 테스트 시나리오(tests/test_agent_mention_sweep.py)에도 예외 처리 브랜치 등 커버리지를 만족하는 테스트를 추가했습니다.

Copy link
Copy Markdown
Contributor Author

@opencode-agent review

Please review exact current head 93eae45d78fc56b319a7c9f9ccf600180577c196. Focus on prompt cancellation after max_dispatches, queued-future cancellation, running-worker page-boundary stopping, deterministic repository-order evidence, and preservation of caller-thread failure accounting. Do not transfer predecessor-head conclusions or treat local/automated evidence as qualifying human approval.

Copy link
Copy Markdown
Contributor Author

Superseded by #938 at exact head b6f2dba96a014e6ac6fddd776fa9a2db85e824f0 from the same protected-main tip 6eb06cdd08c79a06f7b390069d4ffa49e2eb7dba. The canonical branch now preserves deterministic repository-order emission, a zero/one-repository serial fast path, caller-thread error isolation, prompt cancellation between page requests, pending-future cancellation, explicit candidate-source close, and full current-suite/100% owned-coverage proof. All 14 hosted #938 exact-head runs, including Strix, OpenCode, Noema, CodeQL, Semgrep, OSV, SBOM, secret, Python-security, and quality gates, are terminal-success with zero unresolved review threads. Closing this duplicate preserves the requirements and history established here; merge authority for #938 remains fail-closed on two qualifying independent approvals and last-push approval.

@seonghobae seonghobae closed this Aug 12, 2026
@google-labs-jules

Copy link
Copy Markdown

Superseded by #938 at exact head b6f2dba96a014e6ac6fddd776fa9a2db85e824f0 from the same protected-main tip 6eb06cdd08c79a06f7b390069d4ffa49e2eb7dba. The canonical branch now preserves deterministic repository-order emission, a zero/one-repository serial fast path, caller-thread error isolation, prompt cancellation between page requests, pending-future cancellation, explicit candidate-source close, and full current-suite/100% owned-coverage proof. All 14 hosted #938 exact-head runs, including Strix, OpenCode, Noema, CodeQL, Semgrep, OSV, SBOM, secret, Python-security, and quality gates, are terminal-success with zero unresolved review threads. Closing this duplicate preserves the requirements and history established here; merge authority for #938 remains fail-closed on two qualifying independent approvals and last-push approval.

Understood. Acknowledging that this work is now obsolete as it has been superseded by #938, and stopping work on this task.

seonghobae pushed a commit that referenced this pull request Aug 30, 2026
… 2's 502 gap

Two more findings from a sixth Devin Review pass, both verified
directly against the vendored contextual-orchestrator source before
acting:

1. Empty-string content precision. ModelClient._response_content
   checks isinstance(content, str) before ever inspecting reasoning,
   so a genuinely empty string "" (not missing/null) is treated as a
   valid, non-erroring return and never reaches the
   reasoning-without-content branch. Verified this is NOT an
   implementation bug: PR #1452's already-shipped
   _response_has_reasoning_without_content predicate independently
   treats content == "" the same as missing content (reusing
   _chat_response_has_text's own "empty or missing" definition),
   deliberately broader than _response_content's own narrower
   condition, and already escalates this case correctly. Fixed as a
   documentation-precision matter: Trigger B's definition now states
   explicitly that "no usable content" includes a genuinely empty
   string, with a precision note clarifying the _response_content
   citation is the motivating signature this preflight generalizes
   from, not a claim of exact behavioral equivalence.

2. Layer 2 502 misclassification -- a genuine scope gap, not a
   wording issue. server.py's except ProviderResponseError: handler
   is one blanket catch that doesn't even bind the exception,
   collapsing both of _response_content's distinct failure causes
   (reasoning-without-content vs. no-content-at-all) into an
   identical 502 invalid_structured_output body with no
   machine-readable distinguishing field. Layer 2's sidecar script
   therefore classifies this as Trigger A by elimination and retries
   it up to 3 times, rather than failing fast as the correctly-
   classified Trigger B. Verified this requires an out-of-scope
   contextual-orchestrator change to fix properly -- no in-repo
   workaround avoids fragile message-text matching, which this org's
   own no-heuristics convention already rejects elsewhere in this
   ADR. Documented as a known, accepted, tracked Layer 2 limitation
   (Decision Section 1 at the point of definition, Consequences, and
   Decision Section 4's upstream-tracking list) rather than worked
   around, filed as ContextualWisdomLab/contextual-orchestrator#932
   following the existing #926/#927 pattern. Does not change Layer
   2's stated 360s worst case (same shared Trigger-A attempt budget).

Updated CHANGELOG.md and docs/product-technical-gap-baseline.md's
repeated summaries to match, per Devin's own suggested fix scope.
1897 tests pass (unchanged, docs-only); this branch's own test-plan
scope (105 passed, 1 subtest) re-verified.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Gs7KmNvH75nxz1sL8mKjw
seonghobae pushed a commit that referenced this pull request Aug 30, 2026
Four findings, weighed against this org's convergence rule at 26+
review threads across seven rounds on a docs-only PR:

1. Trivial, fixed: Evidence trail's upstream-issue citation still
   named only #926/#927, missing #932 from the round just landed.

2. Cross-reference gap, not reopened: Layer 1's 160s worst-case claim
   (Decision Section 3) never referenced #1455
   anywhere in this ADR's own text, even though #1455 (the
   discovery-timing gap) was filed and fully reasoned during the
   implementation pass on the stacked PR. Added the cross-reference at
   the point of definition and in Consequences; the underlying
   discovery-timing question itself stays tracked on #1455, not
   re-litigated here.

3. Genuinely new, verified real against the actual code (not just the
   ADR prose): REVIEW_PREFLIGHT_MAX_ESCALATIONS's shared budget is
   consumed in deterministic catalog order (alphabetical by
   provider/model, not random), so a later-sorting healthy candidate
   can be denied its own escalation attempt purely because 4 earlier
   candidates already claimed the shared budget. Considered a cheap
   reordering fix (round-robin, random shuffling) and rejected it on
   the merits: any selection policy for a fixed-size shared budget
   smaller than the candidate pool still has to deny someone a slot,
   so reordering only changes which candidates are favored, not
   whether the trade-off exists -- and picking a specific policy
   without real telemetry on which candidates actually need escalation
   more often would itself be exactly the unjustified heuristic this
   ADR already rejects elsewhere. Documented as a known, accepted,
   tracked limitation (#1458, matching the
   #1454/#1455/#932 pattern) rather than redesigned.

4. No action: the gap-baseline's repeated review-round narrative is
   this repo's own documented, intentional convention
   (docs/adr/0002-product-technical-gap-baseline.md: the baseline is
   "an operational snapshot" and "live PR metadata inventory," a
   distinct role from the ADR's design record and the CHANGELOG's
   terse pointers), not accidental redundancy.

Updated CHANGELOG.md and docs/product-technical-gap-baseline.md to
match. 1897 tests pass (unchanged, docs-only); this branch's own
test-plan scope (105 passed, 1 subtest) re-verified.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Gs7KmNvH75nxz1sL8mKjw
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant