fix(agent-mention): keep OpenCode dispatch payload under 10-property limit - #910
fix(agent-mention): keep OpenCode dispatch payload under 10-property limit#910seonghobae wants to merge 1 commit into
Conversation
📝 WalkthroughWalkthroughPR 댓글과 조직 스윕에서 신뢰된 에이전트 멘션을 검증합니다. 중복 invocation을 artifact ledger로 차단합니다. 유효한 요청은 중앙 워크플로로 전달하고, OpenCode 호출은 review-only 모드로 제한합니다. Changes에이전트 멘션 라우팅
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 및 영수증 댓글
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 2⚔️ Resolve merge conflicts 💡
🛠️ Fix failing CI checks 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (5)
.github/workflows/agent-mention-router.yml (1)
6-7: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win5분 cron과 168시간 lookback 조합은 REST 할당량을 크게 소모합니다.
스케줄은 하루 288회 실행됩니다. 매 실행마다
scripts/ci/agent_mention_sweep.py는 다음을 수행합니다.
- 조직의 모든 저장소 목록 조회.
- 저장소마다 최근 7일 내 갱신된 열린 PR 목록 조회.
- PR마다 최근 댓글 목록 조회와
repos/{repo}/pulls/{number}단건 조회.- 후보 요청마다 에이전트별 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.run에timeout이 없습니다.gh가 응답하지 않으면 프로세스는 무한정 대기합니다. 스윕은 저장소마다 여러 번 호출하므로, 한 번의 정지가 15분 job 타임아웃을 모두 소진하고 나머지 후보를 처리하지 못하게 만듭니다.
subprocess.TimeoutExpired를RuntimeError로 변환하면 스윕의 실패 격리 경계가 그대로 동작합니다.♻️ 타임아웃 추가 제안
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_payload와opencode_payload는event_type과agent값만 다릅니다. 나머지 9개 키는 동일합니다. 한쪽만 수정되면 두 wrapper 워크플로의 계약이 갈라집니다.공용 헬퍼로 통합하면
client_payload10키 제한도 한 곳에서 관리할 수 있습니다.♻️ 공용 헬퍼 제안
-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 wininvocation 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는 저장소나 후보 단위 오류를 격리하고 경고만 출력합니다. 그러나main은metrics.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
📒 Files selected for processing (4)
.github/workflows/agent-mention-opencode-dispatch.yml.github/workflows/agent-mention-router.ymlscripts/ci/agent_mention_router.pyscripts/ci/agent_mention_sweep.py
| '{ | ||
| 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", | ||
| } |
There was a problem hiding this comment.
🗄️ 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' . || trueRepository: 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 || trueRepository: 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 || trueRepository: 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:
- 1: https://docs.github.com/actions/using-workflows/events-that-trigger-workflows
- 2: https://github.com/step-security/dispatch-workflow
- 3: https://github.com/peter-evans/repository-dispatch?tab=readme-ov-file
- 4: https://bryanwhiting.com/ai/2026-01-11-github-dispatching/
- 5: https://github.com/marketplace/actions/repository-dispatch
- 6: https://github.com/step-security/repository-dispatch
client_payload의 필드 수제한을 유지하면서 agent_invocation_key와 source_comment_id를 전달하세요.
repository_dispatch는 client_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.
| if comment_id in processed_comment_ids(event.get("conversation_comments") or ()): | ||
| return None |
There was a problem hiding this comment.
🎯 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-L62scripts/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.
| 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 |
There was a problem hiding this comment.
🗄️ 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' || trueRepository: 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.pyRepository: 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 || trueRepository: 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.pyRepository: 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' || trueRepository: 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
fiRepository: 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 || trueRepository: ContextualWisdomLab/.github
Length of output: 4760
Noema dispatcher가 ledger artifact를 생성하는 흐름을 추가하세요.
cwl-noema-review dispatch는 성공하고 ledger_artifact_cache에만 같은 invocation key를 기록합니다. .github/workflows/noema-review.yml과 scripts/ci/noema_review_gate.py는 cwl-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.
| 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 |
There was a problem hiding this comment.
📐 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' {} \; || trueRepository: 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 || trueRepository: ContextualWisdomLab/.github
Length of output: 4291
scripts/ci/의 새 헬퍼 테스트를 추가하세요.
PR은 scripts/ci/agent_mention_router.py와 scripts/ci/agent_mention_sweep.py를 새로 추가했지만, 이 경로의 코드에 요구되는 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 상한을 추가 테스트해야 합니다.
📍 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
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
새로운 기능
개선 사항