Repository navigation
Description
세 개의 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
seonghobae commentedon Sep 5, 2026
#1929가 같은 근본 원인을 독립적으로 찾았습니다. 두 이슈를 함께 보셔야 하고, #1929가 이 이슈에 없던 증거를 둘 들고 있습니다.
실패율 정량:
그리고 이 이슈가 놓친 반례: 실패 표본 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
seonghobae commentedon Sep 5, 2026
#1929가 제기한 반례("신원이 맞는데도 실패한 11건")를 추적했습니다. 별개 원인이 맞고, 그 원인은 이미 해소됐습니다. 그래서 이 이슈의 변수 수정이 리뷰 복구의 남은 마지막 조건일 가능성이 높습니다.
두 번째 실패 양식은 인가와 무관합니다
actor=github-actions[bot]인 실패 run을 열었습니다(33898309078, 2026-09-04T17:01:10Z).인가는 통과합니다. 훨씬 뒤인 coverage/docstring 증거 측정에서 죽습니다. 이 이슈가 다루는 인가 거부와는 완전히 다른 경로입니다.
그 원인은
#1883이 고쳤습니다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으로 직접 측정한 값입니다.그래서 남는 그림
두 원인이 시간적으로 거의 맞물려 교대했습니다. 09-04T17까지는 B가, 그 이후로는 A가 리뷰를 막고 있습니다. 그래서 "실패가 끊긴 적이 없다"는 관측이 나오고, 원인을 하나로 보면 어느 쪽도 설명이 안 됩니다.
확정하지 못하는 것 — 명시합니다
#1883병합 이후에 인가를 통과한 dispatch가 단 한 건도 없습니다. 09-04T17 이후 디스패처가 전부opencode-agent[bot]이라 A에서 먼저 걸리기 때문입니다. 즉 B가 정말 해소됐는지는 아직 실행으로 검증된 적이 없습니다. 세 번째 원인이 없다는 것도 증명할 수 없습니다 — 그 시험이 수행된 적이 없습니다.따라서 정확한 표현은 이렇습니다: A를 고치면 B는 이미 해소되어 있으므로 리뷰가 복구될 가능성이 높고, 아니라면 그때 드러나는 것이 세 번째 원인입니다. A 수정이 진단 도구 역할도 겸합니다.
관련: #1929, #1925, #1883
seonghobae commentedon Sep 5, 2026
앞선 코멘트의 "A를 고치면 복구될 가능성이 높다"를 정정합니다. 44건 전수를 근본 원인으로 재분류하니 세 번째 원인이 나왔고, 그것이 최대이며 미해소입니다.
근본 원인 분포 (44건 전수)
스텝을 run 간에 합산하면 하류 증상이 중복 계산됩니다(
Publish repository_dispatch OpenCode status25건 같은 것은 대부분 증상입니다).validate-pr-metadata → coverage-source-tree → coverage-evidence → opencode-review의존 순서에서 처음 실패한 job으로 다시 냈습니다.앞 코멘트에서 제가 제시한 원인 B(coverage/docstring)는 32%뿐입니다. run 하나를 열고 44건 전체를 설명한 것이 틀렸습니다.
최대 원인 16건의 실제 내역
##[endgroup]뒤의 실행 출력만 읽었습니다. 그 앞은 스크립트 소스 에코라 모든 에러 문구를 포함하고 있어서, 그대로 grep하면 어느 이야기든 만들 수 있습니다.9건의 실제 로그입니다.
head는 맞고 base만 어긋납니다. 디스패치는 보낸 시점의 base SHA를 싣는데, 큐에서 몇 시간 기다리는 동안
main이 전진합니다. 3건은 검증에 도달했을 때 PR이 이미 닫혀 있었고, 같은 기전입니다.원인 C — 큐 지연이 디스패치를 스스로 무효화합니다
16건 중 12건이 큐 지연의 결과입니다. 그리고 자기강화입니다. 대기가 길수록 base가 더 드리프트하고, 실패가 재시도를 부르고, 재시도가 대기를 늘립니다.
이 이슈의 변수 수정으로도,
#1883으로도 고쳐지지 않습니다.그래도 순서는 A 먼저입니다
"A 수정이 진단 도구를 겸한다"는 부분은 유지됩니다. A를 고쳐야 인가를 통과한 dispatch가 다시 생기고, 그때 C가 얼마나 남는지 실행으로 보입니다. 지금은 09-04T17 이후 인가 통과 dispatch가 0건이라 C의 현재 규모를 측정할 방법이 없습니다.
설계 판단이 필요한 지점 하나만 남깁니다. base SHA를 검증 조건에 쓰는 한 대기 시간이 곧 실패율이 됩니다. 지금 대기가 벽시계의 99.87%(실측)인 상태에서는 이 검증이 거의 항상 실패합니다. 완화 방향(base 검증을 head 고정으로 좁히기, 또는 base 드리프트를 허용 범위로 다루기)이 있을 수 있으나 신뢰 경계에 관한 결정이라 제가 정할 사안이 아닙니다.
관련: #1929, #1925, #1883
seonghobae commentedon Sep 5, 2026
코드 측 수정 #1932가 올라왔고, 이 이슈가 짚은 소비처 셋 중 하나를 덮습니다. 잔여 범위를 기록합니다.
origin/main에서 세 게이트 블록을 대조했습니다.#1932만 머지되면:
opencode-review-dispatch는 앱 신원을 받지만,codeql-scan-dispatch는 #1925의toJSON()수정 후 155행에 도달해 여전히 거부되고(앞 코멘트의 순서 의존 그대로),pr-review-fix-scheduler(마지막 성공 2026-08-16)도 그대로입니다. 셋을 함께 다루지 않으면 같은 변수를 소비하는 세 파일이 서로 다른 파싱을 갖게 되어 그것이 다음 드리프트가 됩니다.#1932를 넓힐지 나머지 둘을 별건으로 낼지는 #1932 작성자 판단이고, 여기엔 위치만 남깁니다.
관련: #1932, #1929, #1925
seonghobae commentedon Sep 5, 2026
이 이슈가 짚은 소비처 셋을 #1932가 전부 덮습니다(
d44aa07d).opencode-review-dispatch.yml:127,codeql-scan-dispatch.yml:155,pr-review-fix-scheduler.yml:156이 같은 쉼표 목록 파싱을 씁니다 — actor·sender가 같은 항목과 일치해야 하고, 빈 목록은 거부, 단일 값은 그대로. 변수 자체는 소유자 결정으로 남겨뒀고,gh variable set한 줄은 #1929에 적혀 있습니다.seonghobae commentedon Sep 5, 2026
Fresh downstream canary after #1926's matrix/env repair:
ContextualWisdomLab/quarantine-sandbox-runtime#1@e9ee8470dbafff1c4518b45df165725bff80117e, required CodeQL PR run33962688991, compatibility jobs101316155183(python) and101316155232(actions). The consumer jobs obtain the repository-scoped app token, successfully POST thecodeql-scanrepository_dispatch, receive no authenticated terminalcodeql-dispatch/{language}verdict, and fail closed as pending. #1925's live timeline now shows post-#1926 dispatch handlers reaching authorization and rejecting actor/senderopencode-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.seonghobae commentedon Sep 5, 2026
Fresh CodeQL consumer evidence from
ContextualWisdomLab/LineageWeave#914@61ed3a3712d252e3c179a71d297c52f05e1bac20confirms this stale-identity boundary on the native CodeQL dispatcher after the separate matrix-serialization fix was already merged. The required leaf CodeQL run33979627116dispatched three exact-head shards; central repository_dispatch runs33988646498,33988691535, and33988735145all eventually obtained runners and all failed invalidate-dispatch -> Bind workflow inputs to live organization pull request metadata, while the actual scan job was skipped. Exact log for run33988646498/ job101366754283showsDISPATCH_ACTOR=opencode-agent[bot],DISPATCH_SENDER=opencode-agent[bot], andALLOWED_DISPATCH_ACTOR=github-actions[bot], ending withrepository_dispatch authorization rejected ... because both must match one configured scheduler identity.The same run also proves theSUPPLIED_MATRIXis now valid JSON and reaches the authorization check, so.github#1926did move the failure to the predicted next gate rather than leaving matrix parsing as the cause. Nocodeql-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.seonghobae commentedon Sep 5, 2026
The original intent can now be resolved from merged owner history rather than guessed.
.github#1497(merged as4a5dfd823c678f97f85f5e40a4bd12b202885d35) 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 ambientgithub.tokenmerely to recover the historicalgithub-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.seonghobae commentedon Sep 5, 2026
Correction to my immediately preceding owner-intent comment: do not replace the variable with only
opencode-agent[bot]. Fresh owner history showspr-review-merge-scheduler.ymlalso legitimately dispatches asgithub-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 valuegithub-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.seonghobae commentedon Sep 6, 2026
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@5117383ac15cdfe3813340455cd99b92937ef47cran requiredCodeQL PR34009096429. Its local compatibility shard101428051612acquired 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 centralCodeQL Scan Dispatchrun34013203830eventually acquired hosted runner1001704430;validate-dispatchjob101432456623then failed only at the dispatcher authorization boundary. Exact log values wereDISPATCH_ACTOR=opencode-agent[bot],DISPATCH_SENDER=opencode-agent[bot],ALLOWED_DISPATCH_ACTOR=github-actions[bot], followed byrepository_dispatch authorization rejected ... because both must match one configured scheduler identity.The target remained exactCGC#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_ACTORas 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 emitsopencode-agent[bot], while the scheduler path emitsgithub-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 exactlygithub-actions[bot],opencode-agent[bot]after revalidating that #1932's parser remains on protectedmain. 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-dispatchto pass, the actual CodeQL shard to execute, authenticatedcodeql-dispatch/pythonandcodeql-dispatch/actionsterminal statuses to bind to5117383..., 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.seonghobae commentedon Sep 6, 2026
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#4exact head:5117383ac15cdfe3813340455cd99b92937ef47c.34009096429dispatched the Python and Actions shards from jobs101428051612/101428051650. Both acquiredubuntu-24.04hosted runners and used the exchanged repository-scoped app token for the centralrepository_dispatch; this lane is not arunner_id=0acquisition failure.34013203830and34013205147target the same repository/PR/head. In run34013205147,validate-dispatchjob101432460171also acquired a hosted runner and reached the authorization gate.DISPATCH_ACTOR=opencode-agent[bot],DISPATCH_SENDER=opencode-agent[bot], whileALLOWED_DISPATCH_ACTOR=github-actions[bot]; the job fails withrepository_dispatch authorization rejected ... because both must match one configured scheduler identity..githubsource at43024633eba9d96b0456970391360da5a171fbdaalready 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/pythonandcodeql-dispatch/actionsverdicts 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.
seonghobae commentedon Sep 6, 2026
Fresh consumer canary from
ContextualWisdomLab/naruon#1505(2026-09-06): exact product heada271c1ffd3d4357c2803b6a3f6c971538ecc9e06still targets protecteddevelop@042b0c70531b229af3acbd0421a2f23098d848b3; that protected tip is unchanged, so the known queued-runbase_shadrift class is not applicable to this canary. Application CI34025523073, Bandit34025523024, Docker34025523141, Security Scan34025523048, and Semgrep34025522990are terminal-success, and CodeRabbit review5125084510APPROVED the same exact commit. CodeQL PR34025522963is the sole repository-owned failed workflow in this set: language detection succeeds, and all three compatibility jobs (actions,python,javascript-typescript) completeRequest current-head CodeQL scan dispatchsuccessfully before failing atRelease 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.seonghobae commentedon Sep 6, 2026
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.ymlalready performs the same OIDC -> OpenCode App token exchange (scheduler_app_token), but later mutation/dispatch paths may choosePR_REVIEW_MERGE_TOKEN || OPENCODE_APPROVE_TOKEN || scheduler_app_token || github.token. That explains why bothgithub-actions[bot]andopencode-agent[bot]have appeared. BroadeningOPENCODE_REPOSITORY_DISPATCH_ACTORto 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.ymland 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 toopencode-agent[bot], set/retain the handler allowlist to that one exact principal and add a regression proving nogithub.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 rejectsgithub-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.seonghobae commentedon Sep 6, 2026
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@4a4ddc2162bd48314dc032afd08ec6a2724178dbconcurrently 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.fix(scheduler): canonicalize repository dispatch identity
seonghobae commentedon Sep 6, 2026
SOURCE RECEIPT / WRITER RELEASE — canonical transport-identity repair is now Draft #1984.
Exact remote head:
71bda03067ad94e90db53ad3274e43dc4b242eedParent: protected
main@49eb9e7035a6994fffb5b24bf943156be27a02fbExact tree:
7b81c66cb1a97f27fd89e894e238e015c867648cRED: 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:
399 passed2970 passed, 1 skipped, 21 subtests passedscripts/ci/test_strix_quick_gate.sh: PASSThe 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.
claude commentedon Sep 6, 2026
Refinement: exactly one dispatch has been authorized since 2026-08-31, and its actor is the whole diagnosis
Re-measured
2026-09-06T18:2xagainst 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:triggering_actor2026-09-05T23:25:16Z2026-09-06T03:13:39Zgithub-actions[bot].github#1946@db361daaevent=repository_dispatch, and all four jobs —validate-pr-metadata,coverage-source-tree,coverage-evidence,opencode-review— concludedsuccess. Its validate log ends with the authorization line rather than the rejection:Every rejected dispatch carries a different sender, not a different allowlist. Three failures sampled across three repositories today (
.github#1967job 101520441575,naruon#1531,fast-mlsirm#1764) all stop at the same step,Bind workflow inputs to live organization pull request metadata, withcoverage-source-tree/coverage-evidence/opencode-reviewallskipped:ALLOWED_DISPATCH_ACTORis 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:GITHUB_TOKENmay 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.opencode-review.yml/codeql-pr.ymlrun underpull_request_targetwith a read-only workflow token, so they mint a repository-scoped App token (POST ${OPENCODE_API_BASE_URL}/exchange_github_app_token) and sendrepos/.../dispatcheswithGH_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:
The rerun cannot happen, so the required check is a permanent fail-closed rather than a wait-state. Same shape on the CodeQL side (
.githubjob 101426759771 forcontextual-orchestrator#1082,DISPATCH_OUTCOME: success/VERDICT_STATE: pending, unresolved for 13 h), andcodeql-scan-dispatch.ymlshows 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]requirescontents: writeon apull_request_targetjob, which AGENTS.md/CLAUDE.md forbid ("Do not widen apull_request_targetjob token to repository-write permission"). The receiving gate already parses a comma-separated list and its own comment says the allowlist is shared withopencode-review-dispatch.ymlandpr-review-fix-scheduler.yml, so appending the App identity is the change the code was written to accept:vars.OPENCODE_REPOSITORY_DISPATCH_ACTORis 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→403through 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
seonghobae commentedon Sep 6, 2026
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.
101583929799, started after the settings change and receivedALLOWED_DISPATCH_ACTOR=github-actions[bot],opencode-agent[bot].DISPATCH_ACTOR=opencode-agent[bot]andDISPATCH_SENDER=opencode-agent[bot]matched the same listed principal, and the log emittedAuthorized repository_dispatch actor=opencode-agent[bot] sender=opencode-agent[bot].f8be5e31...no longer matched live head6e1194aa.... It is therefore proof of settings/authorization convergence, not an end-to-end CodeQL GREEN.opencode-agent[bot]for unchanged0bb8f7c06cb2ce291101e7014afe90bef0fe40c4, 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 provesopencode-agent[bot]is legitimate. The minimal repair was and is the #1932 same-identity receiver contract plus exactlygithub-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 valuegithub-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.
seonghobae commentedon Sep 6, 2026
Wardnet exact consumer evidence for the #1925 -> #1927 sequence:
wardnet#140@a904558b79ef392515dff4b501692112ad6beabfhas CI/Fuzz/Security/SAST terminal GREEN, while CodeQL PR34020215930dispatched 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 exacta904558...passes that authorization boundary and produces a terminal source-bound verdict. Do not substitute a mutable/predecessor result or leaf-repository source change.docs(gap-baseline): record the dispatch-actor outage and its fix, the…
claude commentedon Sep 7, 2026
Confirmed fixed — the rejected identity is now admitted
Measured
2026-09-07T07:26Z, following up on the diagnosis in #1927 (comment).codeql-scan-dispatch.ymlopencode-review-dispatch.ymlEvery one of the new
codeql-scan-dispatch.ymlsuccesses carriestriggering_actor = opencode-agent[bot]— the exact identity thatvalidate-dispatchwas rejecting. The earliest is run 34069630406 at00:22:49Z; I had measured 0 successes at00: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@8ca96943the required CodeQL job 101635348456 fail-closed at07:24:51Zexactly as before:The difference is what happened two seconds later:
CodeQL Scan Dispatchruns34095345291and34095345750were created for that exact head, both withactor = 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
seonghobae commentedon Sep 7, 2026
Fresh Context Fabric exact-head canary after protected
.github#2009:context-graph-contracts#4@5117383ac15cdfe3813340455cd99b92937ef47c, required CodeQL run34009096429, Python job rerun from101632390010to attempt-3 job101846612741after.github/main@78a4937c684a54ca8e415822c913742f41c6efc4was protected truth. The rerun acquired a hosted runner and executed, so this is not runner starvation. It still failed closed atRead current-head CodeQL dispatch verdict: no authenticatedcodeql-dispatch/pythonterminal status exists for the unchanged exact head, and the rerun correctly refuses to synthesize or redispatch evidence onRUN_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.seonghobae commentedon Sep 13, 2026
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_ACTORnow readsgithub-actions[bot],opencode-agent[bot](the app identity introduced by4a5dfd82is allow-listed alongside the original scheduler identity, and the workflow's check was made list-aware inf509b29c0"fix(security): isolate privileged review dispatch"). Live effect, exact evidence:opencode-review-dispatch.ymlrun34743239238(2026-09-13 06:38Z, success) logsAuthorized repository_dispatch actor=opencode-agent[bot] sender=opencode-agent[bot] target=ContextualWisdomLab/.github.— the same actor pair this issue showed being rejected.codeql-scan-dispatch.ymlhas 23 successful runs on the first page ofstatus=success(2026-09-07 → 2026-09-12), and today's PR heads carrycodeql-dispatch/*SUCCESS statuses (docs(gap): refresh product-technical baseline to 2026-09-09 (main 7fd571d, 205 PRs, systemic RCA) #2060, fix(security-scan): use current OSV output flags and bind SARIF upload to the head checkout #2143).opencode-review-dispatch.ymlsuccesses today at 05:19Z, 05:43Z, 06:38Z onmain.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.