Skip to content

vars.OPENCODE_REPOSITORY_DISPATCH_ACTOR is stale: every dispatch is rejected since 2026-08-31 #1927

Closed

Description

@seonghobae

세 개의 required dispatch 워크플로가 모든 디스패치를 인가 거부하고 있습니다. 원인은 4a5dfd82(2026-08-31)가 디스패치 신원을 바꿨는데 allowlist 변수가 그대로 남은 것입니다.

인가 경계라 제가 고치지 않고 근거만 올립니다. 지금 디스패치하는 신원에 맞춰 변수를 넓히는 건 자물쇠를 열쇠에 맞추는 방향이라, 신원 변경이 정당했는지 판단이 먼저입니다.

증상

opencode-review-dispatch.yml   success=466   failure=9609   last_success=2026-08-26
pr-review-fix-scheduler.yml    success=405   failure=10     last_success=2026-08-16
codeql-scan-dispatch.yml       success=0     failure=136    last_success=never
repository_dispatch authorization rejected actor=opencode-agent[bot] sender=opencode-agent[bot]
because both must match the configured scheduler identity.

vars.OPENCODE_REPOSITORY_DISPATCH_ACTOR = "github-actions[bot]"   (2026-07-16 설정, 이후 미변경)

원인 — 커밋으로 특정됩니다

GH_TOKEN="$app_token" gh api -X POST .../dispatches 형태를 도입한 커밋은 하나뿐입니다.

4a5dfd82  2026-08-31  fix(review): require substantive agent verdicts (#1497)

opencode-review.yml이 OIDC로 exchange_github_app_token을 호출해 얻은 OpenCode App 토큰으로 디스패치하게 됐고, App 토큰은 앱 신원(opencode-agent[bot])으로 디스패치합니다.

타임라인이 정확히 맞습니다.

2026-07-16   변수를 "github-actions[bot]"로 설정
2026-08-26   마지막 성공 — 실측 actor=github-actions[bot]
2026-08-31   4a5dfd82 가 디스패치를 App 토큰으로 전환
이후         actor=opencode-agent[bot], 전량 거부

마지막 성공 run의 actor가 github-actions[bot]이고 이후 전부 opencode-agent[bot]이라는 건 API로 확인했습니다.

판단이 필요한 지점

4a5dfd82의 커밋 메시지는 신원 변경을 전혀 언급하지 않습니다. 제목과 본문 모두 "require substantive agent verdicts"뿐이고, 토큰 전환은 그 PR에 딸려 들어간 변경입니다. 그래서 의도된 보안 개선인지, 부수적으로 섞여 들어간 것인지가 커밋만으로는 갈리지 않습니다.

변수를 갱신하는 쪽에 유리한 정황:

  • ambient GITHUB_TOKEN 대신 OIDC 기반 저장소 스코프 App 토큰을 쓰는 건 권한 경계상 더 좁고 나은 방향입니다.
  • 같은 저장소 pr-review-merge-scheduler.yml:387-388이 이미 opencode-agent[bot]을 정상 리뷰 작성자로 취급합니다.

디스패치 측을 되돌리는 쪽에 유리한 정황:

  • 변경이 문서화되지 않았습니다. 그래서 아무도 allowlist를 갱신하지 않았고 9,609건이 조용히 거부됐습니다.

범위 — opencode 전용이 아닙니다

변수를 소비하는 곳이 셋입니다.

opencode-review-dispatch.yml:116   ALLOWED_DISPATCH_ACTOR: ${{ vars.OPENCODE_REPOSITORY_DISPATCH_ACTOR }}
codeql-scan-dispatch.yml:142       (동일)
pr-review-fix-scheduler.yml:115    (동일)

codeql-pr.yml:258도 같은 App 토큰 패턴으로 디스패치하고, 최근 codeql-scan-dispatch run 8건이 **전부 actor=opencode-agent[bot]**입니다.

중요한 귀결: codeql-scan-dispatch.yml은 지금 149행 SUPPLIED_MATRIX 템플릿 오류(#1925)로 인가 검사에 도달조차 못 합니다. 인가 검사(155~157행)는 149행과 같은 env: 블록이 붙은 같은 run: 스크립트 안에 있어서, env 평가가 실패하면 스크립트가 한 줄도 실행되지 않습니다. 실제 로그에도 성공 시 찍히는 Authorized repository_dispatch actor=... 줄이 없습니다.

따라서 #1925의 toJSON() 수정이 들어가면 149행을 통과하고 155행에 도달하는데, 이 변수가 그대로면 실패 지점이 한 칸 이동할 뿐 초록이 되지 않습니다. 두 이슈는 순서 의존이라 함께 봐야 합니다.

확인 방법

gh api "repos/ContextualWisdomLab/.github/actions/workflows/opencode-review-dispatch.yml/runs?status=success&per_page=1" \
  --jq '.workflow_runs[0] | "actor=\(.actor.login) created=\(.created_at)"'
# -> actor=github-actions[bot] created=2026-08-26T18:22:23Z

gh api "repos/ContextualWisdomLab/.github/actions/workflows/opencode-review-dispatch.yml/runs?per_page=10" \
  --jq '.workflow_runs[] | .actor.login' | sort -u
# -> opencode-agent[bot]

git log --format='%h %ad %s' --date=short -S'GH_TOKEN="$app_token" gh api -X POST' -- .github/workflows/opencode-review.yml
# -> 4a5dfd82 2026-08-31 fix(review): require substantive agent verdicts (#1497)

관련: #1925 (같은 워크플로의 선행 템플릿 오류)

Activity

  1. seonghobae commented on Sep 5, 2026

    @seonghobae
    ContributorAuthor

    #1929가 같은 근본 원인을 독립적으로 찾았습니다. 두 이슈를 함께 보셔야 하고, #1929가 이 이슈에 없던 증거를 둘 들고 있습니다.

    실패율 정량:

    opencode-review-dispatch 누적   success 466 / failure 9611   (실패율 95.4%)
    최근 실패 10건 전부 validate-pr-metadata 에서 종료
    성공 표본 40건 전부 github-actions[bot], 최신 2026-08-26
    실패 표본 40건 중 27건이 opencode-agent[bot], 최신 2026-09-05
    

    그리고 이 이슈가 놓친 반례: 실패 표본 40건 중 **11건은 github-actions[bot]**입니다. 즉 신원이 허용 목록과 맞는데도 실패한 경로가 최소 하나 더 있습니다. 마지막 성공(2026-08-26)과 앱 토큰 전환(4a5dfd82, 2026-08-31) 사이 5일도 이 변수로는 안 덮입니다.

    따라서 이 이슈의 제목("every dispatch is rejected")은 과합니다. 낡은 허용 목록이 실패의 지배적 원인인 것은 두 측정이 일치하지만, 유일 원인은 아닙니다. 그 5일 공백과 github-actions[bot] 실패 11건은 별도 원인을 가리키고, 변수만 고쳐도 그쪽은 남습니다.

    #1929는 opencode-review-dispatch 단독에 집중하고, 이 이슈는 소비 워크플로 3개 전부와 #1925와의 순서 의존까지 다룹니다. 범위가 다르니 한쪽을 닫기보다 함께 두시고, 조치할 때 두 이슈의 증거를 합쳐 보시는 편이 낫겠습니다.

    관련: #1929, #1925

  2. seonghobae commented on Sep 5, 2026

    @seonghobae
    ContributorAuthor

    #1929가 제기한 반례("신원이 맞는데도 실패한 11건")를 추적했습니다. 별개 원인이 맞고, 그 원인은 이미 해소됐습니다. 그래서 이 이슈의 변수 수정이 리뷰 복구의 남은 마지막 조건일 가능성이 높습니다.

    두 번째 실패 양식은 인가와 무관합니다

    actor=github-actions[bot]인 실패 run을 열었습니다(33898309078, 2026-09-04T17:01:10Z).

    validate-pr-metadata   success   steps=4     ← 인가 통과
    coverage-source-tree   success   steps=5
    coverage-evidence      FAILURE   steps=10    ← "Measure test and docstring evidence"
    opencode-review        FAILURE   steps=26    ← "Enforce current-head formal OpenCode review receipt"
    

    인가는 통과합니다. 훨씬 뒤인 coverage/docstring 증거 측정에서 죽습니다. 이 이슈가 다루는 인가 거부와는 완전히 다른 경로입니다.

    그 원인은 #1883이 고쳤습니다

    github-actions[bot] 실패 44건   가장 오래된 2026-09-01T08:45:17Z
                                    가장 최신   2026-09-04T17:01:10Z
    #1883 병합                                  2026-09-05T08:10:23Z
    

    44건 전부가 #1883 병합보다 앞섭니다 — 가장 최신 실패도 15시간 이릅니다. #1883은 admission-controller의 coverage/docstring 공백을 메운 PR이고, 그 전까지 main의 게이트는 coverage 99% / interrogate 98.3%로 깨져 있었습니다. coverage-evidence가 Measure test and docstring evidence에서 죽은 것이 정확히 그 상태입니다.

    병합 후 main을 fresh clone으로 직접 측정한 값입니다.

    2883 passed / coverage 100% / interrogate 100%   (main@7fcada5)
    

    그래서 남는 그림

    원인 A  낡은 OPENCODE_REPOSITORY_DISPATCH_ACTOR   →  여전히 활성. 09-04T17 이후 전량 거부
    원인 B  main의 깨진 coverage/docstring 게이트      →  #1883 (09-05T08:10) 이 해소
    

    두 원인이 시간적으로 거의 맞물려 교대했습니다. 09-04T17까지는 B가, 그 이후로는 A가 리뷰를 막고 있습니다. 그래서 "실패가 끊긴 적이 없다"는 관측이 나오고, 원인을 하나로 보면 어느 쪽도 설명이 안 됩니다.

    확정하지 못하는 것 — 명시합니다

    #1883 병합 이후에 인가를 통과한 dispatch가 단 한 건도 없습니다. 09-04T17 이후 디스패처가 전부 opencode-agent[bot]이라 A에서 먼저 걸리기 때문입니다. 즉 B가 정말 해소됐는지는 아직 실행으로 검증된 적이 없습니다. 세 번째 원인이 없다는 것도 증명할 수 없습니다 — 그 시험이 수행된 적이 없습니다.

    따라서 정확한 표현은 이렇습니다: A를 고치면 B는 이미 해소되어 있으므로 리뷰가 복구될 가능성이 높고, 아니라면 그때 드러나는 것이 세 번째 원인입니다. A 수정이 진단 도구 역할도 겸합니다.

    관련: #1929, #1925, #1883

  3. seonghobae commented on Sep 5, 2026

    @seonghobae
    ContributorAuthor

    앞선 코멘트의 "A를 고치면 복구될 가능성이 높다"를 정정합니다. 44건 전수를 근본 원인으로 재분류하니 세 번째 원인이 나왔고, 그것이 최대이며 미해소입니다.

    근본 원인 분포 (44건 전수)

    스텝을 run 간에 합산하면 하류 증상이 중복 계산됩니다(Publish repository_dispatch OpenCode status 25건 같은 것은 대부분 증상입니다). validate-pr-metadata → coverage-source-tree → coverage-evidence → opencode-review 의존 순서에서 처음 실패한 job으로 다시 냈습니다.

    16  validate-pr-metadata | Bind workflow inputs...        36%
    14  coverage-evidence    | Measure test and docstring     32%
     6  opencode-review      | Wake exact-head required...    14%
     8  나머지 (각 1~2건)
    

    앞 코멘트에서 제가 제시한 원인 B(coverage/docstring)는 32%뿐입니다. run 하나를 열고 44건 전체를 설명한 것이 틀렸습니다.

    최대 원인 16건의 실제 내역

    ##[endgroup] 뒤의 실행 출력만 읽었습니다. 그 앞은 스크립트 소스 에코라 모든 에러 문구를 포함하고 있어서, 그대로 grep하면 어느 이야기든 만들 수 있습니다.

    9  repository_dispatch metadata does not match the live pull request: base_sha
    3  rejected closed, missing, or malformed live metadata   (state=closed)
    4  repository_dispatch authorization rejected
    

    9건의 실제 로그입니다.

    Authorized repository_dispatch actor=github-actions[bot] sender=github-actions[bot]   ← 인가 통과
    ##[error]... does not match the live pull request: base_sha.
      supplied_base=main/76969152...   live_base=main/5d55a31e...
      supplied_head=...fd964d24        live_head=...fd964d24                              ← head는 일치
    

    head는 맞고 base만 어긋납니다. 디스패치는 보낸 시점의 base SHA를 싣는데, 큐에서 몇 시간 기다리는 동안 main이 전진합니다. 3건은 검증에 도달했을 때 PR이 이미 닫혀 있었고, 같은 기전입니다.

    원인 C — 큐 지연이 디스패치를 스스로 무효화합니다

    16건 중 12건이 큐 지연의 결과입니다. 그리고 자기강화입니다. 대기가 길수록 base가 더 드리프트하고, 실패가 재시도를 부르고, 재시도가 대기를 늘립니다.

    이 이슈의 변수 수정으로도, #1883으로도 고쳐지지 않습니다.

    원인 A  낡은 인가 변수         4/16 (+ 09-04T17 이후 전량 거부)   미해소
    원인 B  깨진 coverage 게이트   14/44                             #1883 이 해소
    원인 C  큐 지연 staleness      12/16                             미해소, 구조적
    

    그래도 순서는 A 먼저입니다

    "A 수정이 진단 도구를 겸한다"는 부분은 유지됩니다. A를 고쳐야 인가를 통과한 dispatch가 다시 생기고, 그때 C가 얼마나 남는지 실행으로 보입니다. 지금은 09-04T17 이후 인가 통과 dispatch가 0건이라 C의 현재 규모를 측정할 방법이 없습니다.

    설계 판단이 필요한 지점 하나만 남깁니다. base SHA를 검증 조건에 쓰는 한 대기 시간이 곧 실패율이 됩니다. 지금 대기가 벽시계의 99.87%(실측)인 상태에서는 이 검증이 거의 항상 실패합니다. 완화 방향(base 검증을 head 고정으로 좁히기, 또는 base 드리프트를 허용 범위로 다루기)이 있을 수 있으나 신뢰 경계에 관한 결정이라 제가 정할 사안이 아닙니다.

    관련: #1929, #1925, #1883

  4. seonghobae commented on Sep 5, 2026

    @seonghobae
    ContributorAuthor

    코드 측 수정 #1932가 올라왔고, 이 이슈가 짚은 소비처 셋 중 하나를 덮습니다. 잔여 범위를 기록합니다.

    #1932  +62/-5   변수 미변경(추가 줄에 변수 언급 0)
      opencode-review-dispatch.yml:127   COVERED   — 쉼표 목록 허용, actor·sender가 같은 항목과 일치해야 통과(원 보장 유지)
      codeql-scan-dispatch.yml:155       NOT covered
      pr-review-fix-scheduler.yml:156    NOT covered
    

    origin/main에서 세 게이트 블록을 대조했습니다.

    opencode-review-dispatch.yml   69d6d380c8a7   ┐ 5줄 블록 해시(들여쓰기 제거)
    codeql-scan-dispatch.yml       69d6d380c8a7   ┘ 바이트 동일 → #1932 hunk 그대로 적용 가능
    pr-review-fix-scheduler.yml    cf7750197ed8     조건 3개 동일, 에러 문구만 echo 한 줄이 다름
    

    #1932만 머지되면: opencode-review-dispatch는 앱 신원을 받지만, codeql-scan-dispatch는 #1925의 toJSON() 수정 후 155행에 도달해 여전히 거부되고(앞 코멘트의 순서 의존 그대로), pr-review-fix-scheduler(마지막 성공 2026-08-16)도 그대로입니다. 셋을 함께 다루지 않으면 같은 변수를 소비하는 세 파일이 서로 다른 파싱을 갖게 되어 그것이 다음 드리프트가 됩니다.

    #1932를 넓힐지 나머지 둘을 별건으로 낼지는 #1932 작성자 판단이고, 여기엔 위치만 남깁니다.

    관련: #1932, #1929, #1925

  5. seonghobae commented on Sep 5, 2026

    @seonghobae
    ContributorAuthor

    이 이슈가 짚은 소비처 셋을 #1932가 전부 덮습니다(d44aa07d). opencode-review-dispatch.yml:127, codeql-scan-dispatch.yml:155, pr-review-fix-scheduler.yml:156이 같은 쉼표 목록 파싱을 씁니다 — actor·sender가 같은 항목과 일치해야 하고, 빈 목록은 거부, 단일 값은 그대로. 변수 자체는 소유자 결정으로 남겨뒀고, gh variable set 한 줄은 #1929에 적혀 있습니다.

  6. seonghobae commented on Sep 5, 2026

    @seonghobae
    ContributorAuthor

    Fresh downstream canary after #1926's matrix/env repair: ContextualWisdomLab/quarantine-sandbox-runtime#1@e9ee8470dbafff1c4518b45df165725bff80117e, required CodeQL PR run 33962688991, compatibility jobs 101316155183 (python) and 101316155232 (actions). The consumer jobs obtain the repository-scoped app token, successfully POST the codeql-scan repository_dispatch, receive no authenticated terminal codeql-dispatch/{language} verdict, and fail closed as pending. #1925's live timeline now shows post-#1926 dispatch handlers reaching authorization and rejecting actor/sender opencode-agent[bot]; this consumer therefore belongs to the #1927/#1929 identity-owner path, not a quarantine-sandbox-runtime source fix. Preserve fail-closed behavior and exact-head binding.

  7. seonghobae commented on Sep 5, 2026

    @seonghobae
    ContributorAuthor

    Fresh CodeQL consumer evidence from ContextualWisdomLab/LineageWeave#914@61ed3a3712d252e3c179a71d297c52f05e1bac20 confirms this stale-identity boundary on the native CodeQL dispatcher after the separate matrix-serialization fix was already merged. The required leaf CodeQL run 33979627116 dispatched three exact-head shards; central repository_dispatch runs 33988646498, 33988691535, and 33988735145 all eventually obtained runners and all failed in validate-dispatch -> Bind workflow inputs to live organization pull request metadata, while the actual scan job was skipped. Exact log for run 33988646498 / job 101366754283 shows DISPATCH_ACTOR=opencode-agent[bot], DISPATCH_SENDER=opencode-agent[bot], and ALLOWED_DISPATCH_ACTOR=github-actions[bot], ending with repository_dispatch authorization rejected ... because both must match one configured scheduler identity. The same run also proves the SUPPLIED_MATRIX is now valid JSON and reaches the authorization check, so .github#1926 did move the failure to the predicted next gate rather than leaving matrix parsing as the cause. No codeql-dispatch/<language> terminal status was published back to the unchanged LineageWeave head. This exact consumer should be included in the owner-side post-decision canary whichever identity policy is chosen; the leaf will not widen the allowlist, synthesize statuses, or churn its head.

  8. seonghobae commented on Sep 5, 2026

    @seonghobae
    ContributorAuthor

    The original intent can now be resolved from merged owner history rather than guessed. .github#1497 (merged as 4a5dfd823c678f97f85f5e40a4bd12b202885d35) explicitly states that the Required OpenCode check should “initiate the existing exact-PR central scheduler chain through GitHub OIDC and the OpenCode App, while preserving the no-checkout/no-Actions-secret bootstrap boundary.” That makes the OpenCode App token/identity an intentional trust-boundary change, not an incidental token substitution hidden by the PR title. Combined with the live #914 CodeQL canary, the safe owner direction is therefore to align the configured scheduler identity with the intended OpenCode App identity (opencode-agent[bot]) and lock that identity relationship with a regression/contract, rather than reverting dispatch to ambient github.token merely to recover the historical github-actions[bot] sender. I do not have an organization-variable mutation surface in this LineageWeave lane, and changing an authorization allowlist remains an owner/admin action; no leaf workaround is being made.

  9. seonghobae commented on Sep 5, 2026

    @seonghobae
    ContributorAuthor

    Correction to my immediately preceding owner-intent comment: do not replace the variable with only opencode-agent[bot]. Fresh owner history shows pr-review-merge-scheduler.yml also legitimately dispatches as github-actions[bot], and merged #1932 (6f8c51d7389c22ebaf294fe8fe9ef495257883c0) deliberately changed all three consumers to parse the same comma-separated identity allowlist so both trusted paths can coexist while still requiring actor and sender to match the same listed identity. #1497 establishes that the OpenCode App path is intentional; #1932 establishes that the existing scheduler identity is also intentional. Therefore the already-documented owner action is the two-entry value github-actions[bot],opencode-agent[bot], not a replacement. I am leaving the variable unchanged because this connector does not expose the repository/organization Actions-variable mutation surface and authorization-list membership remains owner/admin policy. The LineageWeave #914 canary remains valid for post-change verification.

  10. seonghobae commented on Sep 6, 2026

    @seonghobae
    ContributorAuthor

    Fresh Context Fabric exact-head canary resolves the remaining authorization choice into an executable owner-plane repair; this is not a request for a user decision.

    ContextualWisdomLab/context-graph-contracts#4@5117383ac15cdfe3813340455cd99b92937ef47c ran required CodeQL PR 34009096429. Its local compatibility shard 101428051612 acquired a hosted runner, revalidated the live open PR/base/head, obtained the repository-scoped OpenCode App token through OIDC, and successfully dispatched the exact Python shard. The corresponding central CodeQL Scan Dispatch run 34013203830 eventually acquired hosted runner 1001704430; validate-dispatch job 101432456623 then failed only at the dispatcher authorization boundary. Exact log values were DISPATCH_ACTOR=opencode-agent[bot], DISPATCH_SENDER=opencode-agent[bot], ALLOWED_DISPATCH_ACTOR=github-actions[bot], followed by repository_dispatch authorization rejected ... because both must match one configured scheduler identity. The target remained exact CGC#4@5117383...; this is not a CGC source or runner-selector defect.

    Protected source has already established the representation needed for the causal repair: merged #1932 changed all three consumers to parse OPENCODE_REPOSITORY_DISPATCH_ACTOR as a comma-separated allowlist while still requiring actor and sender to equal the same listed principal and while keeping an empty list fail-closed. #1932 also records two current trusted producers: the OpenCode App path emits opencode-agent[bot], while the scheduler path emits github-actions[bot]. Therefore replacing one identity with the other would merely move the outage. The smallest reversible policy repair is to set the owner-plane variable to exactly github-actions[bot],opencode-agent[bot] after revalidating that #1932's parser remains on protected main. Do not add a human/manual actor, wildcard, service identity, or actor/sender mismatch acceptance; do not revert the narrower repository-scoped App-token dispatch solely to preserve the stale scalar setting.

    GREEN acceptance: mutate the variable through the authorized central settings path; read it back; rerun the unchanged CGC #4 exact-head CodeQL dispatch (or allow the canonical exact-run wake path) and require validate-dispatch to pass, the actual CodeQL shard to execute, authenticated codeql-dispatch/python and codeql-dispatch/actions terminal statuses to bind to 5117383..., and the original compatibility jobs to terminate from those exact-head verdicts. Also run an unchanged OpenCode canary and a scheduler-origin canary so both listed trusted principals are proven while an unlisted/manual principal remains rejected. Any later failure is a new causal boundary, not permission to weaken fail-closed review or CodeQL evidence.

  11. seonghobae commented on Sep 6, 2026

    @seonghobae
    ContributorAuthor

    2026-09-06 fresh exact-head Context Fabric reproduction confirms this authorization defect is still live and is now directly observable past the earlier template/evaluation blocker.

    • context-graph-contracts#4 exact head: 5117383ac15cdfe3813340455cd99b92937ef47c.
    • Required CodeQL PR run 34009096429 dispatched the Python and Actions shards from jobs 101428051612 / 101428051650. Both acquired ubuntu-24.04 hosted runners and used the exchanged repository-scoped app token for the central repository_dispatch; this lane is not a runner_id=0 acquisition failure.
    • Central dispatch runs 34013203830 and 34013205147 target the same repository/PR/head. In run 34013205147, validate-dispatch job 101432460171 also acquired a hosted runner and reached the authorization gate.
    • Runtime identity is DISPATCH_ACTOR=opencode-agent[bot], DISPATCH_SENDER=opencode-agent[bot], while ALLOWED_DISPATCH_ACTOR=github-actions[bot]; the job fails with repository_dispatch authorization rejected ... because both must match one configured scheduler identity.
    • Current .github source at 43024633eba9d96b0456970391360da5a171fbda already has the comma-separated allowlist contract and tests that accept a listed identity only when actor and sender are the same identity. Therefore the current causal defect is configuration/dispatcher-identity drift, not a need to relax sender binding or permit arbitrary actors.

    Owner acceptance for this live case: revalidate the intended app-token dispatcher boundary, align the authorized configuration with that intended identity without weakening the same-identity fail-closed check, then rerun/revalidate the unchanged CGC #4 head and require authenticated terminal codeql-dispatch/python and codeql-dispatch/actions verdicts plus the exact failed required-job wake/rerun. If a subsequent run then fails only because the live base advanced while the immutable PR head did not, treat that separately as the queue-staleness/base-SHA design defect already identified here; preserve exact-head binding rather than weakening evidence.

    Related control-plane dependencies remain #712, #1137 and #772.

  12. seonghobae commented on Sep 6, 2026

    @seonghobae
    ContributorAuthor

    Fresh consumer canary from ContextualWisdomLab/naruon#1505 (2026-09-06): exact product head a271c1ffd3d4357c2803b6a3f6c971538ecc9e06 still targets protected develop@042b0c70531b229af3acbd0421a2f23098d848b3; that protected tip is unchanged, so the known queued-run base_sha drift class is not applicable to this canary. Application CI 34025523073, Bandit 34025523024, Docker 34025523141, Security Scan 34025523048, and Semgrep 34025522990 are terminal-success, and CodeRabbit review 5125084510 APPROVED the same exact commit. CodeQL PR 34025522963 is the sole repository-owned failed workflow in this set: language detection succeeds, and all three compatibility jobs (actions, python, javascript-typescript) complete Request current-head CodeQL scan dispatch successfully before failing at Release runner or enforce current-head CodeQL verdict. This does not by itself prove which producer-side authorization branch fired, so I am not reclassifying it beyond the central dispatch/verdict path; it is a useful unchanged-base canary after the matrix-serialization repair. Naruon will not add a dummy commit, rerun around the fail-closed consumer contract, or widen owner authorization locally.

  13. seonghobae commented on Sep 6, 2026

    @seonghobae
    ContributorAuthor

    Context Fabric dependency decision, corrected after reading the newer #1929 evidence and current protected scheduler source: do not simply replace the allowlist value while two dispatch identities remain live. The safer causal repair is to canonicalize the repository_dispatch transport identity first, then keep the allowlist single-valued.

    Current protected pr-review-merge-scheduler.yml already performs the same OIDC -> OpenCode App token exchange (scheduler_app_token), but later mutation/dispatch paths may choose PR_REVIEW_MERGE_TOKEN || OPENCODE_APPROVE_TOKEN || scheduler_app_token || github.token. That explains why both github-actions[bot] and opencode-agent[bot] have appeared. Broadening OPENCODE_REPOSITORY_DISPATCH_ACTOR to admit both principals would enlarge the authorization surface and is not the preferred repair.

    Owner-path decision: use the repository-scoped OIDC/App token as the single credential for the repository_dispatch send operation in both opencode-review.yml and the scheduler path, while leaving any separately justified PR mutation credential selection outside that transport step. If the App-token exchange is unavailable, fail closed instead of silently falling back to a different dispatch principal. Once both senders are contractually bound to opencode-agent[bot], set/retain the handler allowlist to that one exact principal and add a regression proving no github.token/PAT fallback is used for repository_dispatch.

    End-to-end GREEN must prove: (1) both producer paths emit actor=sender=opencode-agent[bot]; (2) the handler accepts only that pair and rejects github-actions[bot], the human account, and any other principal; (3) CodeQL/OpenCode terminal verdicts bind back to the requesting exact run/job/head; and (4) existing merge/update mutation authority is not widened. This supersedes the earlier single-line allowlist recommendation in this comment.

  14. seonghobae commented on Sep 6, 2026

    @seonghobae
    ContributorAuthor

    SOURCE WRITER CLAIM — bounded P0 transport-identity repair from protected main@49eb9e7035a6994fffb5b24bf943156be27a02fb. Scope: make both repository_dispatch producer paths fail closed unless the repository-scoped OIDC/OpenCode App token is present, use that token only for the dispatch POST, and keep scheduler Actions reads/PR mutations on their existing separately justified credential boundaries. Preserve actor=sender same-identity handler enforcement. I will add realistic RED contracts before the minimal workflow/core change. #1238@4a4ddc2162bd48314dc032afd08ec6a2724178db concurrently owns only flag coercion in the same workflow; its semantic delta must be ordinarily adopted before either lane merges. No allowlist/settings mutation, secret invention, consumer rerun, force update, or gate weakening is part of this claim.

  15. seonghobae commented on Sep 6, 2026

    @seonghobae
    ContributorAuthor

    SOURCE RECEIPT / WRITER RELEASE — canonical transport-identity repair is now Draft #1984.

    Exact remote head: 71bda03067ad94e90db53ad3274e43dc4b242eed
    Parent: protected main@49eb9e7035a6994fffb5b24bf943156be27a02fb
    Exact tree: 7b81c66cb1a97f27fd89e894e238e015c867648c

    RED: 6 focused failures proved the scheduler still wired OpenCode/Strix repository-dispatch POSTs to the runner-token path and had no App-only fail-closed boundary.

    GREEN:

    • scheduler/queue focused: 399 passed
    • full Python: 2970 passed, 1 skipped, 21 subtests passed
    • full scripts/ci/test_strix_quick_gate.sh: PASS
    • Python compile / diff check: PASS

    The repair sends both POST paths only through the OIDC-exchanged OpenCode App token, preserves actor=sender validation and exact target/head binding, keeps Actions-read and mutation credentials separate, and has no ambient/PAT/runner-token fallback.

    #1238 remains an independent same-file writer; whichever lands second must ordinarily adopt the first. #1984 is Draft with 11 hosted jobs queued and 0 exact-head approvals. Live allowlist/settings are unchanged and still require an authorized settings owner after protected source convergence; no consumer rerun is valid yet.

    Source writer released. No force-push, bypass, settings mutation, self-approval, gate weakening, or leaf mutation.

  16. claude commented on Sep 6, 2026

    @claude

    Refinement: exactly one dispatch has been authorized since 2026-08-31, and its actor is the whole diagnosis

    Re-measured 2026-09-06T18:2x against the Actions API. The "every dispatch is rejected" framing has exactly one counterexample, and it is the informative one.

    opencode-review-dispatch.yml, status=success&created=>2026-08-31 → total_count = 1:

    run started ended triggering_actor target
    33998627665 2026-09-05T23:25:16Z 2026-09-06T03:13:39Z github-actions[bot] .github#1946@db361daa

    event=repository_dispatch, and all four jobs — validate-pr-metadata, coverage-source-tree, coverage-evidence, opencode-review — concluded success. Its validate log ends with the authorization line rather than the rejection:

    DISPATCH_ACTOR: github-actions[bot]
    DISPATCH_SENDER: github-actions[bot]
    ALLOWED_DISPATCH_ACTOR: github-actions[bot]
    Authorized repository_dispatch actor=github-actions[bot] sender=github-actions[bot] target=ContextualWisdomLab/.github.
    

    Every rejected dispatch carries a different sender, not a different allowlist. Three failures sampled across three repositories today (.github#1967 job 101520441575, naruon#1531, fast-mlsirm#1764) all stop at the same step, Bind workflow inputs to live organization pull request metadata, with coverage-source-tree / coverage-evidence / opencode-review all skipped:

    DISPATCH_ACTOR: opencode-agent[bot]
    DISPATCH_SENDER: opencode-agent[bot]
    ALLOWED_DISPATCH_ACTOR: github-actions[bot]
    ##[error]repository_dispatch authorization rejected actor=opencode-agent[bot] sender=opencode-agent[bot]
             because both must match one configured scheduler identity.
    

    ALLOWED_DISPATCH_ACTOR is identical in the authorized run and in the rejected ones. So the variable is not stale in the sense of having drifted away from its own past value — it is consistent with one of the two senders that exist:

    • Merge-scheduler path — dispatch sent from a scheduled/dispatched run whose GITHUB_TOKEN may be write-scoped → github-actions[bot] → authorized. This is run 33998627665, and it proves the rest of the pipeline works end to end once authorization passes.
    • Required-workflow self-dispatch path — opencode-review.yml / codeql-pr.yml run under pull_request_target with a read-only workflow token, so they mint a repository-scoped App token (POST ${OPENCODE_API_BASE_URL}/exchange_github_app_token) and send repos/.../dispatches with GH_TOKEN="$app_token". GitHub attributes that to the App → opencode-agent[bot] → rejected.

    The required job's own error text promises the sequence that authorization then prevents:

    ##[error]No APPROVED or CHANGES_REQUESTED from opencode-agent on the current head.
    The dispatch workflow will rerun this failed job after publishing an authenticated exact-head verdict.
    

    The rerun cannot happen, so the required check is a permanent fail-closed rather than a wait-state. Same shape on the CodeQL side (.github job 101426759771 for contextual-orchestrator#1082, DISPATCH_OUTCOME: success / VERDICT_STATE: pending, unresolved for 13 h), and codeql-scan-dispatch.yml shows 0 successes across 2352 runs — consistent with a path fed only by the App-token sender. #1925 names an independent second defect on that workflow, so fixing authorization alone will not be sufficient there.

    Why the remedy is the allowlist and not the sender. Making the required job dispatch as github-actions[bot] requires contents: write on a pull_request_target job, which AGENTS.md/CLAUDE.md forbid ("Do not widen a pull_request_target job token to repository-write permission"). The receiving gate already parses a comma-separated list and its own comment says the allowlist is shared with opencode-review-dispatch.yml and pr-review-fix-scheduler.yml, so appending the App identity is the change the code was written to accept:

    IFS=',' read -r -a allowed_dispatch_actors <<<"$ALLOWED_DISPATCH_ACTOR"

    vars.OPENCODE_REPOSITORY_DISPATCH_ACTOR is an Actions variable, not repository content, so no PR diff can carry the fix and this session cannot read or write it (GET /repos/.../actions/variables → 403 through the agent proxy). It needs an owner action.

    Blast radius observed today, all stopped at this same gate: .github#1884, #1967, #1187, #834, #1644; contextual-orchestrator#1053, #1082, #1067, #1084; naruon#1531, #1244; fast-mlsirm#1764; life-os#247; bandscope#866, #1076.


    Generated by Claude Code

  17. seonghobae commented on Sep 6, 2026

    @seonghobae
    ContributorAuthor

    UPDATE — 2026-09-07: the authorized settings owner has now applied the canonical dual-principal value. Execution evidence, not a settings-API guess, is authoritative here.

    • run 34069437294, validate job 101583929799, started after the settings change and received ALLOWED_DISPATCH_ACTOR=github-actions[bot],opencode-agent[bot].
    • The receiver preserved the fix(dispatch): parse the trusted-dispatcher allowlist identically in all three consumers (#1929) #1932 invariant: DISPATCH_ACTOR=opencode-agent[bot] and DISPATCH_SENDER=opencode-agent[bot] matched the same listed principal, and the log emitted Authorized repository_dispatch actor=opencode-agent[bot] sender=opencode-agent[bot].
    • That run then failed closed on a separate live-PR metadata mismatch: the supplied head f8be5e31... no longer matched live head 6e1194aa.... It is therefore proof of settings/authorization convergence, not an end-to-end CodeQL GREEN.
    • Fresh .github#1725 exact-head child runs 34077321864 and 34077342761 were dispatched by opencode-agent[bot] for unchanged 0bb8f7c06cb2ce291101e7014afe90bef0fe40c4, but both validation jobs are still runner-unassigned queued. Do not count terminal scan or parent wake-up yet.

    The previous finding remains valid as historical RCA: scheduler 33998627665 proves github-actions[bot] is legitimate, while repository-scoped required-workflow dispatch proves opencode-agent[bot] is legitimate. The minimal repair was and is the #1932 same-identity receiver contract plus exactly github-actions[bot],opencode-agent[bot]. #1984's single-principal premise remains blocked.

    Historical protected-source reproduction on .github#1644 showed the pre-change failure clearly: CodeQL child runs 34059608826, 34059609580, and OpenCode child run 34060961005 all had actor=sender opencode-agent[bot] but the then-live single-principal value github-actions[bot], so authorization failed before scan/review.

    Acceptance remains fail-closed: require an unchanged exact head to pass current live metadata validation, complete the central scan/review, publish the base-bound terminal status, and wake the parent compatibility job. No consumer rerun, source relaxation, ambient-token fallback, or principal expansion beyond these two identities is justified.

  18. seonghobae commented on Sep 6, 2026

    @seonghobae
    ContributorAuthor

    Wardnet exact consumer evidence for the #1925 -> #1927 sequence: wardnet#140@a904558b79ef392515dff4b501692112ad6beabf has CI/Fuzz/Security/SAST terminal GREEN, while CodeQL PR 34020215930 dispatched successfully and then failed closed waiting for a terminal current-head verdict. After #1925 allows the dispatched native handler to compile, this same unchanged head should proceed to the dispatch-authorization check. Acceptance for #1927 is that the intended trusted dispatcher identity and configured allowlist are reconciled without broadening actor authorization, and a fresh native CodeQL dispatch for exact a904558... passes that authorization boundary and produces a terminal source-bound verdict. Do not substitute a mutable/predecessor result or leaf-repository source change.

  19. claude commented on Sep 7, 2026

    @claude

    Confirmed fixed — the rejected identity is now admitted

    Measured 2026-09-07T07:26Z, following up on the diagnosis in #1927 (comment).

    workflow before 2026-09-07T04:10Z 2026-09-07T07:26Z
    codeql-scan-dispatch.yml 0 successes across 2352 runs 5 10
    opencode-review-dispatch.yml 467, last one 2026-09-05T23:25 467 468

    Every one of the new codeql-scan-dispatch.yml successes carries triggering_actor = opencode-agent[bot] — the exact identity that validate-dispatch was rejecting. The earliest is run 34069630406 at 00:22:49Z; I had measured 0 successes at 00:07Z, so the allowlist changed inside that 15-minute window.

    Observed end to end on a PR rather than only in the aggregate. On .github#1967@8ca96943 the required CodeQL job 101635348456 fail-closed at 07:24:51Z exactly as before:

    DISPATCH_OUTCOME: success
    VERDICT_STATE: pending
    ##[error]CodeQL scan dispatched. The dispatch workflow will rerun this exact failed CodeQL job
             after publishing its terminal verdict.
    

    The difference is what happened two seconds later: CodeQL Scan Dispatch runs 34095345291 and 34095345750 were created for that exact head, both with actor = opencode-agent[bot], and neither was rejected at the authorization gate. That fail-closed is now the designed wait-state it always read as, rather than a dead end.

    Scope of the claim, stated precisely: what is proven is that the authorization gate no longer rejects the App-token sender. Those two dispatches are still queued, so no terminal verdict has been published on that head yet and I am not claiming the full publish-and-rerun cycle has completed — only that the step which made it impossible is gone. #1925's separate defect on the same workflow is untouched by this and is presumably why some of those runs still need watching.

    Leaving the issue open for the owner to close, since the fix was theirs and the remaining verification is the first completed verdict.


    Generated by Claude Code

  20. seonghobae commented on Sep 7, 2026

    @seonghobae
    ContributorAuthor

    Fresh Context Fabric exact-head canary after protected .github#2009: context-graph-contracts#4@5117383ac15cdfe3813340455cd99b92937ef47c, required CodeQL run 34009096429, Python job rerun from 101632390010 to attempt-3 job 101846612741 after .github/main@78a4937c684a54ca8e415822c913742f41c6efc4 was protected truth. The rerun acquired a hosted runner and executed, so this is not runner starvation. It still failed closed at Read current-head CodeQL dispatch verdict: no authenticated codeql-dispatch/python terminal status exists for the unchanged exact head, and the rerun correctly refuses to synthesize or redispatch evidence on RUN_ATTEMPT=3. Thus #2009's legacy-payload compatibility fix does not retroactively recover an already-failed handoff whose dispatch/verdict was never durably published. Keep this in the central owner lane: repair/reconcile the dispatch authorization/configuration path tracked here, then provide a supported exact-head re-dispatch/recovery path (or fresh required-workflow event) that publishes the authenticated verdict before rerunning the required job. Do not patch CGC product source or treat the failed rerun as passing evidence.

  21. seonghobae commented on Sep 13, 2026

    @seonghobae
    ContributorAuthor

    Closing on re-verification, 2026-09-13. The authorization decision this issue deferred to the owner was taken on 2026-09-07 00:14Z: repository variable OPENCODE_REPOSITORY_DISPATCH_ACTOR now reads github-actions[bot],opencode-agent[bot] (the app identity introduced by 4a5dfd82 is allow-listed alongside the original scheduler identity, and the workflow's check was made list-aware in f509b29c0 "fix(security): isolate privileged review dispatch"). Live effect, exact evidence:

    The three workflows therefore no longer reject every dispatch. Remaining dispatch-chain gaps are tracked separately (#1929 status-publication proofs, #2125/#2126 stale peer-check verdicts, #1948 gateway budget); none of them is the allow-list mismatch recorded here.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority: highHigh-priority or P1 work

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions