Skip to content

fix(agent-mention): keep OpenCode dispatch payload under 10-property limit - #910

Open
seonghobae wants to merge 1 commit into
mainfrom
fix/agent-mention-opencode-payload-cap
Open

fix(agent-mention): keep OpenCode dispatch payload under 10-property limit#910
seonghobae wants to merge 1 commit into
mainfrom
fix/agent-mention-opencode-payload-cap

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary\n- Restore agent mention workflow chain and keep OpenCode dispatch payload within GitHub repository_dispatch client_payload cap.\n- Reduce initial opencode dispatch payload to 9 keys in scripts/ci/agent_mention_router.py.\n- Reduce merge-scheduler forward payload in .github/workflows/agent-mention-opencode-dispatch.yml to 10 keys while preserving validation/ledger contract via env defaults + canonical claim fields.\n- Recreate router and sweep entrypoint scripts in dotgithub for deterministic, scoped routing.

Summary by CodeRabbit

  • 새로운 기능

    • 풀 리퀘스트 댓글의 승인된 에이전트 멘션을 자동으로 감지하고 관련 작업을 실행합니다.
    • 최근 멘션을 주기적으로 확인하는 예약 처리 기능을 추가했습니다.
    • 지원 대상과 권한을 검증해 안전하게 작업을 전달합니다.
    • 동일한 요청의 중복 실행을 방지하고 처리 결과를 댓글과 반응으로 안내합니다.
    • 처리 기간, 최대 실행 수, 사전 실행 확인 모드를 설정할 수 있습니다.
  • 개선 사항

    • 오류가 발생한 저장소나 댓글이 전체 처리에 영향을 주지 않도록 격리합니다.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

PR 댓글과 조직 스윕에서 신뢰된 에이전트 멘션을 검증합니다. 중복 invocation을 artifact ledger로 차단합니다. 유효한 요청은 중앙 워크플로로 전달하고, OpenCode 호출은 review-only 모드로 제한합니다.

Changes

에이전트 멘션 라우팅

Layer / File(s) Summary
멘션 요청 검증과 CLI
scripts/ci/agent_mention_router.py
PR 댓글, 작성자, 저장소, 브랜치, SHA 및 지원 에이전트를 검증합니다. CLI와 gh api 클라이언트를 추가합니다.
에이전트 디스패치와 artifact ledger
scripts/ci/agent_mention_router.py
에이전트별 claim과 invocation key를 생성합니다. 처리된 요청을 조회하고 미처리 에이전트만 디스패치합니다. reaction과 영수증 댓글을 기록합니다.
이벤트 라우터 워크플로 통합
.github/workflows/agent-mention-router.yml
PR 댓글 이벤트를 로컬 라우터로 전달합니다. 읽기 전용 기본 권한과 동시 실행 제한을 설정합니다.
조직 댓글 스윕
.github/workflows/agent-mention-router.yml, scripts/ci/agent_mention_sweep.py
조직 저장소와 최근 열린 PR 댓글을 조회합니다. 토큰 교환, 조회 기간, 디스패치 제한 및 dry-run을 처리합니다.
OpenCode 호출 claim과 scheduler dispatch
.github/workflows/agent-mention-opencode-dispatch.yml
호출 페이로드와 SHA-256 invocation key를 검증합니다. 유효한 claim을 artifact로 저장하고 review-only merge-scheduler dispatch를 한 번만 전송합니다.

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

Sequence Diagram(s)

sequenceDiagram
  participant Author as PR 작성자
  participant RouterWorkflow as agent-mention-router 워크플로
  participant RouterScript as agent_mention_router.py
  participant Ledger as Actions artifact ledger
  participant Scheduler as merge-scheduler
  Author->>RouterWorkflow: PR 댓글에 에이전트 멘션
  RouterWorkflow->>RouterScript: 검증된 이벤트 전달
  RouterScript->>Ledger: invocation key 조회
  Ledger-->>RouterScript: claim 존재 여부 반환
  RouterScript->>Scheduler: 미처리 요청 dispatch
  RouterScript->>Author: reaction 및 영수증 댓글
Loading

Possibly related PRs

  • ContextualWisdomLab/.github#787: 동일한 에이전트 멘션 라우터, 조직 스윕 및 artifact ledger 기반 OpenCode dispatch 흐름을 추가합니다.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 OpenCode 디스패치 페이로드를 10개 속성 제한 이내로 유지하는 주요 변경을 정확하고 간결하게 설명합니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches 💡 2
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch fix/agent-mention-opencode-payload-cap
🛠️ 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 fix/agent-mention-opencode-payload-cap

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: 4

🧹 Nitpick comments (5)
.github/workflows/agent-mention-router.yml (1)

6-7: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

5분 cron과 168시간 lookback 조합은 REST 할당량을 크게 소모합니다.

스케줄은 하루 288회 실행됩니다. 매 실행마다 scripts/ci/agent_mention_sweep.py는 다음을 수행합니다.

  1. 조직의 모든 저장소 목록 조회.
  2. 저장소마다 최근 7일 내 갱신된 열린 PR 목록 조회.
  3. PR마다 최근 댓글 목록 조회와 repos/{repo}/pulls/{number} 단건 조회.
  4. 후보 요청마다 에이전트별 artifact ledger 조회.

7일 창은 이미 처리된 오래된 PR을 반복해서 재검사합니다. 새 작업은 대부분 최근 몇 시간 안에 발생합니다. cron 간격을 늘리거나 기본 LOOKBACK_HOURS를 줄이면 중복 작업이 줄어듭니다. 기본값 168은 백필 실행에만 사용하는 편이 안전합니다.

♻️ 기본값 조정 제안
   schedule:
-    - cron: "*/5 * * * *"
+    - cron: "*/15 * * * *"
-      LOOKBACK_HOURS: ${{ vars.AGENT_MENTION_LOOKBACK_HOURS || '168' }}
+      LOOKBACK_HOURS: ${{ vars.AGENT_MENTION_LOOKBACK_HOURS || '6' }}

Also applies to: 82-83

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

In @.github/workflows/agent-mention-router.yml around lines 6 - 7, Update the
workflow schedule and the sweep script’s default LOOKBACK_HOURS to reduce
repeated seven-day scans and REST API usage: use a less frequent cron interval
and a shorter default lookback suitable for normal runs, while preserving 168
hours only as an explicit backfill option.
scripts/ci/agent_mention_router.py (2)

62-94: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

gh api 호출에 타임아웃을 설정하세요.

subprocess.runtimeout이 없습니다. gh가 응답하지 않으면 프로세스는 무한정 대기합니다. 스윕은 저장소마다 여러 번 호출하므로, 한 번의 정지가 15분 job 타임아웃을 모두 소진하고 나머지 후보를 처리하지 못하게 만듭니다.

subprocess.TimeoutExpiredRuntimeError로 변환하면 스윕의 실패 격리 경계가 그대로 동작합니다.

♻️ 타임아웃 추가 제안
     def request(
         self,
         args: Sequence[str],
         *,
         input_payload: dict[str, Any] | None = None,
+        timeout: float = 60.0,
     ) -> Any:
         """Execute ``gh api`` and decode its optional JSON response."""
 
         command = ["gh", "api", *args]
         if input_payload is not None:
             command.extend(["--input", "-"])
         environment = os.environ.copy()
         environment["GH_TOKEN"] = self._token
-        completed = subprocess.run(
-            command,
-            input=None if input_payload is None else json.dumps(input_payload),
-            text=True,
-            capture_output=True,
-            check=False,
-            env=environment,
-        )
+        try:
+            completed = subprocess.run(
+                command,
+                input=None if input_payload is None else json.dumps(input_payload),
+                text=True,
+                capture_output=True,
+                check=False,
+                env=environment,
+                timeout=timeout,
+            )
+        except subprocess.TimeoutExpired as exc:
+            raise RuntimeError(f"gh api timed out after {timeout}s") from exc
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/ci/agent_mention_router.py` around lines 62 - 94, Update the
subprocess.run call in request to include a finite timeout for gh api
invocations, and catch subprocess.TimeoutExpired to raise RuntimeError with a
concise timeout diagnostic. Preserve the existing nonzero-exit handling so
request remains the sweep’s failure-isolation boundary.

372-410: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

두 payload 빌더를 하나로 합칠 수 있습니다.

noema_payloadopencode_payloadevent_typeagent 값만 다릅니다. 나머지 9개 키는 동일합니다. 한쪽만 수정되면 두 wrapper 워크플로의 계약이 갈라집니다.

공용 헬퍼로 통합하면 client_payload 10키 제한도 한 곳에서 관리할 수 있습니다.

♻️ 공용 헬퍼 제안
-def noema_payload(request: MentionRequest) -> dict[str, Any]:
-    """Return the durable Noema wrapper dispatch request body."""
-
-    agent = "cwl-noema-review"
-    return {
-        "event_type": "agent-mention-noema",
-        "client_payload": {
-            "target_repository": request.repository,
-            "pr_number": request.pull_request_number,
-            "pr_head_sha": request.pull_request_head_sha,
-            "pr_base_sha": request.pull_request_base_sha,
-            "base_branch": request.pull_request_base_branch,
-            "requested_agent": agent,
-            "agent_invocation_key": agent_invocation_key(request, agent),
-            "requested_by": request.actor,
-            "source_comment_id": request.comment_id,
-        },
-    }
-
-
-def opencode_payload(request: MentionRequest) -> dict[str, Any]:
-    """Return the durable review-only OpenCode wrapper dispatch body."""
-
-    agent = "opencode-agent"
-    claim = agent_invocation_claim(request, agent)
-    return {
-        "event_type": "agent-mention-opencode",
-        "client_payload": {
-            "target_repository": request.repository,
-            "pr_number": request.pull_request_number,
-            "pr_head_sha": request.pull_request_head_sha,
-            "pr_base_sha": request.pull_request_base_sha,
-            "base_branch": request.pull_request_base_branch,
-            "requested_agent": agent,
-            "agent_invocation_key": agent_invocation_key(request, agent),
-            "requested_by": request.actor,
-            "source_comment_id": request.comment_id,
-        },
-    }
+AGENT_EVENT_TYPES = {
+    "cwl-noema-review": "agent-mention-noema",
+    "opencode-agent": "agent-mention-opencode",
+}
+
+
+def agent_payload(request: MentionRequest, agent: str) -> dict[str, Any]:
+    """Return the durable wrapper dispatch body for one agent.
+
+    GitHub allows at most ten ``client_payload`` properties, so this builder
+    stays the single place where that budget is spent.
+    """
+
+    event_type = AGENT_EVENT_TYPES.get(agent)
+    if event_type is None:
+        raise ValueError(f"unsupported agent: {agent}")
+    return {
+        "event_type": event_type,
+        "client_payload": {
+            "target_repository": request.repository,
+            "pr_number": request.pull_request_number,
+            "pr_head_sha": request.pull_request_head_sha,
+            "pr_base_sha": request.pull_request_base_sha,
+            "base_branch": request.pull_request_base_branch,
+            "requested_agent": agent,
+            "agent_invocation_key": agent_invocation_key(request, agent),
+            "requested_by": request.actor,
+            "source_comment_id": request.comment_id,
+        },
+    }

호출부도 함께 갱신하세요.

dispatch_client.request(
    [dispatch_endpoint, "-X", "POST"],
    input_payload=agent_payload(request, agent),
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/ci/agent_mention_router.py` around lines 372 - 410, 통합된 agent_payload
헬퍼를 추가해 request와 agent를 받아 두 payload 빌더의 공통 client_payload 필드와
agent_invocation_key를 한 곳에서 생성하도록 변경하세요. event_type은 agent 값에 맞게 결정하고,
noema_payload와 opencode_payload는 각각 해당 agent를 전달하는 얇은 래퍼로 유지하거나 호출부를
agent_payload(request, agent)로 갱신하세요. 두 wrapper의 기존 이벤트 타입과 에이전트 값 및 10개
client_payload 계약을 그대로 보존하세요.
.github/workflows/agent-mention-opencode-dispatch.yml (1)

67-96: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

invocation key는 인증 수단이 아닙니다. 주석으로 명확히 하세요.

hmac.compare_digest는 상수 시간 비교만 제공합니다. 여기서 비교하는 값은 비밀 키 없는 순수 SHA-256 다이제스트입니다. 모든 입력 필드는 payload에 함께 전달됩니다. 따라서 repository_dispatch를 보낼 수 있는 주체는 임의의 payload에 대해 유효한 key를 직접 계산할 수 있습니다.

현재는 중앙 저장소에 대한 contents: write 권한이 실질적인 신뢰 경계입니다. 이 검증은 위변조 방지가 아니라 무결성 체크섬입니다. 향후 이 값을 인증 근거로 오해하지 않도록 주석을 남기세요. 더 강한 보증이 필요하면 조직 시크릿을 키로 사용하는 실제 HMAC으로 전환하세요.

♻️ 의도를 명시하는 주석 추가
           python3 - <<'PYTHON'
           import hashlib
           import hmac
           import json
           import os
 
+          # The invocation key is an unkeyed integrity checksum, not a MAC.
+          # It binds every behavior flag to one dispatch so that defaults cannot
+          # silently widen scope. Authentication comes from the `contents: write`
+          # permission required to send repository_dispatch to this repository.
           canonical = json.dumps(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/agent-mention-opencode-dispatch.yml around lines 67 - 96,
Add a concise comment next to the canonical SHA-256 calculation and comparison
in the Python validation block, explicitly stating that INVOCATION_KEY is an
integrity checksum rather than an authentication mechanism because it uses no
secret key. Note that repository contents: write permission remains the trust
boundary, and direct callers can recompute the value from the payload; do not
change the validation logic.
scripts/ci/agent_mention_sweep.py (1)

376-390: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

격리된 실패 하나가 스윕 job 전체를 실패로 만듭니다.

sweep는 저장소나 후보 단위 오류를 격리하고 경고만 출력합니다. 그러나 mainmetrics.failures가 1 이상이면 종료 코드 1을 반환합니다. 조직 규모 스윕에서는 접근 권한이 없는 저장소 하나만으로도 5분마다 job이 빨간색이 됩니다. 알림 피로가 발생하고 실제 장애 신호가 묻힙니다.

실패 비율에 임계값을 두거나, 디스패치가 전혀 성공하지 못한 경우에만 실패로 처리하는 방식을 고려하세요.

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

In `@scripts/ci/agent_mention_sweep.py` around lines 376 - 390, Update the
exit-status logic in main after sweep completes so isolated repository or
candidate failures recorded in SweepMetrics do not fail the entire job. Return a
failure status only when no dispatch succeeds, or apply an explicit failure-rate
threshold, while preserving dry-run behavior and the existing sweep execution.
🤖 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 @.github/workflows/agent-mention-opencode-dispatch.yml:
- Around line 198-211: Update the repository dispatch payload’s client_payload
block to pass agent_invocation_key and source_comment_id while keeping no more
than 10 top-level keys. Consolidate related values under an existing nested key
or remove unnecessary fields, and preserve the values needed for original
comment correlation and duplicate agent-invocation handling.

In `@scripts/ci/agent_mention_router.py`:
- Around line 171-172: The event contract does not consistently provide
conversation comments, leaving receipt-based duplicate prevention inactive. In
scripts/ci/agent_mention_router.py lines 171-172, retain the
conversation_comments lookup once producers are updated; in
.github/workflows/agent-mention-router.yml lines 51-62, populate
conversation_comments from the gh API comments response and merge it into the
event; in scripts/ci/agent_mention_sweep.py lines 266-274, add the already
fetched comments list to the event dictionary under conversation_comments.
- Around line 526-555: 리뷰에서 지정한 두 헬퍼의 100% 커버리지를 달성하도록 테스트를 추가하세요.
scripts/ci/agent_mention_router.py의 parse_event 신뢰 판정, allowlist 분기, claim/key
정규화, _artifact_records fail-closed 경로, dispatch_request의 dry-run 및 중복 경로를 검증하고,
scripts/ci/agent_mention_sweep.py의 cutoff_timestamp 경계값, flatten_pages 오류 분기,
list_recent_pull_requests의 cutoff 조기 종료, sweep fail-closed 경로와 max_dispatches
상한을 각각 테스트하세요. 새 테스트는 실제 외부 호출을 모킹하고 성공·실패 경로를 모두 포함해야 합니다.
- Around line 439-473: Update the cwl-noema-review branch in the dispatcher so a
successful dispatch creates/uploads the exact
agent_ledger_artifact_name(request, agent) ledger artifact, not merely updating
ledger_artifact_cache. Reuse the same claim/upload mechanism used by the
opencode-agent dispatch flow, ensuring the artifact is recorded only after the
dispatch succeeds and duplicate invocations are prevented.

---

Nitpick comments:
In @.github/workflows/agent-mention-opencode-dispatch.yml:
- Around line 67-96: Add a concise comment next to the canonical SHA-256
calculation and comparison in the Python validation block, explicitly stating
that INVOCATION_KEY is an integrity checksum rather than an authentication
mechanism because it uses no secret key. Note that repository contents: write
permission remains the trust boundary, and direct callers can recompute the
value from the payload; do not change the validation logic.

In @.github/workflows/agent-mention-router.yml:
- Around line 6-7: Update the workflow schedule and the sweep script’s default
LOOKBACK_HOURS to reduce repeated seven-day scans and REST API usage: use a less
frequent cron interval and a shorter default lookback suitable for normal runs,
while preserving 168 hours only as an explicit backfill option.

In `@scripts/ci/agent_mention_router.py`:
- Around line 62-94: Update the subprocess.run call in request to include a
finite timeout for gh api invocations, and catch subprocess.TimeoutExpired to
raise RuntimeError with a concise timeout diagnostic. Preserve the existing
nonzero-exit handling so request remains the sweep’s failure-isolation boundary.
- Around line 372-410: 통합된 agent_payload 헬퍼를 추가해 request와 agent를 받아 두 payload
빌더의 공통 client_payload 필드와 agent_invocation_key를 한 곳에서 생성하도록 변경하세요. event_type은
agent 값에 맞게 결정하고, noema_payload와 opencode_payload는 각각 해당 agent를 전달하는 얇은 래퍼로
유지하거나 호출부를 agent_payload(request, agent)로 갱신하세요. 두 wrapper의 기존 이벤트 타입과 에이전트 값 및
10개 client_payload 계약을 그대로 보존하세요.

In `@scripts/ci/agent_mention_sweep.py`:
- Around line 376-390: Update the exit-status logic in main after sweep
completes so isolated repository or candidate failures recorded in SweepMetrics
do not fail the entire job. Return a failure status only when no dispatch
succeeds, or apply an explicit failure-rate threshold, while preserving dry-run
behavior and the existing sweep execution.
🪄 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: 1aa5c380-8126-4439-906c-47f7bcfbf161

📥 Commits

Reviewing files that changed from the base of the PR and between 6eb06cd and 43d7007.

📒 Files selected for processing (4)
  • .github/workflows/agent-mention-opencode-dispatch.yml
  • .github/workflows/agent-mention-router.yml
  • scripts/ci/agent_mention_router.py
  • scripts/ci/agent_mention_sweep.py

Comment on lines +198 to +211
'{
event_type: "merge-scheduler",
client_payload: {
target_repository: $target_repository,
pr_number: $pr_number,
pr_head_sha: $pr_head_sha,
pr_base_sha: $pr_base_sha,
base_branch: $base_branch,
trigger_reviews: true,
review_dispatch_limit: "1",
enable_auto_merge: false,
update_branches: false,
merge_mode: "disabled",
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check which merge-scheduler client_payload fields the consumer reads.
set -euo pipefail

echo "== merge-scheduler dispatch consumers =="
rg -n -C 5 'merge-scheduler' --glob '.github/workflows/*.yml' || true

echo "== scheduler references to dropped fields =="
rg -n -C 3 'agent_invocation_key|source_comment_id' --glob '*.py' --glob '*.yml' || echo "no references found"

Repository: ContextualWisdomLab/.github

Length of output: 206


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow files =="
git ls-files '.github/workflows/*.yml' '.github/workflows/*.yaml' | sed -n '1,200p'

echo "== target workflow candidate =="
fd -a 'agent-mention-opencode-dispatch\.yml$' . | sed -n '1,20p'

echo "== target workflow lines 150-230 =="
file="$(fd 'agent-mention-opencode-dispatch\.yml$' . | head -n1)"
if [ -n "${file:-}" ]; then
  wc -l "$file"
  sed -n '150,230p' "$file" | nl -ba -v150
fi

echo "== all client_payload / dispatch references in workflows =="
rg -n -C 4 'client_payload|agent_invocation_key|source_comment_id|event_type: "merge-scheduler"|event_type: "merge' --glob '.github/workflows/*.yml' --glob '.github/workflows/*.yaml' . || true

Repository: ContextualWisdomLab/.github

Length of output: 1255


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repository files mentioning merge-scheduler =="
rg -n -C 5 'merge-scheduler' . || true

echo "== repository files mentioning scheduler comment/invocation fields =="
rg -n -C 4 'agent_invocation_key|source_comment_id|invocation_key|comment_id|comment' . | sed -n '1,260p' || true

echo "== Python/scheduler candidate files =="
fd -e py . | sed -n '1,200p'

Repository: ContextualWisdomLab/.github

Length of output: 50385


🏁 Script executed:

#!/bin/bash
set -euo pipefail

target="$(fd 'agent-mention-opencode-dispatch\.yml$' . | head -n1 || true)"
if [ -n "${target:-}" ]; then
  echo "== file: $target =="
  wc -l "$target"
  echo "== lines 1 - 120 =="
  sed -n '1,120p' "$target" | nl -ba -v1
  echo "== lines 120 - 230 =="
  sed -n '120,230p' "$target" | nl -ba -v120
else
  echo "missing agent-mention-opencode-dispatch.yml"
fi

echo "== tracked files with 'agent_invocation_key' or 'source_comment_id' excluding docs/tests =="
rg -n -C 3 'agent_invocation_key|source_comment_id' --glob '!PR_GOVERNANCE_AUDIT.md' --glob '!CLAUDE.md' --glob '!docs/**' --glob '!tests/**' . || true

echo "== all repository files with event_type merge-scheduler ==
rg -n -C 4 '"event_type":\s*"merge-scheduler"|event_type:' --glob '.github/workflows/*.yml' --glob '.github/workflows/*.yaml' .github || true

Repository: ContextualWisdomLab/.github

Length of output: 3351


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate dispatch/workflow files =="
git ls-files | rg '(^|/)(agent-mention-opencode-dispatch|agent-mention-router|pr-review-merge-scheduler)\.(yml|yaml|py)$|(^|/)workflows/.*(yml|yaml)$|(^|/)scripts/ci/.*\.py$' | sed -n '1,220p'

echo "== merge-scheduler event consumers =="
rg -n -C 5 '"merge-scheduler"|merge-scheduler|workflow_id' .github/workflows || true

echo "== agent_invocation_key/source_comment_id references =="
rg -n -C 3 'agent_invocation_key|source_comment_id|comment_id|invocation_key' --glob '!PR_GOVERNANCE_AUDIT.md' --glob '!CLAUDE.md' --glob '!docs/**' --glob '!tests/**' . || true

echo "== merged PR file content if absent from repository =="
git rev-parse --verify HEAD >/dev/null 2>&1 || true
git show HEAD:.github/workflows/agent-mention-opencode-dispatch.yml 2>/dev/null | sed -n '160,225p' | nl -ba -v160 || true

Repository: ContextualWisdomLab/.github

Length of output: 21011


🌐 Web query:

GitHub Actions repository_dispatch client_payload limit 10 items documentation

💡 Result:

When using the GitHub Actions repository_dispatch event, the client_payload is subject to specific technical limitations enforced by the GitHub API [1][2]: * Maximum top-level properties: The client_payload JSON object can contain a maximum of 10 top-level properties [1][3][4]. If this limit is exceeded, the request will fail with an error such as "No more than 10 properties are allowed" [5][2]. * Payload size: The entire client_payload is limited to a maximum of 65,535 characters [1][4]. Exceeding this limit will result in a "client_payload is too large" error [5][2]. Workaround: If you need to send more than 10 properties, you can bypass the top-level property limit by nesting your data within a single top-level object [5][3]. For example, instead of sending multiple properties at the root level, structure your JSON to contain a single key that holds your entire payload [5][6]: { "data": { "key1": "value1", "key2": "value2",... "key15": "value15" } } By wrapping the content in this way, you satisfy the requirement for a maximum of 10 top-level properties while still being able to pass complex or extensive data, provided the total size remains under the 65,535-character limit [1][5].

Citations:


client_payload의 필드 수제한을 유지하면서 agent_invocation_keysource_comment_id를 전달하세요.

repository_dispatchclient_payload에 최대 10개의 top-level 키만 허용하므로, 현재 10개 항목이 채워진 payload는 새 필드를 추가할 수 없습니다. 이 두 필드는 원본 멘션 댓글 연결과 agent invocation 중복 처리에 필요하므로, 같은 키 안에서 구조화하거나 기존 불필요 필드를 정리해 전달해야 합니다.

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

In @.github/workflows/agent-mention-opencode-dispatch.yml around lines 198 -
211, Update the repository dispatch payload’s client_payload block to pass
agent_invocation_key and source_comment_id while keeping no more than 10
top-level keys. Consolidate related values under an existing nested key or
remove unnecessary fields, and preserve the values needed for original comment
correlation and duplicate agent-invocation handling.

Comment on lines +171 to +172
if comment_id in processed_comment_ids(event.get("conversation_comments") or ()):
return None

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

conversation_comments 키에 생산자가 없어 영수증 기반 중복 방지가 동작하지 않습니다. parse_event는 이 키를 소비하지만 두 호출 경로 모두 이 키를 만들지 않습니다. 그 결과 processed_comment_ids, receipt_marker, RECEIPT_RE는 실행되지 않는 코드가 되고, 중앙 artifact ledger가 유일한 중복 방지 수단으로 남습니다. ledger는 하위 워크플로가 나중에 기록하므로, 그 사이 구간에서 같은 댓글이 재처리됩니다.

  • scripts/ci/agent_mention_router.py#L171-L172: 계약을 확정하세요. 두 생산자가 키를 채우도록 만들거나, 키가 영구히 비어 있다면 processed_comment_ids, receipt_marker, RECEIPT_RE와 이 조회를 함께 제거하세요.
  • .github/workflows/agent-mention-router.yml#L51-L62: jq 보강 단계에서 gh api "repos/${REPOSITORY}/issues/${PR_NUMBER}/comments" 결과를 --argjson conversation_comments로 읽어 . + {pull_request: $pull_request, conversation_comments: $conversation_comments}로 병합하세요.
  • scripts/ci/agent_mention_sweep.py#L266-L274: 255-260번 줄에서 이미 가져온 comments 목록을 이벤트 딕셔너리에 "conversation_comments": comments로 추가하세요.
📍 Affects 3 files
  • scripts/ci/agent_mention_router.py#L171-L172 (this comment)
  • .github/workflows/agent-mention-router.yml#L51-L62
  • scripts/ci/agent_mention_sweep.py#L266-L274
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/ci/agent_mention_router.py` around lines 171 - 172, The event
contract does not consistently provide conversation comments, leaving
receipt-based duplicate prevention inactive. In
scripts/ci/agent_mention_router.py lines 171-172, retain the
conversation_comments lookup once producers are updated; in
.github/workflows/agent-mention-router.yml lines 51-62, populate
conversation_comments from the gh API comments response and merge it into the
event; in scripts/ci/agent_mention_sweep.py lines 266-274, add the already
fetched comments list to the event dictionary under conversation_comments.

Comment on lines +439 to +473
existing = dispatched_agents(
request,
dispatch_client,
dispatchable,
ledger_artifact_cache=ledger_artifact_cache,
)
missing = tuple(agent for agent in dispatchable if agent not in existing)
handles = tuple(f"@{agent}" for agent in missing)
if not missing:
if rejected:
print(
"Rejected agent mention without target mutation "
f"repo={request.repository} pr={request.pull_request_number} "
f"comment={request.comment_id} "
f"agents={','.join(rejected)}"
)
return ()

dispatch_endpoint = f"repos/{CENTRAL_AUTOMATION_REPOSITORY}/dispatches"
if "cwl-noema-review" in missing:
agent = "cwl-noema-review"
dispatch_client.request(
[dispatch_endpoint, "-X", "POST"],
input_payload=noema_payload(request),
)
if ledger_artifact_cache is not None:
ledger_artifact_cache[agent_ledger_artifact_name(request, agent)] = True
if "opencode-agent" in missing:
agent = "opencode-agent"
dispatch_client.request(
[dispatch_endpoint, "-X", "POST"],
input_payload=opencode_payload(request),
)
if ledger_artifact_cache is not None:
ledger_artifact_cache[agent_ledger_artifact_name(request, agent)] = True

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify that a Noema wrapper workflow writes the exact-name ledger artifact.
set -euo pipefail

echo "== workflows reacting to agent-mention-noema =="
rg -n -C 5 'agent-mention-noema' --glob '.github/workflows/*.yml' || echo "no consumer workflow found"

echo "== ledger artifact writers =="
rg -n -C 5 'cwl-agent-invocation-' --glob '.github/workflows/*.yml' || true

Repository: ContextualWisdomLab/.github

Length of output: 240


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
git ls-files 'scripts/ci/agent_mention_router.py' '.github/workflows/*.yml' | sed -n '1,120p'

echo "== router outline/section =="
wc -l scripts/ci/agent_mention_router.py
sed -n '1,120p' scripts/ci/agent_mention_router.py
sed -n '400,490p' scripts/ci/agent_mention_router.py

echo "== noema/opencode payloads and dispatch helpers =="
rg -n -C 8 'noema_payload|opencode_payload|dispatched_agents|agent_ledger_artifact_name|ledger_artifact_cache' scripts/ci/agent_mention_router.py

Repository: ContextualWisdomLab/.github

Length of output: 14597


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflows section =="
git ls-files '.github/workflows/*.yml'

echo "== workflows mentioning noema opencode dispatch artifact or cwl-agent-invocation =="
rg -n -C 8 'noema|opencode|cwl-agent-invocation|dispatch|agent-mention' --glob '.github/workflows/*.yml' .github/workflows || true

echo "== exact names around artifact upload =="
rg -n -C 6 'upload-artifact|download-artifact|artifacts|artifact' --glob '.github/workflows/*.yml' .github/workflows || true

echo "== files relevant to agent-mention =="
rg -l 'agent-mention|agent mention|CENTRAL_AUTOMATION_REPOSITORY|cwl-noema-review|opencode-agent' --glob '.github/workflows/*.yml' .github/workflows || true

Repository: ContextualWisdomLab/.github

Length of output: 50386


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== noema workflow top-level and dispatch-related sections =="
wc -l .github/workflows/noema-review.yml
rg -n -C 12 'on:|workflow_dispatch|repository_dispatch|pull_request|agent_invocation_key|cwl-agent-invocation|upload-artifact|workflow_run|agent-mention-noema|run_id|run_attempt' .github/workflows/noema-review.yml || true

echo "== opencode dispatch workflow relevant sections =="
wc -l .github/workflows/agent-mention-opencode-dispatch.yml
rg -n -C 20 'on:|workflow_dispatch|repository_dispatch|pull_request|agent_invocation_key|cwl-agent-invocation|upload-artifact|agent-mention-opencode|concurrency|runs-on|needs:|always|if:|always()|workflow_run' .github/workflows/agent-mention-opencode-dispatch.yml || true

echo "== router file artifact helpers =="
sed -n '260,370p' scripts/ci/agent_mention_router.py
sed -n '420,485p' scripts/ci/agent_mention_router.py
rg -n -C 6 'def scan_schedule|AGENT_SCHEDULE|CRL|cron|5|minute|dispatch_request|dry_run|ledger_artifact_cache' scripts/ci/agent_mention_router.py

Repository: ContextualWisdomLab/.github

Length of output: 26814


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== scan schedule references =="
rg -n -C 8 'AGENT_SCHEDULE|schedule|cron|minute|dispatch_request|dry_run| ledger_artifact' --glob 'scripts/ci/**/*.py' scripts/ci || true

echo "== noema review artifact/claim/ledger references =="
rg -n -C 8 'artifact|upload-artifact|claim|cwl-agent|ledger|invocation|noema_review_gate|event.client_payload' .github/workflows/noema-review.yml scripts/ci/noema_review_gate.py scripts/ci/* | sed -n '1,260p' || true

Repository: ContextualWisdomLab/.github

Length of output: 50384


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== exact scan schedule matches with line context =="
rg -n -C 8 'schedule:|cron:|AGENT_SCHEDULE|dispatch_request\(|ledger_artifact_cache|ledger-artifact|AGENTS|agent_mentions|agent_mention_router' scripts/ci/agent_mention_router.py

echo "== noema workflow invocation key and ledger-related references only =="
rg -l 'cwl-agent|artifact|upload-artifact|client_payload.agent_invocation_key|noema_review_gate|agent_invocation_key|ledger' .github/workflows/noema-review.yml scripts/ci/noema_review_gate.py
sed -n '1,270p' .github/workflows/noema-review.yml | nl -ba
if [ -f scripts/ci/noema_review_gate.py ]; then
  wc -l scripts/ci/noema_review_gate.py
  rg -n -C 8 'agent_invocation_key|client_payload|cwl-agent|artifact|upload-artifact|ledger' scripts/ci/noema_review_gate.py
fi

Repository: ContextualWisdomLab/.github

Length of output: 4632


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== noema workflow invocations =="
python3 - <<'PY'
from pathlib import Path
p = Path(".github/workflows/noema-review.yml")
text = p.read_text()
for i, line in enumerate(text.splitlines(), 1):
    if i <= 55 or any(k in line for k in ("cwl-agent", ".github/artifact", "upload-artifact", "github.event.client_payload.agent_invocation_key", "client_payload.agent_invocation_key")):
        print(f"{i}: {line}")
PY

echo "== noema_review_gate artifact references =="
python3 - <<'PY'
from pathlib import Path
p = Path("scripts/ci/noema_review_gate.py")
print("exists", p.exists())
if p.exists():
    print("lines", len(p.read_text().splitlines()))
    for i, line in enumerate(p.read_text().splitlines(), 1):
        if any(k in line for k in ("cwl-agent", "artifact", "upload-artifact", "agent_invocation_key", "ledger")):
            print(f"{i}: {line}")
PY

echo "== exact schedule references in router file =="
python3 - <<'PY'
from pathlib import Path
text = Path("scripts/ci/agent_mention_router.py").read_text()
for i, line in enumerate(text.splitlines(), 1):
    if any(k in line for k in ("schedule", "cron", "schedule:", "AGENT_SCHEDULE", "5 minute", "5-minute", "dispatch_request", "agent_mention_router")):
        print(f"{i}: {line}")
PY

echo "== file workflow top 50 =="
sed -n '1,50p' .github/workflows/noema-review.yml 2>/dev/null || true

Repository: ContextualWisdomLab/.github

Length of output: 4760


Noema dispatcher가 ledger artifact를 생성하는 흐름을 추가하세요.

cwl-noema-review dispatch는 성공하고 ledger_artifact_cache에만 같은 invocation key를 기록합니다. .github/workflows/noema-review.ymlscripts/ci/noema_review_gate.pycwl-agent-invocation-<key> artifact를 기록하지 않습니다. 기존 스윕은 artifact가 없으므로 @cwm-noema-review가 걸린 PR마다 동일한 key로 계속 dispatch됩니다. @opencode-agent처럼 dispatcher가 claim/upload한 exact-name artifact를 기준으로 중복을 막도록 맞춰야 합니다.

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

In `@scripts/ci/agent_mention_router.py` around lines 439 - 473, Update the
cwl-noema-review branch in the dispatcher so a successful dispatch
creates/uploads the exact agent_ledger_artifact_name(request, agent) ledger
artifact, not merely updating ledger_artifact_cache. Reuse the same claim/upload
mechanism used by the opencode-agent dispatch flow, ensuring the artifact is
recorded only after the dispatch succeeds and duplicate invocations are
prevented.

Comment on lines +526 to +555
def main(argv: Sequence[str] | None = None) -> int:
"""Run the mention router for one enriched GitHub issue-comment event."""

parser = argparse.ArgumentParser()
parser.add_argument("--event-path", default=os.environ.get("GITHUB_EVENT_PATH", ""))
parser.add_argument("--dry-run", action="store_true")
args = parser.parse_args(argv)
if not args.event_path:
parser.error("--event-path or GITHUB_EVENT_PATH is required")
request = parse_event(load_event(args.event_path))
if request is None:
print("No trusted pull-request agent mention found; nothing to dispatch.")
return 0
target_token = os.environ.get("TARGET_REPOSITORY_TOKEN") or os.environ.get(
"GH_TOKEN", ""
)
dispatch_token = os.environ.get("AGENT_DISPATCH_TOKEN") or os.environ.get(
"GH_TOKEN", ""
)
allowlist = parse_repository_allowlist(
os.environ.get("OPENCODE_REPOSITORY_DISPATCH_TARGETS", "")
)
dispatch_request(
request,
target_client=GitHubClient(target_token),
dispatch_client=GitHubClient(dispatch_token),
opencode_allowlist=allowlist,
dry_run=args.dry_run,
)
return 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Locate existing tests and coverage configuration for scripts/ci helpers.
set -euo pipefail

echo "== test files referencing the new modules =="
rg -n 'agent_mention_router|agent_mention_sweep' --glob '*test*' || echo "no tests found"

echo "== test directories =="
fd -t d -i 'tests?' -d 3 || true

echo "== coverage / interrogate configuration =="
fd -t f -i 'pyproject.toml|setup.cfg|.coveragerc|tox.ini' -d 2 --exec rg -n -C 3 'interrogate|fail_under|omit|source' {} \; || true

Repository: ContextualWisdomLab/.github

Length of output: 225


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== tracked files under scripts/ci and tests-related top-level =="
git ls-files | rg '(^scripts/ci/|^tests?/|^.*test.*\.py$|pyproject\.toml|setup\.cfg|tox\.ini|\.coveragerc)' || true

echo "== agent_mention_router outline/section =="
ast-grep outline scripts/ci/agent_mention_router.py --view compact || true
echo "== agent_mention_sweep outline/section =="
ast-grep outline scripts/ci/agent_mention_sweep.py --view compact || true

echo "== changed files stat/name comparison if available =="
git diff --stat HEAD^..HEAD 2>/dev/null || git diff --stat 2>/dev/null || true
git diff --name-only HEAD^..HEAD 2>/dev/null || git diff --name-only 2>/dev/null || true

Repository: ContextualWisdomLab/.github

Length of output: 4291


scripts/ci/의 새 헬퍼 테스트를 추가하세요.

PR은 scripts/ci/agent_mention_router.pyscripts/ci/agent_mention_sweep.py를 새로 추가했지만, 이 경로의 코드에 요구되는 100% 테스트 커버리지 테스트가 없습니다. scripts/ci/agent_mention_router.pyparse_event 신뢰 판정, allowlist 분기, claim/key 정규화, _artifact_records fail-closed 경로, dispatch_request dry-run 및 중복 경로를 추가 테스트해야 합니다. scripts/ci/agent_mention_sweep.pycutoff_timestamp 경계값, flatten_pages 오류 분기, list_recent_pull_requests cutoff 조기 종료, sweep fail-closed 경로, max_dispatches 상한을 추가 테스트해야 합니다.

📍 Affects 2 files
  • scripts/ci/agent_mention_router.py#L526-L555 (this comment)
  • scripts/ci/agent_mention_sweep.py#L359-L390
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/ci/agent_mention_router.py` around lines 526 - 555, 리뷰에서 지정한 두 헬퍼의
100% 커버리지를 달성하도록 테스트를 추가하세요. scripts/ci/agent_mention_router.py의 parse_event 신뢰
판정, allowlist 분기, claim/key 정규화, _artifact_records fail-closed 경로,
dispatch_request의 dry-run 및 중복 경로를 검증하고, scripts/ci/agent_mention_sweep.py의
cutoff_timestamp 경계값, flatten_pages 오류 분기, list_recent_pull_requests의 cutoff 조기
종료, sweep fail-closed 경로와 max_dispatches 상한을 각각 테스트하세요. 새 테스트는 실제 외부 호출을 모킹하고
성공·실패 경로를 모두 포함해야 합니다.

Source: Coding guidelines

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