diff --git a/.github/workflows/opencode-review.yml b/.github/workflows/opencode-review.yml index 1c7eaef01..45b2e19a2 100644 --- a/.github/workflows/opencode-review.yml +++ b/.github/workflows/opencode-review.yml @@ -3006,6 +3006,12 @@ jobs: printf 'OpenCode could not publish %s; the requested GitHub side effect is unavailable.\n' "$action" >&2 if [ -s "$error_file" ]; then sed 's/^/gh: /' "$error_file" >&2 || true + if grep -Eiq 'Unprocessable Entity.*HTTP 422' "$error_file"; then + printf 'gh: GitHub returned HTTP 422 for this review write; likely causes are token/event policy, a non-reviewable commit_id, or duplicate actor review state.\n' >&2 + fi + if grep -Eiq 'API rate limit exceeded for installation ID|secondary rate limit|You have exceeded a secondary rate limit' "$error_file"; then + printf 'gh: GitHub rate-limited the review write token; retry after the reported reset window or use a less-contended review token.\n' >&2 + fi fi } @@ -3302,12 +3308,22 @@ jobs: review_write_token="$OPENCODE_APP_TOKEN" review_write_token_source="opencode-app" elif [ -n "${CHECK_LOOKUP_GH_TOKEN:-}" ] && [ "${GH_REPOSITORY:-}" = "${GITHUB_REPOSITORY:-}" ]; then - review_write_token="$CHECK_LOOKUP_GH_TOKEN" - review_write_token_source="github-token" + if [ -n "${configured_review_write_token:-}" ] && [ "${configured_review_write_token:-}" != "${CHECK_LOOKUP_GH_TOKEN:-}" ]; then + review_write_token="$configured_review_write_token" + if [ -n "${OPENCODE_APP_TOKEN:-}" ] && [ "${configured_review_write_token:-}" = "${OPENCODE_APP_TOKEN:-}" ]; then + review_write_token_source="opencode-app" + else + review_write_token_source="configured" + fi + review_write_fallback_token="$CHECK_LOOKUP_GH_TOKEN" + else + review_write_token="$CHECK_LOOKUP_GH_TOKEN" + review_write_token_source="github-token" + fi elif [ -n "${OPENCODE_APP_TOKEN:-}" ] && [ "${GH_TOKEN:-}" = "${OPENCODE_APP_TOKEN:-}" ]; then review_write_token_source="opencode-app" fi - if [ -n "${configured_review_write_token:-}" ] && [ "${configured_review_write_token:-}" != "${review_write_token:-}" ]; then + if [ -z "${review_write_fallback_token:-}" ] && [ -n "${configured_review_write_token:-}" ] && [ "${configured_review_write_token:-}" != "${review_write_token:-}" ]; then review_write_fallback_token="$configured_review_write_token" fi overview_comment_token="$review_write_token" @@ -3323,6 +3339,12 @@ jobs: printf 'OpenCode could not publish %s; continuing without review side effect.\n' "$action" >&2 if [ -s "$error_file" ]; then sed 's/^/gh: /' "$error_file" >&2 || true + if grep -Eiq 'Unprocessable Entity.*HTTP 422' "$error_file"; then + printf 'gh: GitHub returned HTTP 422 for this review write; likely causes are token/event policy, a non-reviewable commit_id, or duplicate actor review state.\n' >&2 + fi + if grep -Eiq 'API rate limit exceeded for installation ID|secondary rate limit|You have exceeded a secondary rate limit' "$error_file"; then + printf 'gh: GitHub rate-limited the review write token; retry after the reported reset window or use a less-contended review token.\n' >&2 + fi fi } diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index 85b97b9b0..233dcb5bd 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -856,6 +856,8 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_contains "$workflow_file" 'review_write_token="$GH_TOKEN"' "opencode approval starts review writes from the configured token" assert_file_contains "$workflow_file" 'review_write_token="$OPENCODE_APP_TOKEN"' "opencode approval uses the app token for cross-repository review writes" assert_file_contains "$workflow_file" 'review_write_token="$CHECK_LOOKUP_GH_TOKEN"' "opencode approval uses the workflow token for same-repository review writes" + assert_file_contains "$workflow_file" 'review_write_token="$configured_review_write_token"' "opencode approval prefers configured app review token over same-repository workflow token when available" + assert_file_contains "$workflow_file" 'review_write_fallback_token="$CHECK_LOOKUP_GH_TOKEN"' "opencode approval keeps same-repository workflow token as review publication fallback" assert_file_not_contains "$workflow_file" 'review_write_token="${OPENCODE_APP_TOKEN:-$GH_TOKEN}"' "opencode approval must not force same-repository review writes through the app token" assert_file_contains "$workflow_file" 'env GH_TOKEN="$review_write_token" gh api -X POST "repos/${GH_REPOSITORY}/pulls/${PR_NUMBER}/reviews"' "opencode review writes use the review write token" assert_file_contains "$workflow_file" 'app_token_limited_check_lookup()' "opencode approval detects app-token-limited GitHub Checks lookups" @@ -875,6 +877,8 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_contains "$workflow_file" 'warn_gh_publication_failure "initial review overview comment"' "opencode initial overview comment soft-fails permission-denied publication errors" assert_file_contains "$workflow_file" 'warn_gh_publication_failure "pull review with primary review token"' "opencode approval explains primary review publication failures" assert_file_contains "$workflow_file" 'warn_gh_publication_failure "pull review with fallback review token"' "opencode approval explains fallback review publication failures" + assert_file_contains "$workflow_file" 'GitHub returned HTTP 422 for this review write; likely causes are token/event policy' "opencode approval logs an actionable HTTP 422 publication reason" + assert_file_contains "$workflow_file" 'GitHub rate-limited the review write token; retry after the reported reset window' "opencode approval logs an actionable rate-limit publication reason" assert_file_contains "$workflow_file" 'REVIEW_PUBLISH_RETRY_ATTEMPTS: "3"' "opencode approval gives review publication a bounded retry budget" assert_file_contains "$workflow_file" 'gh_error_is_retryable_publication_failure()' "opencode approval detects retryable GitHub review publication throttles" assert_file_contains "$workflow_file" 'review_publish_retry_sleep_seconds()' "opencode approval can wait until a near GitHub rate-limit reset before retrying review publication" diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index f17a70bbf..5c7d7b65a 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -327,6 +327,8 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent(): assert 'review_write_token="$GH_TOKEN"' in workflow assert 'review_write_token="$OPENCODE_APP_TOKEN"' in workflow assert 'review_write_token="$CHECK_LOOKUP_GH_TOKEN"' in workflow + assert 'review_write_token="$configured_review_write_token"' in workflow + assert 'review_write_fallback_token="$CHECK_LOOKUP_GH_TOKEN"' in workflow assert 'review_write_token="${OPENCODE_APP_TOKEN:-$GH_TOKEN}"' not in workflow assert 'REVIEW_PUBLISH_RETRY_ATTEMPTS: "3"' in workflow assert "gh_error_is_retryable_publication_failure()" in workflow @@ -334,6 +336,8 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent(): assert 'post_pull_review_with_retry "primary review"' in workflow assert 'post_pull_review_with_retry "fallback review"' in workflow assert "hit a retryable GitHub API throttle; retrying attempt" in workflow + assert "GitHub returned HTTP 422 for this review write; likely causes are token/event policy" in workflow + assert "GitHub rate-limited the review write token; retry after the reported reset window" in workflow assert "Review execution contracts" in workflow assert "Accessibility/i18n:" in workflow assert "Supply-chain/license:" in workflow