Skip to content
12 changes: 12 additions & 0 deletions .github/workflows/strix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,15 @@ jobs:
- name: Publish same-head manual Strix status
env:
TARGET_APP_STATUS_TOKEN: ${{ steps.target_app_token.outputs.token || '' }}
# Same conditional as the strix job's own GITHUB_STATUS_TOKEN above:
# github.token only has statuses:write on THIS repository, so it is
# only worth offering as a publish credential when the dispatch
# target is this repository itself (the self-referential case
# target-app-token cannot cover -- the OpenCode app is scoped to
# sibling repos, not to ContextualWisdomLab/.github as its own
# target). Empty for a cross-repo target, matching post_strix_status's
# existing empty-token skip.
GITHUB_STATUS_TOKEN: ${{ (github.event.client_payload.target_repository == '' || github.event.client_payload.target_repository == github.repository) && github.token || '' }}
Comment thread
seonghobae marked this conversation as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📝 Info: Fallback remains repository-scoped

An absent target resolves to the workflow repository, while an explicit matching target selects GITHUB_STATUS_TOKEN. Sibling targets leave it empty and skip publication.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

GITHUB_STATUS_READ_TOKEN: ${{ github.token }}
PR_REVIEW_MERGE_STATUS_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || '' }}
OPENCODE_APPROVE_STATUS_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN || '' }}
Expand Down Expand Up @@ -1202,6 +1211,9 @@ jobs:
if post_strix_status "target-app-token" "$TARGET_APP_STATUS_TOKEN"; then
exit 0
fi
if post_strix_status "github-token" "$GITHUB_STATUS_TOKEN"; then
exit 0
fi
Comment on lines +1214 to +1216

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📝 Info: Fallback ordering preserves sibling behavior

Self-target runs try GITHUB_STATUS_TOKEN after the app token. Sibling runs skip the empty token, preserving the existing credential sequence.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

if post_strix_status "pr-review-merge-token" "$PR_REVIEW_MERGE_STATUS_TOKEN"; then
exit 0
fi
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ this file. The format follows Keep a Changelog, and versioned releases follow
Semantic Versioning where the repository publishes a release.

## [Unreleased]
- Fix `strix.yml`'s `publish-manual-pr-evidence-status` job failing to
publish a manual Strix status back to `.github` when it is the
`repository_dispatch` target of its own Strix run: `target-app-token` is
scoped for sibling repositories and always 403s for this self-referential
case, and the job was missing the `github.token` fallback (conditioned on
`target_repository == github.repository`, matching the job's own
already-declared `statuses: write` permission) that a near-identical
block elsewhere in the same file already had. No behavior change for
genuine cross-repo targets.
- Harden the review sidecar's per-account catalog cap against silent drift:
`contextual_orchestrator_review_launcher.py`'s two
`build_zdr_prioritized_catalog` call sites now source their
Expand Down
33 changes: 23 additions & 10 deletions docs/product-technical-gap-baseline.md
Original file line number Diff line number Diff line change
Expand Up @@ -1133,20 +1133,33 @@ then a 502 on the actual gateway request).
has not been extended to PR #1434 specifically, so this pass did not
self-authorize one) or a `repository_dispatch` targeting a *different*
repository that does not itself edit these trusted files.
- **Secondary, separate finding on the same run**: the follow-up
`publish-manual-pr-evidence-status` job also failed —
- **Secondary, separate finding on the same run — now fixed.** The
follow-up `publish-manual-pr-evidence-status` job also failed —
`target-app-token` got `HTTP 403: Resource not accessible by integration`
publishing the (correctly non-success, per the self-test failure above)
Strix status back to `.github`'s own PR #1434. The publisher's own logic
only tolerates a publish failure silently when `STRIX_RESULT=success`; a
non-success result that also cannot be published hard-fails by design, so
this is arguably correct fail-closed behavior surfacing a real,
previously-unobserved token-scoping gap, not a logic bug. Plausibly an
edge case specific to `.github` being the `target_repository` of its own
`repository_dispatch` Strix run (this central repo normally dispatches
Strix *to* sibling repos, not to itself) rather than a gap sibling repos
would hit; not investigated further or fixed this pass given it is
downstream of, and only surfaced by, the self-test failure above.
non-success result that also cannot be published hard-fails by design,
which is correct fail-closed behavior, not a logic bug — but the actual
token-scoping gap it surfaced was real: `target-app-token` (an OpenCode
app-token exchange) is scoped for *sibling* repositories, not for
`.github` as the target of its own `repository_dispatch` (this central
repo normally dispatches Strix *to* siblings, not to itself), so it can
never succeed for this specific self-referential case. The job's own
`permissions: statuses: write` block already grants `github.token`
exactly the scope this case needs, and the sibling status-publish block
earlier in the same file (the `strix` job's own inline publish step, ~40
lines above) already had a `github-token` fallback conditioned on
`target_repository == '' || target_repository == github.repository` —
this second, near-identical block had simply drifted without it. Fixed
by adding the same conditional `GITHUB_STATUS_TOKEN` env var and a
matching `post_strix_status "github-token" "$GITHUB_STATUS_TOKEN"` call
to this job's fallback chain, mirroring the existing pattern exactly;
empty (never attempted) for a genuine cross-repo target, so no behavior
change for the normal sibling-repo case. Full local suite unaffected
(1882 passed, 100% interrogate); not verified on a live hosted run since
reproducing it requires a `repository_dispatch` Strix run targeting
`.github` itself, the same rare trigger path that surfaced the bug.

## 2026-08-30 ZDR/NIM-routing architecture review (owner-directed)

Expand Down
25 changes: 23 additions & 2 deletions scripts/ci/test_strix_quick_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,29 @@ assert_strix_workflow_pr_trigger_hardened() {
assert_file_contains "$workflow_file" "default-branch repository_dispatch evidence cannot cancel" "strix workflow documents manual evidence isolation from branch protection contexts"
assert_file_contains "$workflow_file" "re-dispatches exact-head evidence" "strix workflow documents current-head queue recovery"
assert_file_contains "$workflow_file" "refs/pull/<n>/head has already advanced before this queued run starts" "strix workflow documents stale scan queue avoidance"
# Two declarations as of 2026-08-30: the strix job's own inline status
# publish, and publish-manual-pr-evidence-status's -- the latter added a
# github-token fallback for the self-referential case where .github is
# the repository_dispatch target of its own Strix run (target-app-token
# is scoped for sibling repositories and always 403s there). Both must
# stay byte-identical same-repository conditionals, not merely
# same-named: a third assertion below pins the exact conditional
# expression to the same count so a divergent copy (e.g. a typo'd
# comparison, or one job's copy missing the || github.repository
# fallback) fails closed here instead of only in the field the next
# time .github dispatches Strix at itself.
status_token_count="$(grep -c '^[[:space:]]*GITHUB_STATUS_TOKEN:' "$workflow_file")"
assert_equals "1" "$status_token_count" "strix workflow defines GITHUB_STATUS_TOKEN once so GitHub can parse repository_dispatch"
assert_equals "2" "$status_token_count" "strix workflow defines GITHUB_STATUS_TOKEN exactly twice, once per status-publishing job"
status_token_conditional_count="$(grep -c "GITHUB_STATUS_TOKEN: \${{ (github.event.client_payload.target_repository == '' || github.event.client_payload.target_repository == github.repository) && github.token || '' }}" "$workflow_file")"
assert_equals "2" "$status_token_conditional_count" "both GITHUB_STATUS_TOKEN declarations use the identical same-repository conditional"
# Three invocations, not two: the strix job's own inline publish tries
# the github-token fallback twice (once mid-chain, once as the final
# last-resort retry after every other credential has failed), while
# publish-manual-pr-evidence-status tries it once. Both jobs actually
# consuming their own declared GITHUB_STATUS_TOKEN (not just declaring
# and ignoring it) is the property under test here.
github_status_token_fallback_count="$(grep -c 'post_strix_status "github-token" "\$GITHUB_STATUS_TOKEN"' "$workflow_file")"
assert_equals "3" "$github_status_token_fallback_count" "both status-publishing jobs actually invoke their own GITHUB_STATUS_TOKEN fallback, not just declare it"
Comment on lines 220 to +231

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📝 Info: Contract test couples global counts

The assertions count declarations and calls across the whole workflow. A future unrelated publisher will require updating this test despite preserving both protected jobs.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

assert_file_not_contains "$workflow_file" "github.event.pull_request.number == 240" "strix workflow must not hard-code repository-specific PR bypasses"
assert_file_contains "$workflow_file" "models: read" "strix workflow grants only the GitHub Models read permission needed for Strix"
assert_file_contains "$workflow_file" "actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0" "strix workflow pins actions/setup-python"
Expand Down Expand Up @@ -522,7 +543,7 @@ assert_opencode_review_uses_codegraph_and_contextual_orchestrator() {
assert_file_not_contains "$workflow_file" "Wait for trusted OpenCode approval review" "opencode pull_request bridge was removed to avoid duplicate required-check resource use"
assert_file_not_contains "$workflow_file" "Trusted OpenCode requested changes for head" "opencode pull_request bridge no longer reconsumes stale trusted review state"
assert_file_not_contains "$workflow_file" "github.event.pull_request.number == 240" "opencode review workflow must not hard-code repository-specific PR bypasses"
if awk '/^ required-workflow-bootstrap:$/,/^[^ ]/' "$bootstrap_file" | grep -q '^[[:space:]]*if:'; then
if awk '/^ required-workflow-bootstrap:$/{p=1; print; next} p && /^ [A-Za-z0-9_-]+:/{exit} p' "$bootstrap_file" | grep -q '^[[:space:]]*if:'; then
record_failure "opencode required workflow bootstrap must not depend on required-workflow event payload fields"
fi
assert_file_contains "$workflow_file" 'github.event.client_payload.target_repository || github.repository' "opencode review scopes concurrency by target repository"
Expand Down
Loading