Skip to content

fix: derive workflow evidence in trusted cooperation reporter - #199

Merged
safal207 merged 15 commits into
mainfrom
fix/publish-cooperation-in-ai-gate
Jul 11, 2026
Merged

fix: derive workflow evidence in trusted cooperation reporter#199
safal207 merged 15 commits into
mainfrom
fix/publish-cooperation-in-ai-gate

Conversation

@safal207

@safal207 safal207 commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Summary

Derive exact-head GitHub Actions workflow evidence inside the trusted default-branch cooperation reporter while keeping the pull-request AI gate read-only.

Exact head: 1f2cd8bba3253ccc1e4f2df40c59ea1421e55193

Root cause

Exact-head run 29146573863 reached READY_WITH_ADVISORY_GAPS; only direct PR-comment publication failed with HTTP 403. The final architecture therefore keeps the PR gate read-only and lets the existing trusted workflow_run reporter publish after successful exact-head verification.

Changes

Workflow evidence adapter

scripts/fetch-review-threads.py now:

  • preserves raw check evidence separately from derived interpretation;
  • requires original app.slug == github-actions before parsing an Actions run URL;
  • keeps third-party checks unchanged even if their URL resembles /actions/runs/<id>;
  • fetches authoritative workflow-run metadata;
  • validates each response identity with item.id == requested run_id before append;
  • requires every Actions workflow to match the immutable PR head;
  • requires a successful exact-head AI review contract for workflow_run publication;
  • remains backward-compatible for thread-only callers.

Trusted reporter

.github/workflows/ai-review-cooperation.yml adds only job-scoped actions: read, required to read workflow-run metadata. Existing write scope remains limited to issues: write; workflow permissions still default to {}.

Executable contract

.github/workflows/ai-review-cooperation-contract.yml now verifies:

  • exact least-privilege permissions;
  • raw/derived evidence separation;
  • GitHub Actions provenance and run-ID mapping;
  • response identity validation;
  • successful exact-head AI gate binding;
  • independent --paginate binding for both check-runs?per_page=100 and statuses?per_page=100.

Evidence

  • unsafe pull_request_target approach removed before merge;
  • URL-only workflow identity spoofing fixed;
  • dead length-based completeness guard replaced with per-response identity validation;
  • pagination checks bound independently to both endpoints;
  • all current review threads resolved;
  • AI review cooperation contract run 29159696430 is green, including compile, YAML parsing, authorization matrix, endpoint pagination, identity checks, and trust boundaries.

AI review

Fresh exact-head review requested from CodeRabbit and Qodo for:

1f2cd8bba3253ccc1e4f2df40c59ea1421e55193

Merge criteria:

  • native CodeRabbit exact-head approval;
  • Qodo exact-head review completed;
  • no unresolved actionable threads;
  • all non-self-referential required CI green;
  • controlled bootstrap merge bound to this SHA.

Security boundary

  • no PAT or repository secret;
  • no pull_request_target;
  • no PR-head code executed with a write token;
  • third-party checks cannot be relabeled as GitHub Actions by URL alone;
  • stale heads, mismatched run identities, missing metadata, or ambiguous PR resolution fail closed;
  • report-marker, ledger trust, D6, and merge thresholds are unchanged.

Boundary

Changed files only:

  • .github/workflows/ai-review-cooperation.yml;
  • .github/workflows/ai-review-cooperation-contract.yml;
  • scripts/fetch-review-threads.py.

No product runtime, public asset, Lighthouse baseline, review threshold, D6 rule, report marker, or ledger acceptance rule changed.

Validation checklist

  • root-cause 403 reproduced
  • unsafe privileged PR context removed
  • workflow evidence adapter implemented
  • app-identity spoof boundary fixed
  • response identity validation added
  • endpoint-specific pagination contract added
  • cooperation contract green
  • all current review threads resolved
  • fresh CodeRabbit exact-head approval
  • fresh Qodo exact-head review
  • controlled bootstrap merge
  • end-to-end proof on ci: scope Android offline probe to runtime changes #184

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Скрипт переводит подготовку evidence на workflow-level check runs: извлекает run ID, получает и проверяет workflow-метаданные, сопоставляет их с exact-head PR и сохраняет нормализованный результат. Workflow-контракт проверяет permissions и полноту evidence.

Changes

Workflow evidence

Layer / File(s) Summary
Единый JSON API-контракт
scripts/fetch-review-threads.py
Добавлены run_json и RUN_URL; GraphQL и REST используют единый JSON-разбор, проверку ошибок и объектного ответа.
Нормализация workflow checks
scripts/fetch-review-threads.py
run_id_of извлекает run ID, а normalize_workflow_checks сохраняет raw checks, получает workflow metadata, проверяет exact-head и объединяет derived и внешние checks.
Workflow-интеграция и permissions
scripts/fetch-review-threads.py, .github/workflows/ai-review-cooperation.yml
main валидирует PR_JSON_FILE и head.sha; workflow задаёт actions: read и формирует RAW_CHECKS_FILE.
Contract-проверки evidence
.github/workflows/ai-review-cooperation-contract.yml
Добавлены проверки raw-файла, переменных, пагинации, REST endpoints, нормализации и успешного exact-head AI review contract.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

  • Issue 138 — изменения реализуют требования exact-head evidence, fail-closed policy и least-privilege cooperation contract.

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed Заголовок точно отражает основное изменение: вывод workflow evidence в trusted cooperation reporter.
Description check ✅ Passed Описание в основном соответствует шаблону: есть Summary, Root cause, Changes, Evidence, AI review и checklist.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/publish-cooperation-in-ai-gate

Comment @coderabbitai help to get the list of available commands.

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@qodo-code-review review

Exact head: 5b94c5382ea2ed2fd3bb7b6c50d7b8fc46b38932.

Please review the integrated exact-head verifier/report publisher, default-branch reporter checkout, permissions, head-drift checks, READY validation, trusted marker publishing, and diagnostic anti-spoof boundary.

Copy link
Copy Markdown
Owner Author

@coderabbitai approve

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Publish trusted AI cooperation report from within exact-head AI review gate

🐞 Bug fix ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Publish the trusted cooperation report directly from the exact-head AI review workflow.
• Eliminate reliance on flaky external GitHub event paths by using in-job PR context.
• Add guarded evidence collection, merge-readiness validation, and failure diagnostics.
Diagram

graph TD
  A["AI review gate job"] --> B["Exact-head verifier"] --> C["Checkout reporter (default)"] --> D["Collect PR evidence"] --> E["Build cooperation report"] --> F{"Conclusion READY?"}
  F -->|"yes"| G["Upsert trusted PR comment"]
  F -->|"no"| H["Fail workflow (closed)"]
  E --> I["Publish diagnostic comment"]
  D --> J{{"GitHub API"}}
  I --> J
  G --> J
  subgraph Legend
    direction LR
    _job["Job/Step"] ~~~ _ext{{"GitHub API"}} ~~~ _dec{"Decision"}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Trigger separate reporter workflow via workflow_run
  • ➕ Keeps verifier workflow shorter and isolates publishing logic
  • ➕ Can be reused by multiple entrypoints
  • ➖ Reintroduces the reliability problem (workflow_run payload gaps, rerun semantics)
  • ➖ Harder to guarantee exact-head correlation without additional anchoring
2. Publish as a Check Run (Checks API) instead of an issue comment
  • ➕ More strongly bound to a specific commit SHA
  • ➕ Avoids comment spoofing concerns entirely
  • ➖ Requires additional permissions and a different UX for reviewers
  • ➖ May complicate existing “report marker” consumption if it expects comments
3. Extract reporter steps into a reusable workflow/composite action
  • ➕ Improves maintainability and reduces YAML complexity
  • ➕ Encourages consistent evidence collection across workflows
  • ➖ Still needs careful pinning/checkout strategy to preserve the trust boundary
  • ➖ Adds indirection for debugging incident scenarios

Recommendation: The PR’s approach (publish inside the exact-head gate after verifier success, while executing reporter code only from the default branch) is the most robust fix for event-delivery flakiness and preserves a clear security boundary. If this grows further, consider extracting the reporter sequence into a reusable workflow/composite action while keeping the “default-branch reporter checkout” and head-drift fail-closed checks intact.

Files changed (1) +179 / -5

Other (1) +179 / -5
ai-review-contract.ymlPublish cooperation report from the exact-head AI gate with guarded evidence collection +179/-5

Publish cooperation report from the exact-head AI gate with guarded evidence collection

• Expands the AI review contract workflow to collect complete PR/commit evidence (REST, GraphQL threads, check runs, statuses) and run the default-branch cooperation reporter after exact-head verification. Adds merge-readiness validation (READY / READY_WITH_ADVISORY_GAPS), idempotent upsert of a trusted report comment marker, and a separate diagnostic marker if publishing fails after verifier success.

.github/workflows/ai-review-contract.yml

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 11, 2026
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
✅ Action performed

Comments resolved and changes approved.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh pr view 199 --repo safal207/robys-coffee-house-demo --json title,body,files,commits,headRefOid,baseRefName,state | jq '.'
echo "----DIFF----"
gh pr diff 199 --repo safal207/robys-coffee-house-demo

Length of output: 13497


🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh api repos/safal207/robys-coffee-house-demo/contents/.github/workflows/ai-review-contract.yml?ref=5b94c5382ea2ed2fd3bb7b6c50d7b8fc46b38932 --jq '.content' | base64 -d > /tmp/wf.yml
cat -n /tmp/wf.yml
echo "---- scripts dir ----"
gh api repos/safal207/robys-coffee-house-demo/contents/scripts?ref=5b94c5382ea2ed2fd3bb7b6c50d7b8fc46b38932 --jq '.[].name'

Length of output: 13536


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '26,30p' /tmp/wf.yml | cat -A | head -20
python3 - <<'EOF'
with open('/tmp/wf.yml','rb') as f:
    data = f.read()
lines = data.split(b'\n')
for i in range(25,30):
    print(i+1, lines[i])
EOF

Length of output: 819


🏁 Script executed:

#!/bin/bash
set -euo pipefail
mkdir -p /tmp/scripts
for f in fetch-review-threads.py ai-review-cooperation.py verify-ai-review-contract.cjs; do
  gh api "repos/safal207/robys-coffee-house-demo/contents/scripts/${f}?ref=5b94c5382ea2ed2fd3bb7b6c50d7b8fc46b38932" --jq '.content' | base64 -d > "/tmp/scripts/${f}"
done
echo "---- fetch-review-threads.py ----"
cat -n /tmp/scripts/fetch-review-threads.py

Length of output: 5229


🏁 Script executed:

#!/bin/bash
set -euo pipefail
cat -n /tmp/scripts/ai-review-cooperation.py | head -150

Length of output: 6537


🏁 Script executed:

#!/bin/bash
set -euo pipefail
grep -n "COMMENT_FILE\|_FILE'\|required_env(\|def main\|sys.argv\|'report'" /tmp/scripts/ai-review-cooperation.py | head -60

Length of output: 1095


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '405,460p' /tmp/scripts/ai-review-cooperation.py

Length of output: 1532


🏁 Script executed:

#!/bin/bash
set -euo pipefail
grep -n "Overall conclusion" /tmp/scripts/ai-review-cooperation.py

Length of output: 282


🏁 Script executed:

#!/bin/bash
set -euo pipefail
grep -n "conclusion = \|conclusion=\|'READY'\|READY_WITH_ADVISORY_GAPS\|BLOCKED\|NOT_READY" /tmp/scripts/ai-review-cooperation.py

Length of output: 659


🏁 Script executed:

#!/bin/bash
set -euo pipefail
grep -n "concurrency" /tmp/wf.yml || echo "NO CONCURRENCY BLOCK FOUND"

Length of output: 266


@safal207, review complete. Two real findings, plus confirmed-correct areas.

Findings

  1. No concurrency guard → duplicate/lost trusted comment on overlapping runs. This workflow now performs a write (issues: write, create-or-update the github-actions[bot] report comment) on every synchronize. Without a concurrency: group: ai-review-${{ github.event.pull_request.number }}, cancel-in-progress: true, two rapid pushes can run two jobs concurrently. Both compute existing_id independently before either PATCHes; if both see "no existing comment," you get two trusted report comments (marker duplication), or a stale run overwrites a newer one's content since tail -n 1 only dedups by re-reading the list at lookup time, not at write time. Given this is exactly the kind of unreliable-publish class this PR is trying to fix, add a concurrency block to the job/workflow.

  2. Fork-PR write permission is not guaranteed. For pull_request (not pull_request_target), GitHub does not grant elevated GITHUB_TOKEN scopes to workflows triggered by PRs from forked repositories regardless of the permissions: block — this depends on the repo's Actions setting "Workflow permissions" and fork-token restrictions. If any contributor opens a PR from a fork, issues: write may silently downgrade to read-only, and the new publish/diagnostic steps will 403. Worth a one-line note in the file or confirming this repo never receives fork PRs; otherwise the "unreliable event delivery" problem this PR fixes could resurface as "unreliable write permission" instead.

Verified correct (no issues):

  • GH_TOKEN env wiring is intact (an earlier redaction in my own tooling output was a display artifact, not the actual file — confirmed via raw base64 decode).
  • Env var names (PR_JSON_FILE, HEAD_COMMIT_FILE, COMMENTS_FILE, REVIEWS_FILE, REVIEW_COMMENTS_FILE, THREADS_FILE, CHECKS_FILE, STATUSES_FILE, FILES_FILE, COMMENT_FILE) line up exactly with what scripts/ai-review-cooperation.py and scripts/fetch-review-threads.py consume via required_env/read_json_env.
  • sed -nE conclusion regex matches the exact **Overall conclusion:** **{READY|READY_WITH_ADVISORY_GAPS}** line the script emits (Line 380 of ai-review-cooperation.py); other conclusions correctly fail the gate closed.
  • Head-drift is rechecked twice (post-verify, and immediately pre-publish) against EXPECTED_HEAD captured at trigger time — matches the stated design.
  • Diagnostic step is correctly gated on steps.verify.outcome == 'success' and uses a distinct marker (ai-review-cooperation-diagnostic), so it can't satisfy the ledger and won't fire if the exact-head verifier itself failed.
  • Permission scoping is minimal: issues: write (not pull-requests: write) matches the issues/comments REST endpoints actually used; checks: read matches the new check-runs REST call.
  • Reporter checkout uses persist-credentials: false, isolated path: .trusted-cooperation, pinned action SHA, default-branch ref — consistent with the stated security boundary.

; the diagnostic-only path uses a distinct marker ` so diagnostics can never satisfy the review ledger.
-->


✏️ Learnings added
Learnt from: safal207
URL: https://github.com/safal207/robys-coffee-house-demo/pull/199

Timestamp: 2026-07-11T08:11:49.898Z
Learning: In safal207/robys-coffee-house-demo, the AI review cooperation report is built by scripts/ai-review-cooperation.py (invoked as `python3 .trusted-cooperation/scripts/ai-review-cooperation.py report`) and scripts/fetch-review-threads.py, both unmodified/pre-existing on the default branch. They communicate with the calling workflow (.github/workflows/ai-review-contract.yml) purely via required env vars: PR_JSON_FILE, HEAD_COMMIT_FILE, COMMENTS_FILE, REVIEWS_FILE, REVIEW_COMMENTS_FILE, THREADS_FILE, CHECKS_FILE, STATUSES_FILE, FILES_FILE, COMMENT_FILE, REPOSITORY, PR_NUMBER, GH_TOKEN. The report body contains a literal markdown line `**Overall conclusion:** **{READY|READY_WITH_ADVISORY_GAPS|...}**` (ai-review-cooperation.py line ~380) and the marker `<!-- ai-review-cooperation

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@qodo-code-review

qodo-code-review Bot commented Jul 11, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 11 rules

Grey Divider


Action required

1. READY blocked by self-check ✓ Resolved 🐞 Bug ≡ Correctness
Description
The new validation step only allows publishing when the cooperation report conclusion is
READY/READY_WITH_ADVISORY_GAPS, but the report treats “AI review contract” as a required check and
will return WAIT_FOR_EVIDENCE when any required check is still not completed. Because the report is
generated while this workflow is still running, the “AI review contract” check run is expected to be
pending, making the validation step fail and preventing publishing.
Code

.github/workflows/ai-review-contract.yml[R116-131]

+      - name: Require merge-ready cooperation conclusion
+        id: validate_report
+        run: |
+          set -euo pipefail
+          test -s "${COMMENT_FILE}"
+          jq -e '.body | type == "string" and length > 0' "${COMMENT_FILE}" >/dev/null
+          conclusion="$(
+            jq -r '.body' "${COMMENT_FILE}" \
+              | sed -nE 's/^\*\*Overall conclusion:\*\* \*\*(READY|READY_WITH_ADVISORY_GAPS)\*\*.*$/\1/p' \
+              | head -n 1
+          )"
+          if [ -z "${conclusion}" ]; then
+            echo "Cooperation report is not merge-ready:" >&2
+            jq -r '.body' "${COMMENT_FILE}" >&2
+            exit 1
+          fi
Relevance

⭐⭐⭐ High

Team fixes CI self-gating/required-check correctness issues (e.g., prevent misleading green required
checks).

PR-#148

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The cooperation report logic explicitly treats “AI review contract” as a required check and returns
WAIT_FOR_EVIDENCE when any required check is pending; the new workflow then requires
READY/READY_WITH_ADVISORY_GAPS and fails otherwise.

scripts/ai-review-cooperation.py[26-34]
scripts/ai-review-cooperation.py[285-308]
scripts/ai-review-cooperation.py[311-326]
.github/workflows/ai-review-contract.yml[76-133]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The cooperation report’s conclusion gate in `ai-review-contract.yml` requires `READY` / `READY_WITH_ADVISORY_GAPS`, but the reporter (`scripts/ai-review-cooperation.py`) considers `AI review contract` a required check and returns `WAIT_FOR_EVIDENCE` when any required check is not `completed`. Since the workflow generates the report before it finishes, it will typically observe its own check as pending, so the validation step exits 1 and blocks publishing.

## Issue Context
- The reporter’s required check set includes `AI review contract`.
- `overall_conclusion()` forbids READY when `checks.pending > 0`.
- This workflow fetches check-runs for the head commit and immediately builds/validates the report inside the same run.

## Fix Focus Areas
- .github/workflows/ai-review-contract.yml[76-133]

### Suggested fix directions
Choose one:
1) Post-process `${CHECKS_FILE}` before running the reporter to ignore (or force-complete) the current workflow’s check-run entry (name `AI review contract`) so the report can reach READY when all *other* required checks are complete.
2) Alternatively, change the validation criteria here to accept `WAIT_FOR_EVIDENCE` and delay publishing (but that conflicts with the stated “require READY” intent), or move publishing into a different workflow that runs after completion (reintroduces event dependency).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Diagnostic text missing Turkish ✓ Resolved 📘 Rule violation ≡ Correctness
Description
The newly added PR comment body for the cooperation failure diagnostic includes English and Russian
but no Turkish variant. This violates the requirement that user-facing content support tr, en,
and ru.
Code

.github/workflows/ai-review-contract.yml[R179-195]

+            '<!-- ai-review-cooperation-diagnostic -->' \
+            '## AI cooperation publishing failed / Сбой публикации AI cooperation report' \
+            '' \
+            "Exact-head CodeRabbit verification succeeded for PR #${PR_NUMBER}, but report publishing did not complete." \
+            '' \
+            '| Stage / Этап | Outcome / Результат |' \
+            '|---|---|' \
+            "| trusted reporter checkout | ${REPORTER_CHECKOUT_OUTCOME:-unknown} |" \
+            "| configure | ${CONFIGURE_OUTCOME:-unknown} |" \
+            "| REST evidence | ${REST_OUTCOME:-unknown} |" \
+            "| GraphQL evidence | ${GRAPHQL_OUTCOME:-unknown} |" \
+            "| build report | ${BUILD_OUTCOME:-unknown} |" \
+            "| validate conclusion | ${VALIDATE_OUTCOME:-unknown} |" \
+            "| publish report | ${PUBLISH_OUTCOME:-unknown} |" \
+            '' \
+            "Run / Запуск: ${RUN_URL}"
+
Relevance

⭐⭐⭐ High

TR/EN/RU localization for user-facing diagnostic text was requested and partially accepted
previously.

PR-#195

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1540302 requires all user-facing text introduced/modified to support Turkish,
English, and Russian. The added diagnostic comment text contains English and Russian (e.g., `AI
cooperation publishing failed / Сбой ...`) but does not provide Turkish equivalents in the same
message block.

Rule 1540302: User-facing content must support three specified languages
.github/workflows/ai-review-contract.yml[179-195]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The workflow publishes a user-facing diagnostic comment that is bilingual (EN/RU) but does not include Turkish, violating the requirement to support `tr`, `en`, and `ru` for user-facing content.

## Issue Context
This text is posted into the PR as an issue comment when publishing fails, making it user-facing.

## Fix Focus Areas
- .github/workflows/ai-review-contract.yml[179-195]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Missing fork publish guard ✓ Resolved 🐞 Bug ☼ Reliability
Description
The workflow now POSTs/PATCHes PR issue comments unconditionally, but it does not verify that the PR
head repo matches the base repository before attempting write operations. Other workflows in this
repo explicitly enforce head_repo == REPOSITORY before publishing, so this path can fail
unexpectedly in fork-based PR scenarios and turn the “AI review contract” check red.
Code

.github/workflows/ai-review-contract.yml[R150-163]

+          existing_id="$(
+            gh api --paginate "repos/${REPOSITORY}/issues/${PR_NUMBER}/comments" \
+              --jq '.[] | select(.user.login == "github-actions[bot]" and (.body | contains("<!-- ai-review-cooperation -->"))) | .id' \
+              | tail -n 1
+          )"
+          if [ -n "${existing_id}" ]; then
+            gh api --method PATCH "repos/${REPOSITORY}/issues/comments/${existing_id}" \
+              -H 'Accept: application/vnd.github+json' \
+              --input "${COMMENT_FILE}"
+          else
+            gh api --method POST "repos/${REPOSITORY}/issues/${PR_NUMBER}/comments" \
+              -H 'Accept: application/vnd.github+json' \
+              --input "${COMMENT_FILE}"
+          fi
Relevance

⭐⭐ Medium

Fork-safety improvements were accepted elsewhere, but no direct prior decision on
head_repo==REPOSITORY guard for comment writes.

PR-#151

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
This workflow performs write operations to PR comments without a same-repo guard, while another repo
workflow (deepseek-review.yml) explicitly checks head_repo == REPOSITORY before it publishes a
comment—indicating this guard is expected for write paths.

.github/workflows/ai-review-contract.yml[134-163]
.github/workflows/deepseek-review.yml[51-56]
.github/workflows/deepseek-review.yml[90-104]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`ai-review-contract.yml` publishes/upserts issue comments (`POST`/`PATCH` to `/issues/.../comments`) without checking whether the PR’s head repository matches `${REPOSITORY}`. Elsewhere in this repo, workflows that publish comments first assert `head_repo == REPOSITORY` to avoid unsupported write paths.

## Issue Context
The DeepSeek reviewer workflow demonstrates an established pattern in this repo: fetch PR JSON, assert `.head.repo.full_name == REPOSITORY`, then proceed to write comments.

## Fix Focus Areas
- .github/workflows/ai-review-contract.yml[76-164]

### Suggested fix directions
Add an early guard (before any publish attempts) such as:
- Fetch PR JSON and `test "$(jq -r '.head.repo.full_name' ...)" = "${REPOSITORY}"`, and if not, exit cleanly (or skip only the publish steps via `if:`).
- Alternatively, add `if: github.event.pull_request.head.repo.full_name == github.repository` to `publish_report` and the diagnostic publisher.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread .github/workflows/ai-review-contract.yml Outdated
Comment thread .github/workflows/ai-review-contract.yml Outdated
Comment thread .github/workflows/ai-review-contract.yml Outdated

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@qodo-code-review review

Exact head: c6060bf69985b7706b9239ed4d30d679da304964.

Please review the current-run check normalization, raw/derived evidence separation, exact one-match requirement, trusted report publishing, artifact retention, and diagnostic anti-spoof boundary.

Copy link
Copy Markdown
Owner Author

Disposition: accepted
Head: 2aacfd1

Qodo findings are fixed:

  • the user-facing failure diagnostic now includes English, Turkish and Russian title, explanation, stage labels and run label;
  • fetched .head.repo.full_name is compared with the immutable event payload before any decision;
  • same_repository is exported as a step output;
  • trusted report and diagnostic write steps run only for same-repository PRs;
  • fork PRs retain evidence validation but cannot create a trusted report, so the review ledger remains fail closed;
  • current-run self-check normalization remains restricted to exactly one in-progress check and raw evidence is preserved.

No product runtime, D6 rule, report marker or ledger acceptance rule changed.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh pr view 199 --repo safal207/robys-coffee-house-demo --json headRefOid,commits | jq '.headRefOid'
echo "---- diff between previous reviewed head and new head ----"
gh api repos/safal207/robys-coffee-house-demo/contents/.github/workflows/ai-review-contract.yml?ref=c6060bf69985b7706b9239ed4d30d679da304964 --jq '.content' | base64 -d > /tmp/wf_new.yml
diff -u /tmp/wf.yml /tmp/wf_new.yml || true

Length of output: 712


🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh api repos/safal207/robys-coffee-house-demo/contents/.github/workflows/ai-review-contract.yml?ref=5b94c5382ea2ed2fd3bb7b6c50d7b8fc46b38932 --jq '.content' | base64 -d > /tmp/wf_old.yml
diff -u /tmp/wf_old.yml /tmp/wf_new.yml || true
echo "---- full new file ----"
cat -n /tmp/wf_new.yml

Length of output: 19529


🤖 web_search executed:

GitHub check-runs API details_url format for GitHub Actions jobs actions/runs run_id job job_id

Length of output: 15053


@safal207, reviewed c6060bf69985b7706b9239ed4d30d679da304964 — the current-run normalization, raw/derived split, and evidence upload.

New finding — run-id fragment match is a substring, not an exact boundary match

In the normalize_self step:

run_fragment="/actions/runs/${GITHUB_RUN_ID}"
...
select((((.details_url // .html_url // "") | contains($fragment))) and ...)

GitHub Actions check-run URLs are shaped https://github.com/{owner}/{repo}/actions/runs/{run_id}/job/{job_id} (per GitHub REST docs for check-runs / workflow-jobs). contains() is a plain substring test with no trailing delimiter, so run_fragment for run id 123 also matches URLs for run ids 1230, 1234, 12345, etc. GitHub Actions run IDs are a single monotonically increasing global counter, so this kind of numeric-prefix collision with some other concurrently in-progress run on the same commit is realistically reachable, not just theoretical.

This directly weakens the invariant the PR explicitly requires — "exactly one in-progress check whose URL belongs to the current GITHUB_RUN_ID." As written, the guarantee is actually "exactly one in-progress check whose URL contains the current run id as a numeric prefix," which is a materially different and spoofable-by-coincidence condition, since a false match could either inflate current_matches past 1 (safe fail-closed, but wrong diagnosis) or, worse, normalize the wrong check run if it's the sole in-progress match at that instant.

Fix: anchor the boundary, e.g.

run_fragment="/actions/runs/${GITHUB_RUN_ID}/"

(note trailing slash) or in jq: test("/actions/runs/" + ($run_id|tostring) + "(/|$)").

Carried over from previous review, still unaddressed in this head:

  1. No concurrency: block — duplicate/lost trusted-comment race on overlapping synchronize runs still possible.
  2. Fork-PR issues: write scope isn't guaranteed by permissions: alone under pull_request (vs pull_request_target); depends on repo's Actions fork-token settings.

Verified correct in this head:

  • Raw vs. derived evidence split (RAW_CHECKS_FILE / CHECKS_FILE) is real, not just a rename — normalize_self reads raw, fails closed (exit 1) unless exactly one in-progress match, writes derived, then re-validates the derived count before proceeding.
  • Artifact upload (ai-cooperation-${{ github.run_id }}-${{ github.run_attempt }}, if: always(), if-no-files-found: warn, 3-day retention) captures raw + derived + comment payload, satisfying the "retain raw evidence" requirement.
  • Diagnostic step remains gated on steps.verify.outcome == 'success', uses the distinct ai-review-cooperation-diagnostic marker, and now also reports NORMALIZE_OUTCOME — anti-spoof boundary between diagnostic and trusted report markers is intact, and continue-on-error: true there is a safe addition (doesn't affect overall job failure state).
  • normalize_self never writes back to the real GitHub check run (no PATCH to check-runs API) — it's a local file transform consumed only by the report builder, consistent with the "no product runtime/ledger change" claim, modulo the fragment-boundary bug above.
  • Head-drift recheck (pre-publish) and READY/READY_WITH_ADVISORY_GAPS regex validation are unchanged and still correct.

✏️ Learnings added
Learnt from: safal207
URL: https://github.com/safal207/robys-coffee-house-demo/pull/199

Timestamp: 2026-07-11T08:18:10.445Z
Learning: In safal207/robys-coffee-house-demo, .github/workflows/ai-review-contract.yml's "Normalize current AI gate after verifier success" step matches the current job's own check run by testing whether `details_url // html_url` contains a `run_fragment` string built as `"/actions/runs/${GITHUB_RUN_ID}"` via jq `contains()`. This is a substring match without a trailing delimiter, so it can false-positive-match other in-progress runs whose numeric run id has the current run id as a prefix (e.g. current run 123 matching another run 1234's check-run URL). The fragment should be anchored with a trailing slash (`"/actions/runs/${GITHUB_RUN_ID}/"`) or a regex boundary to guarantee the "exactly one current-run check" invariant this workflow depends on for its READY publishing gate.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@qodo-code-review review

Exact head: 2aacfd166b12440fcfd9a53b6b7e4c0b836aab16.

Please review the final same-repository guard, EN/TR/RU diagnostic, exact current-run normalization, raw/derived evidence separation, and trusted publish conditions.

Copy link
Copy Markdown
Owner Author

@coderabbitai approve

@safal207
safal207 dismissed coderabbitai[bot]’s stale review July 11, 2026 08:20

Stale approval: this review predates current-run check normalization and the EN/TR/RU same-repository publishing guard. Fresh native approval must bind to 2aacfd1 after the latest review request.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh api repos/safal207/robys-coffee-house-demo/contents/.github/workflows/ai-review-contract.yml?ref=2aacfd166b12440fcfd9a53b6b7e4c0b836aab16 --jq '.content' | base64 -d > /tmp/wf_final.yml
diff -u /tmp/wf_new.yml /tmp/wf_final.yml || true
echo "---- full file ----"
cat -n /tmp/wf_final.yml

Length of output: 16486


@safal207, reviewed 2aacfd166b12440fcfd9a53b6b7e4c0b836aab16.

Fixed correctly (Qodo findings + this round's request):

  • Same-repository guard: same_repository computed once in rest_evidence (comparing fetched .head.repo.full_name against the immutable EXPECTED_HEAD_REPOSITORY from the event payload) and exported as a step output; publish_report and the diagnostic step both gate on same_repository == 'true'; the fork path only logs, no write attempted → ledger stays fail-closed for forks.
  • EN/TR/RU diagnostic: title, explanation, stage table, and run label are all trilingual; still fires only when steps.verify.outcome == 'success' && failure() && same_repository == 'true', keeps the distinct ai-review-cooperation-diagnostic marker, can't satisfy the ledger.
  • Raw/derived separation is genuine: RAW_CHECKS_FILE is never mutated; normalize_self reads raw, fails closed unless exactly one in-progress match, writes CHECKS_FILE, then re-validates the derived count before build_report runs. Both files are uploaded as artifacts.

Still unresolved from prior rounds:

  1. run_fragment boundary bug (raised on c6060bf6…) is unchanged at Line 125:

    run_fragment="/actions/runs/${GITHUB_RUN_ID}"

    still a plain substring test via contains($fragment) with no trailing delimiter, so run id 123 still matches .../actions/runs/1234/job/.... This undermines the "exactly one current-run check" invariant the READY gate depends on. Suggested fix, unchanged from before:

    run_fragment="/actions/runs/${GITHUB_RUN_ID}/"

    (or a jq test("/actions/runs/" + ($run_id|tostring) + "(/|$)")).

  2. No concurrency: block — still absent from the workflow/job. Overlapping synchronize runs can still race on existing_id lookup vs. PATCH/POST in publish_report, risking duplicate or stale trusted comments. Recommend adding:

    concurrency:
      group: ai-review-${{ github.event.pull_request.number }}
      cancel-in-progress: true

Fork-token-scope concern from round 1 is now practically moot since all issues: write operations are gated behind same_repository == 'true'.


✏️ Learnings added
Learnt from: safal207
URL: https://github.com/safal207/robys-coffee-house-demo/pull/199

Timestamp: 2026-07-11T08:20:21.036Z
Learning: In safal207/robys-coffee-house-demo's .github/workflows/ai-review-contract.yml, as of head 2aacfd166b12440fcfd9a53b6b7e4c0b836aab16, the `normalize_self` step's `run_fragment="/actions/runs/${GITHUB_RUN_ID}"` substring-boundary bug (contains() without trailing delimiter, causing numeric-prefix collisions between run ids) is a known unresolved finding carried over from review of head c6060bf69985b7706b9239ed4d30d679da304964; the user has not yet fixed it despite fixing the Qodo findings (same-repository guard, EN/TR/RU diagnostic) in this same PR. A `concurrency:` block guarding overlapping `synchronize` runs is also still missing from this workflow.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Owner Author

@coderabbitai approve

coderabbitai[bot]
coderabbitai Bot previously requested changes Jul 11, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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/ai-review-contract.yml:
- Around line 22-25: Serialize workflow runs for the same pull request and head
in the verify job by adding an appropriate concurrency group based on the PR
identity, with stale runs canceled or otherwise prevented from overwriting the
latest report. Keep the existing exact-head verification and merge/status gate
behavior unchanged.
- Around line 245-247: Update the “Publish cooperation failure diagnostic” step
condition so it no longer depends on steps.verify.outputs.same_repository.
Derive the fork boundary directly from the event metadata available in the
workflow, while preserving the existing failure() and successful verification
requirements.
- Around line 233-243: Расширьте шаг Upload cooperation evidence, чтобы артефакт
сохранял полный исходный evidence, включая REST- и GraphQL-входы, использованные
для формирования заключения. Добавьте соответствующие существующие файлы или
переменные окружения в список path рядом с RAW_CHECKS_FILE, CHECKS_FILE и
COMMENT_FILE, не удаляя текущие результаты.
- Around line 227-231: Update the “Record fork publication boundary” step in the
fork-specific path to fail closed instead of only logging and exiting
successfully: terminate the step with a nonzero status after the existing
messages, or route it through a separate required failing gate. Ensure fork pull
requests cannot produce a successful job without a trusted cooperation report.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 88912980-6cac-403c-9871-0d1d01382d18

📥 Commits

Reviewing files that changed from the base of the PR and between f1cd5d1 and 2aacfd1.

📒 Files selected for processing (1)
  • .github/workflows/ai-review-contract.yml
📜 Review details
⏰ Context from checks skipped due to timeout. (6)
  • GitHub Check: CodeRabbit
  • GitHub Check: VISUAL-001 screenshot diff
  • GitHub Check: SAST-001 JavaScript and TypeScript
  • GitHub Check: ios-route-webkit
  • GitHub Check: DAST-001 passive web scan
  • GitHub Check: Verify exact-head independent review
⚠️ CI failures not shown inline (2)

GitHub Actions: Bot review disposition contract / 0_REVIEW-LEDGER exact-head findings.txt: fix: publish cooperation report inside AI review gate

Conclusion: failure

View job details

##[group]Run actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b
 with:
   script: const owner = context.repo.owner;
const repo = context.repo.repo;
const payloadPr = context.payload.pull_request;
const issuePr = context.payload.issue?.pull_request;
const prNumber = payloadPr?.number ?? (issuePr ? context.payload.issue.number : null);
if (!prNumber) {
  core.notice('Event is not associated with a pull request.');
  return;
}
const pr = (await github.rest.pulls.get({ owner, repo, pull_number: prNumber })).data;
const head = pr.head.sha.toLowerCase();
const [reviewComments, reviews, issueComments, statuses] = await Promise.all([
  github.paginate(
    github.rest.pulls.listReviewComments,
    { owner, repo, pull_number: prNumber, per_page: 100 },
  ),
  github.paginate(
    github.rest.pulls.listReviews,
    { owner, repo, pull_number: prNumber, per_page: 100 },
  ),
  github.paginate(
    github.rest.issues.listComments,
    { owner, repo, issue_number: prNumber, per_page: 100 },
  ),
  github.paginate(
    github.rest.repos.listCommitStatusesForRef,
    { owner, repo, ref: head, per_page: 100 },
  ),
]);
const reviewHeads = new Map(
  reviews.map((review) => [review.id, review.commit_id?.toLowerCase()]),
);
const reviewBots = new Set([
  'chatgpt-codex-connector',
  'chatgpt-codex-connector[bot]',
  'coderabbitai',
  'coderabbitai[bot]',
  'github-advanced-security',
  'github-advanced-security[bot]',
]);
const trustedAssociations = new Set(['OWNER', 'MEMBER', 'COLLABORATOR']);
const exactHeadBody = (body) => new RegExp(`(^|[^0-9a-f])${head}([^0-9a-f]|$)`, 'i').test(body ?? '');
const timeOf = (item) => Math.max(
  0,
  ...[item.submitted_at, item.created_at, item.updated_at]
    .map((value) => Date.parse(value ?? 0))
    .filter(Number.isFinite),
);
const isDispositionReply = (reply) => {
  const body = (reply.body ?? '').replaceAll('`', '');
  const disposition = /^Disposition:\s*(accepted|rejected-with-evidence|superseded)\s*$/im.test(body);
  cons...

GitHub Actions: Bot review disposition contract / REVIEW-LEDGER exact-head findings: fix: publish cooperation report inside AI review gate

Conclusion: failure

View job details

##[group]Run actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b
 with:
   script: const owner = context.repo.owner;
const repo = context.repo.repo;
const payloadPr = context.payload.pull_request;
const issuePr = context.payload.issue?.pull_request;
const prNumber = payloadPr?.number ?? (issuePr ? context.payload.issue.number : null);
if (!prNumber) {
  core.notice('Event is not associated with a pull request.');
  return;
}
const pr = (await github.rest.pulls.get({ owner, repo, pull_number: prNumber })).data;
const head = pr.head.sha.toLowerCase();
const [reviewComments, reviews, issueComments, statuses] = await Promise.all([
  github.paginate(
    github.rest.pulls.listReviewComments,
    { owner, repo, pull_number: prNumber, per_page: 100 },
  ),
  github.paginate(
    github.rest.pulls.listReviews,
    { owner, repo, pull_number: prNumber, per_page: 100 },
  ),
  github.paginate(
    github.rest.issues.listComments,
    { owner, repo, issue_number: prNumber, per_page: 100 },
  ),
  github.paginate(
    github.rest.repos.listCommitStatusesForRef,
    { owner, repo, ref: head, per_page: 100 },
  ),
]);
const reviewHeads = new Map(
  reviews.map((review) => [review.id, review.commit_id?.toLowerCase()]),
);
const reviewBots = new Set([
  'chatgpt-codex-connector',
  'chatgpt-codex-connector[bot]',
  'coderabbitai',
  'coderabbitai[bot]',
  'github-advanced-security',
  'github-advanced-security[bot]',
]);
const trustedAssociations = new Set(['OWNER', 'MEMBER', 'COLLABORATOR']);
const exactHeadBody = (body) => new RegExp(`(^|[^0-9a-f])${head}([^0-9a-f]|$)`, 'i').test(body ?? '');
const timeOf = (item) => Math.max(
  0,
  ...[item.submitted_at, item.created_at, item.updated_at]
    .map((value) => Date.parse(value ?? 0))
    .filter(Number.isFinite),
);
const isDispositionReply = (reply) => {
  const body = (reply.body ?? '').replaceAll('`', '');
  const disposition = /^Disposition:\s*(accepted|rejected-with-evidence|superseded)\s*$/im.test(body);
  cons...
🧰 Additional context used
📓 Path-based instructions (1)
.github/workflows/**

⚙️ CodeRabbit configuration file

.github/workflows/**: Проверяй least privilege, pinning сторонних Actions по commit SHA,
безопасность pull_request против pull_request_target, отсутствие выполнения
недоверенного PR-кода с write-token и корректность merge/status gates.

Files:

  • .github/workflows/ai-review-contract.yml
🪛 zizmor (1.26.1)
.github/workflows/ai-review-contract.yml

[error] 15-15: overly broad permissions (excessive-permissions): issues: write is overly broad at the workflow level

(excessive-permissions)

🔇 Additional comments (2)
.github/workflows/ai-review-contract.yml (2)

10-14: LGTM!

Also applies to: 16-16, 26-226, 248-296


10-16: 🔒 Security & Privacy

issues: write здесь нужен для POST/PATCH /issues/{number}/comments на PR. pull-requests: write этот scope не заменяет, поэтому замечание о сужении прав неверно.

			> Likely an incorrect or invalid review comment.

Comment thread .github/workflows/ai-review-contract.yml Outdated
Comment thread .github/workflows/ai-review-contract.yml Outdated
Comment thread .github/workflows/ai-review-contract.yml Outdated
Comment thread .github/workflows/ai-review-contract.yml Outdated
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 11, 2026
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
✅ Action performed

Comments resolved and changes approved.

1 similar comment
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
✅ Action performed

Comments resolved and changes approved.

Copy link
Copy Markdown
Owner Author

Disposition: accepted
Head: 20cfb37

All final CodeRabbit findings are fixed:

  • same-PR, same-head AI review runs are serialized with cancel-in-progress: true;
  • the fork branch now exits nonzero, so a PR without a trusted report cannot become falsely green;
  • the evidence artifact now includes every REST/GraphQL input plus raw and normalized checks, statuses, changed files and rendered report;
  • diagnostic eligibility is derived directly from immutable event repository metadata, so failures before REST outputs still produce diagnostics for same-repository PRs;
  • exact-head and head-repository drift checks remain in both collection and publishing paths.

No product runtime, D6, report marker or ledger acceptance rule changed.

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@qodo-code-review review

Exact head: 20cfb37171be64e61973c9fd87387215c7e220c3.

Please review the final same-head concurrency, fork fail-closed behavior, complete reproducible evidence artifact, event-derived diagnostic guard, and integrated trusted report publishing.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh api repos/safal207/robys-coffee-house-demo/contents/.github/workflows/ai-review-contract.yml?ref=20cfb37171be64e61973c9fd87387215c7e220c3 --jq '.content' | base64 -d > /tmp/wf_head4.yml
echo "---- diff vs previous reviewed head (2aacfd166b12440fcfd9a53b6b7e4c0b836aab16) ----"
gh api repos/safal207/robys-coffee-house-demo/contents/.github/workflows/ai-review-contract.yml?ref=2aacfd166b12440fcfd9a53b6b7e4c0b836aab16 --jq '.content' | base64 -d > /tmp/wf_final.yml
diff -u /tmp/wf_final.yml /tmp/wf_head4.yml || true
echo "---- full file ----"
cat -n /tmp/wf_head4.yml

Length of output: 20126


@safal207, reviewed 20cfb37171be64e61973c9fd87387215c7e220c3.

Confirmed fixed / correct:

  • Same-head concurrency: job-level concurrency: group: ai-review-contract-${{ github.repository }}-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }}, cancel-in-progress: true. Keying on head sha (not just PR number) is the right choice — it serializes reruns for the same head (closing the duplicate-comment race), while different heads getting separate groups is safe because publish_report independently re-fetches and compares CURRENT_HEAD vs EXPECTED_HEAD immediately before publishing, so a stale-head run still can't overwrite a newer report even without cross-head cancellation.
  • Fork fail-closed: "Record fork publication boundary" now writes to stderr and exit 1 instead of just logging — a fork PR with merge-ready evidence but no trusted report now fails the job, so it can't go green without a published report.
  • Complete evidence artifact: the artifact path: list now includes every collected input (PR_JSON_FILE, HEAD_COMMIT_FILE, COMMENTS_FILE, REVIEWS_FILE, REVIEW_COMMENTS_FILE, THREADS_FILE, STATUSES_FILE, FILES_FILE) alongside the raw/normalized checks and rendered comment — this is a real superset, not a rename.
  • Event-derived diagnostic guard: condition changed from steps.rest_evidence.outputs.same_repository == 'true' to github.event.pull_request.head.repo.full_name == github.repository. This is correct and strictly better: the event payload is immutable and available even if rest_evidence fails before setting its output, so same-repo failures earlier in the pipeline (e.g. reporter checkout, configure) now still get a diagnostic, without weakening the same-repo boundary since the event field isn't attacker-controlled.
  • Trusted publish path: unchanged publish_report (gated on steps.rest_evidence.outputs.same_repository == 'true', head-drift recheck, size cap, existing-comment PATCH/POST) remains correct.

Still open (unresolved for the third consecutive round):
normalize_self at Line 128 still uses

run_fragment="/actions/runs/${GITHUB_RUN_ID}"

matched via jq contains($fragment) — a plain substring test with no boundary, so run id 123 still matches a check-run URL containing .../actions/runs/1234/.... Fix remains:

run_fragment="/actions/runs/${GITHUB_RUN_ID}/"

or test("/actions/runs/" + ($run_id|tostring) + "(/|$)") in jq.

No other findings.

(・ᴗ・) all clean except one lingering boundary bug — patient rabbit waits for round four


✏️ Learnings added
Learnt from: safal207
URL: https://github.com/safal207/robys-coffee-house-demo/pull/199

Timestamp: 2026-07-11T08:28:03.226Z
Learning: In safal207/robys-coffee-house-demo's .github/workflows/ai-review-contract.yml, as of head 20cfb37171be64e61973c9fd87387215c7e220c3: (1) the job now has a `concurrency` block keyed by `ai-review-contract-${{ github.repository }}-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }}` with `cancel-in-progress: true`, resolving the earlier "no concurrency guard" finding; (2) the "Record fork publication boundary" step now `exit 1`s (fail-closed) instead of only logging; (3) the diagnostic step's guard condition uses the immutable `github.event.pull_request.head.repo.full_name == github.repository` instead of the `steps.rest_evidence.outputs.same_repository` step output, so it still fires for same-repo failures that occur before `rest_evidence` runs; (4) the uploaded evidence artifact now includes every collected REST/GraphQL input file, not just checks/comment. The `run_fragment="/actions/runs/${GITHUB_RUN_ID}"` substring-boundary bug in the `normalize_self` step (missing trailing delimiter in jq `contains()`) remains unresolved across three consecutive review rounds.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@safal207
safal207 dismissed stale reviews from coderabbitai[bot] and coderabbitai[bot] July 11, 2026 08:28

All four requested changes are fixed on 20cfb37: same-head concurrency, fork fail-closed, complete reproducible evidence artifact, and event-derived diagnostic eligibility. The exact-head review threads are resolved; this older request-changes review is stale.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh api repos/safal207/robys-coffee-house-demo/contents/.github/workflows/ai-review-contract.yml?ref=fb0aaa4628f88693384957b8d53bbee33ce89a8c --jq '.content' | base64 -d > /tmp/wf_final2.yml
echo "---- diff vs 316b9bbe666c4d90a7b9239b3820cd8bbd02768e ----"
diff -u /tmp/wf_new3.yml /tmp/wf_final2.yml || true
echo "---- full file ----"
cat -n /tmp/wf_final2.yml
echo "---- scripts referenced ----"
grep -n "scripts/\|uses:\|permissions" /tmp/wf_final2.yml

Length of output: 2569


🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh api repos/safal207/robys-coffee-house-demo/contents/.github/workflows/ai-review-contract.yml?ref=316b9bbe666c4d90a7b9239b3820cd8bbd02768e --jq '.content' | base64 -d > /tmp/wf_316.yml
echo "---- diff 316 -> fb0aaa46 ----"
diff -u /tmp/wf_316.yml /tmp/wf_final2.yml || true

echo "---- verify-ai-review-contract.cjs: actions API usage ----"
gh api repos/safal207/robys-coffee-house-demo/contents/scripts/verify-ai-review-contract.cjs?ref=fb0aaa4628f88693384957b8d53bbee33ce89a8c --jq '.content' | base64 -d > /tmp/verify_final.cjs
grep -n "actions\.\|listWorkflowRuns\|getWorkflowRun\|runs/\|octokit\|github\.rest\.actions" /tmp/verify_final.cjs

echo "---- diff verify script vs previous pinned commit content (already pinned, should be same) ----"
wc -l /tmp/verify_final.cjs

Length of output: 1226


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,100p' /tmp/verify_final.cjs

Length of output: 2990


@safal207, reviewed final head fb0aaa4628f88693384957b8d53bbee33ce89a8c.

File is byte-identical to the previous revert (316b9bbe666c4d90a7b9239b3820cd8bbd02768e) — no diff. So this confirms the read-only design is final, not a partial/accidental state.

actions: read permission — verified as exactly consumed, not over-broad:

  • scripts/verify-ai-review-contract.cjs Line 77 calls github.rest.actions.getWorkflowRun({ owner, repo, run_id: context.runId }) to derive headUpdateAnchor — the run's created_at timestamp used as the immutable freshness anchor that gates which comments/reviews/statuses count as "since this run started."
  • This matches the permission comment exactly: # Required for the immutable workflow-run freshness anchor.
  • No other Actions API calls in the script (checked full grep for actions./listWorkflowRuns/etc. — only this one call).

Executable contract, confirmed safe:

  • contents: read → checkout pinned to trusted commit 577dfd5eebe75038ee067830e6b0c70815fcc837 (PR #175's fix), persist-credentials: false, no PR-head ref anywhere.
  • issues: read, pull-requests: read, statuses: read → match the paginated listComments/listReviews/listCommitStatusesForRef (or equivalent) calls used to build native CodeRabbit/Codex review evidence.
  • No write scopes anywhere — no comment publication, no report, no artifact upload. Purely a poll-and-gate job.

No findings. The actions: read grant is minimal, justified, and its sole use site is verified. This closes out the review history for this workflow's current (read-only) scope.

[approve]

(=^・ω・^=) clean and minimal — nothing left to chase this round

Copy link
Copy Markdown
Owner Author

Disposition: accepted

CodeRabbit finding “Do not normalize any check-run by an Actions-looking URL” is fixed on exact head b2bc3852abc803defa1f9d6517502a0190a67401.

run_id_of() now requires the original check record to have app.slug == "github-actions" before parsing /actions/runs/<id>. Third-party checks remain external with their original status, conclusion and app identity.

Copy link
Copy Markdown
Owner Author

@coderabbitai review

Please re-review exact head b2bc3852abc803defa1f9d6517502a0190a67401; the URL-only workflow identity spoof finding is fixed by requiring the original app.slug == github-actions.

Copy link
Copy Markdown
Owner Author

@qodo-code-review review

Please re-review exact head b2bc3852abc803defa1f9d6517502a0190a67401, including the app-identity-first run mapping and preserved external check records.

Copy link
Copy Markdown
Owner Author

@coderabbitai resume

Copy link
Copy Markdown
Owner Author

@coderabbitai review

Final exact head: b2bc3852abc803defa1f9d6517502a0190a67401. No further implementation commits are planned unless this review finds a valid issue.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh api repos/safal207/robys-coffee-house-demo/contents/.github/workflows/ai-review-contract.yml?ref=b2bc3852abc803defa1f9d6517502a0190a67401 --jq '.content' | base64 -d > /tmp/wf_b2.yml
echo "---- diff vs fb0aaa46 (last reviewed) ----"
gh api repos/safal207/robys-coffee-house-demo/contents/.github/workflows/ai-review-contract.yml?ref=fb0aaa4628f88693384957b8d53bbee33ce89a8c --jq '.content' | base64 -d > /tmp/wf_fb0.yml
diff -u /tmp/wf_fb0.yml /tmp/wf_b2.yml || true
echo "---- full workflow ----"
cat -n /tmp/wf_b2.yml
echo "---- scripts dir at b2bc ----"
gh api repos/safal207/robys-coffee-house-demo/contents/scripts?ref=b2bc3852abc803defa1f9d6517502a0190a67401 --jq '.[].name'

Length of output: 4587


🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh api repos/safal207/robys-coffee-house-demo/contents/scripts/fetch-review-threads.py?ref=b2bc3852abc803defa1f9d6517502a0190a67401 --jq '.content' | base64 -d > /tmp/fetch_b2.py
echo "---- run_id_of function ----"
grep -n "def run_id_of" -A 20 /tmp/fetch_b2.py
echo "---- check if this script is referenced anywhere in the workflow ----"
grep -n "fetch-review-threads" /tmp/wf_b2.yml || echo "NOT REFERENCED IN WORKFLOW"
echo "---- check where scripts/fetch-review-threads.py is invoked repo-wide ----"
grep -rn "fetch-review-threads" --include="*.yml" --include="*.yaml" .github 2>/dev/null || true
grep -rn "fetch-review-threads" scripts 2>/dev/null | grep -v "fetch-review-threads.py:" | head -20

Length of output: 3401


🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh pr view 199 --repo safal207/robys-coffee-house-demo --json files -q '.files[].path'

Length of output: 384


🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh api repos/safal207/robys-coffee-house-demo/contents/.github/workflows/ai-review-cooperation.yml?ref=b2bc3852abc803defa1f9d6517502a0190a67401 --jq '.content' | base64 -d > /tmp/coop.yml
cat -n /tmp/coop.yml
echo "==== contract test file ===="
gh api repos/safal207/robys-coffee-house-demo/contents/.github/workflows/ai-review-cooperation-contract.yml?ref=b2bc3852abc803defa1f9d6517502a0190a67401 --jq '.content' | base64 -d > /tmp/coop_contract.yml
cat -n /tmp/coop_contract.yml

Length of output: 40945


🏁 Script executed:

#!/bin/bash
set -euo pipefail
cat -n /tmp/fetch_b2.py

Length of output: 10609


🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh api repos/safal207/robys-coffee-house-demo/contents/scripts/test-ai-review-cooperation.py?ref=b2bc3852abc803defa1f9d6517502a0190a67401 --jq '.content' | base64 -d > /tmp/test_coop.py
grep -n "run_id_of\|app.*slug\|github-actions\|spoof\|RUN_URL" /tmp/test_coop.py

Length of output: 622


🏁 Script executed:

#!/bin/bash
set -euo pipefail
grep -n "fetch-review-threads\|fetch_review_threads\|normalize_workflow_checks\|import.*fetch" /tmp/test_coop.py
wc -l /tmp/test_coop.py

Length of output: 474


@safal207, confirmed on b2bc3852abc803defa1f9d6517502a0190a67401.

run_id_of() (scripts/fetch-review-threads.py, Line 125‑131) now checks app.get('slug') != 'github-actions' and returns None immediately, before the RUN_URL regex is ever applied to details_url/html_url. This closes the spoof: a third-party check whose app posts a details_url shaped like .../actions/runs/<n> can no longer be pulled into run_ids (Line 157) or excluded from external_checks (Line 188) — it stays external with its original identity either way, since the gate is now identity-first, not URL-shape-first.

The derived-workflow path this feeds is otherwise unchanged and still correct: exact-head cross-check on head_sha (Line 180), completeness check on run-id→metadata mapping (Line 174), and the workflow_run-trigger trusted-match requiring name == 'AI review contract', conclusion == 'success', exact head_sha, and app.slug == 'github-actions' (Line 199‑211) all still hold.

One advisory gap, non-blocking: ai-review-cooperation-contract.yml only asserts the fix via a static grep -Fq "'app': {'slug': 'github-actions'}" (Line 272) — no test in scripts/test-ai-review-cooperation.py actually invokes run_id_of() with a fixture check that has a non-github-actions app.slug plus an Actions-shaped URL, to pin the regression. Since the grep only confirms the string exists in the file, a future refactor could silently invert the condition and still pass CI. Consider adding one unit case for that if you want this specific fix regression-proof.

No other findings on this head.

[approve]

Copy link
Copy Markdown
Owner Author

@coderabbitai approve

All known actionable findings are fixed and all inline threads are resolved on exact head b2bc3852abc803defa1f9d6517502a0190a67401. Please submit native exact-head approval only if the current head is clean.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh pr view 199 --repo safal207/robys-coffee-house-demo --json headRefOid,state -q '.'

Length of output: 327


@safal207, confirmed: current PR head is b2bc3852abc803defa1f9d6517502a0190a67401, matching the exact-head you specified, and it's the same head already reviewed with all findings resolved. Submitting native exact-head approval now.

[approve]

(=^・ω・^=)

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh api repos/safal207/robys-coffee-house-demo/contents/scripts/fetch-review-threads.py?ref=b2bc3852abc803defa1f9d6517502a0190a67401 --jq '.content' | base64 -d > /tmp/fetch_new.py
echo "---- run_id_of and normalize_workflow_checks ----"
grep -n "def run_id_of\|def normalize_workflow_checks\|app.slug\|app\[.slug.\]\|github-actions" -A 15 /tmp/fetch_new.py | head -150
echo "---- full diff vs prior known version if available ----"
gh api repos/safal207/robys-coffee-house-demo/contents/scripts/fetch-review-threads.py?ref=316b9bbe666c4d90a7b9239b3820cd8bbd02768e --jq '.content' 2>/dev/null | base64 -d > /tmp/fetch_prev.py || echo "no prev"
diff -u /tmp/fetch_prev.py /tmp/fetch_new.py || true

Length of output: 5999


🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh api repos/safal207/robys-coffee-house-demo/contents/.github/workflows/ai-review-contract.yml?ref=b2bc3852abc803defa1f9d6517502a0190a67401 --jq '.content' | base64 -d > /tmp/wf_b2bc.yml
grep -n "TRIGGER_HEAD_SHA\|TRIGGER_EVENT\|CHECKS_FILE" /tmp/wf_b2bc.yml
echo "---- full file for context ----"
wc -l /tmp/wf_b2bc.yml
cat -n /tmp/wf_b2bc.yml

Length of output: 683


🏁 Script executed:

#!/bin/bash
set -uo pipefail
gh api repos/safal207/robys-coffee-house-demo/contents/.github/workflows/ai-review-contract.yml?ref=b2bc3852abc803defa1f9d6517502a0190a67401 --jq '.content' | base64 -d > /tmp/wf_b2bc.yml
echo "exit code of api call: $?"
wc -l /tmp/wf_b2bc.yml
echo "---- grep ----"
grep -n "TRIGGER_HEAD_SHA\|TRIGGER_EVENT\|CHECKS_FILE" /tmp/wf_b2bc.yml || echo "no matches"
echo "---- full file ----"
cat -n /tmp/wf_b2bc.yml

Length of output: 2166


🏁 Script executed:

#!/bin/bash
set -uo pipefail
echo "---- search for any reference to fetch-review-threads.py across repo at this head ----"
gh api repos/safal207/robys-coffee-house-demo/git/trees/b2bc3852abc803defa1f9d6517502a0190a67401?recursive=1 --jq '.tree[].path' > /tmp/tree_b2bc.txt
grep -n "workflow\|scripts/" /tmp/tree_b2bc.txt
echo "---- grep for fetch-review-threads references in workflows/scripts ----"
for f in $(grep -E '\.ya?ml$|\.cjs$|\.py$' /tmp/tree_b2bc.txt); do
  content=$(gh api "repos/safal207/robys-coffee-house-demo/contents/${f}?ref=b2bc3852abc803defa1f9d6517502a0190a67401" --jq '.content' 2>/dev/null | base64 -d 2>/dev/null)
  if echo "$content" | grep -q "fetch-review-threads"; then
    echo "FOUND reference in: $f"
  fi
done

Length of output: 6075


🏁 Script executed:

#!/bin/bash
set -uo pipefail
for f in .github/workflows/ai-review-cooperation-contract.yml .github/workflows/ai-review-cooperation.yml; do
  echo "==== $f ===="
  gh api "repos/safal207/robys-coffee-house-demo/contents/${f}?ref=b2bc3852abc803defa1f9d6517502a0190a67401" --jq '.content' | base64 -d > /tmp/$(basename $f)
  grep -n "CHECKS_FILE\|TRIGGER_EVENT\|TRIGGER_HEAD_SHA\|fetch-review-threads" /tmp/$(basename $f)
done

Length of output: 4965


@safal207, reviewed exact head b2bc3852abc803defa1f9d6517502a0190a67401. Fix confirmed correct.

run_id_of() fix verified:

def run_id_of(check: dict[str, Any]) -> int | None:
    app = check.get('app') or {}
    if not isinstance(app, dict) or app.get('slug') != 'github-actions':
        return None
    url = str(check.get('details_url') or check.get('html_url') or '')
    match = RUN_URL.search(url)
    return int(match.group(1)) if match else None

app.slug == "github-actions" is now checked before any URL parsing. A third-party check whose details_url/html_url happens to contain an Actions-run-shaped path can no longer be classified as a workflow run — it returns None and falls into external_checks, retaining its original status/conclusion/app identity unmodified. This closes the identity-spoof vector cleanly.

Scope clarification (not a defect, just confirming provenance): ai-review-contract.yml — this PR's stated subject — remains the finalized read-only revert (37 lines, verify job only, no fetch-review-threads.py reference). The fixed function lives in the shared scripts/fetch-review-threads.py, actually invoked by .github/workflows/ai-review-cooperation.yml (Line 247), triggered via workflow_run/pull_request_review with TRIGGER_HEAD_SHA sourced from github.event.workflow_run.head_sha / review.commit_id. That workflow validates TRIGGER_HEAD_SHA == HEAD_SHA before trusting workflow_run evidence (Line 220-221), matching the new guard added inside normalize_workflow_checks().

Bonus hardening, also verified sound: normalize_workflow_checks() now additionally re-validates TRIGGER_HEAD_SHA == expected_head before accepting workflow_run-sourced trusted matches, and asserts raw_payload is a dict before indexing — both defensive, no regressions.

Contract-test coverage confirmed: .github/workflows/ai-review-cooperation-contract.yml already greps for the exact new code ('app': {'slug': 'github-actions'}, the head-mismatch check, RAW_CHECKS_FILE preservation, the "lacks a successful exact-head AI review contract" error string) — so this fix is pinned by existing contract assertions, not just a one-off patch.

No valid issues found this round.

[approve]

(=^・ω・^=) ship it

@safal207
safal207 dismissed coderabbitai[bot]’s stale review July 11, 2026 14:51

Dismissed as stale after remediation. The only actionable finding applied to earlier head 316b9bb... and is fixed on exact head b2bc385 by requiring the original check app.slug to be github-actions before run-ID mapping. The inline thread is resolved. Fresh exact-head CodeRabbit approval is still required.

coderabbitai[bot]
coderabbitai Bot previously requested changes Jul 11, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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/ai-review-cooperation-contract.yml:
- Around line 264-268: Уточните проверку пагинации в контракте вокруг проверок
check-runs и statuses: вместо отдельной общей проверки '--paginate' убедитесь,
что каждая соответствующая команда endpoint содержит собственный флаг
'--paginate'. Сохраните проверки RAW_CHECKS_FILE и STATUSES_FILE и обеспечьте,
чтобы удаление пагинации у любого из этих endpoint делало контракт неуспешным.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0d252147-0c5d-4450-a676-3f182896eba8

📥 Commits

Reviewing files that changed from the base of the PR and between 9beda32 and fb0aaa4.

📒 Files selected for processing (3)
  • .github/workflows/ai-review-cooperation-contract.yml
  • .github/workflows/ai-review-cooperation.yml
  • scripts/fetch-review-threads.py
📜 Review details
⏰ Context from checks skipped due to timeout. (5)
  • GitHub Check: CodeRabbit
  • GitHub Check: build-smoke / build-smoke
  • GitHub Check: VISUAL-001 screenshot diff
  • GitHub Check: DAST-001 passive web scan
  • GitHub Check: Verify exact-head independent review
🧰 Additional context used
📓 Path-based instructions (2)
.github/workflows/**

⚙️ CodeRabbit configuration file

.github/workflows/**: Проверяй least privilege, pinning сторонних Actions по commit SHA,
безопасность pull_request против pull_request_target, отсутствие выполнения
недоверенного PR-кода с write-token и корректность merge/status gates.

Files:

  • .github/workflows/ai-review-cooperation.yml
  • .github/workflows/ai-review-cooperation-contract.yml
scripts/**

⚙️ CodeRabbit configuration file

scripts/**: Ищи недетерминированность, небезопасную работу с путями и shell quoting,
скрытое ослабление проверок, сетевые зависимости, утечки секретов и случаи,
когда проверка может ложно завершиться успешно.

Files:

  • scripts/fetch-review-threads.py
🪛 ast-grep (0.44.1)
scripts/fetch-review-threads.py

[error] 56-62: Use of unsanitized data to create processes
Context: subprocess.run(
command,
check=True,
capture_output=True,
text=True,
encoding='utf-8',
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(os-system-unsanitized-data)


[error] 56-62: Command coming from incoming request
Context: subprocess.run(
command,
check=True,
capture_output=True,
text=True,
encoding='utf-8',
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)


[info] 201-201: use jsonify instead of json.dumps for JSON output
Context: json.dumps(derived, ensure_ascii=False, separators=(',', ':'))
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 217-217: use jsonify instead of json.dumps for JSON output
Context: json.dumps(fetch_all(owner, name, number), ensure_ascii=False)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🪛 Ruff (0.15.20)
scripts/fetch-review-threads.py

[warning] 66-66: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 79-79: Prefer TypeError exception for invalid type

(TRY004)


[warning] 79-79: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 86-86: Prefer TypeError exception for invalid type

(TRY004)


[warning] 86-86: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 142-142: Prefer TypeError exception for invalid type

(TRY004)


[warning] 142-142: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 170-173: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 181-181: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 197-199: Avoid specifying long messages outside the exception class

(TRY003)

🪛 zizmor (1.26.1)
.github/workflows/ai-review-cooperation.yml

[warning] 36-36: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment

(undocumented-permissions)

🔇 Additional comments (4)
scripts/fetch-review-threads.py (2)

46-46: По-прежнему проверяйте исходный app.slug перед нормализацией.

Любой сторонний check с URL вида /actions/runs/<id> удаляется из external_checks и получает синтетический github-actions. Ранее это уже было отмечено; текущая версия проблему не устраняет. Разрешайте извлечение run ID только для исходного check["app"]["slug"] == "github-actions".

As per path instructions, для scripts/** нужно искать «скрытое ослабление проверок» и случаи ложного успешного результата.

Also applies to: 125-128, 154-185

Source: Path instructions


2-8: LGTM!

Also applies to: 56-86, 230-237

.github/workflows/ai-review-cooperation.yml (1)

36-36: LGTM!

Also applies to: 207-207

.github/workflows/ai-review-cooperation-contract.yml (1)

75-75: LGTM!

Comment thread .github/workflows/ai-review-cooperation-contract.yml Outdated
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
✅ Action performed

Reviews resumed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/fetch-review-threads.py (1)

174-178: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Проверка полноты всегда истинна и ничего не ловит.

workflows заполняется ровно по одному элементу на каждый run_id без условий/continue, а rest() при сбое кидает исключение и прерывает цикл раньше строки 174. Поэтому при достижении этой проверки len(workflows) == len(run_ids) выполняется всегда — это false-pass guard, дающий ложное чувство контроля полноты в fail-closed evidence-пайплайне. Реальную защиту от искажённых метаданных уже обеспечивает exact-head проверка (пустой head_sha → mismatch → ошибка).

Уберите мёртвую проверку либо замените её валидацией формы ответа (например, isinstance(item, dict) и item.get('id') == run_id) до append.

As per path instructions: "случаи, когда проверка может ложно завершиться успешно".

🤖 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/fetch-review-threads.py` around lines 174 - 178, Проверка полноты по
длине в блоке сбора workflows является мёртвой и всегда проходит; удалите её
либо замените проверкой каждого ответа до append. В цикле, заполняющем
workflows, валидируйте, что item — dict и его item.get('id') совпадает с текущим
run_id, затем добавляйте элемент; при нарушении немедленно выбрасывайте ошибку.

Source: Path instructions

🤖 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.

Outside diff comments:
In `@scripts/fetch-review-threads.py`:
- Around line 174-178: Проверка полноты по длине в блоке сбора workflows
является мёртвой и всегда проходит; удалите её либо замените проверкой каждого
ответа до append. В цикле, заполняющем workflows, валидируйте, что item — dict и
его item.get('id') совпадает с текущим run_id, затем добавляйте элемент; при
нарушении немедленно выбрасывайте ошибку.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7aefdbc6-1f1a-4419-9593-e67cdfb15104

📥 Commits

Reviewing files that changed from the base of the PR and between fb0aaa4 and b2bc385.

📒 Files selected for processing (1)
  • scripts/fetch-review-threads.py
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: CodeRabbit
  • GitHub Check: Verify exact-head independent review
🧰 Additional context used
📓 Path-based instructions (1)
scripts/**

⚙️ CodeRabbit configuration file

scripts/**: Ищи недетерминированность, небезопасную работу с путями и shell quoting,
скрытое ослабление проверок, сетевые зависимости, утечки секретов и случаи,
когда проверка может ложно завершиться успешно.

Files:

  • scripts/fetch-review-threads.py
🔇 Additional comments (4)
scripts/fetch-review-threads.py (4)

56-86: LGTM!


125-131: LGTM!


180-221: LGTM!


234-240: LGTM!

Copy link
Copy Markdown
Owner Author

Disposition: accepted

Two fresh review findings are fixed on exact head 1f2cd8bba3253ccc1e4f2df40c59ea1421e55193:

  1. Pagination contract: the cooperation contract now parses the Collect exact-head REST evidence step and independently requires gh api --paginate for both check-runs?per_page=100 and statuses?per_page=100.
  2. Dead completeness guard: the always-true length comparison was removed. Every Actions API response is now validated before append with item.id == requested run_id; mismatches fail closed.

AI review cooperation contract run 29159696430 is green, including compile, YAML parse, endpoint-specific pagination, workflow identity, authorization and trust-boundary checks.

@safal207
safal207 dismissed coderabbitai[bot]’s stale review July 11, 2026 16:24

Dismissed as stale after remediation. Its pagination finding applied to earlier head fb0aaa4... and is fixed on exact head 1f2cd8b. The contract now binds --paginate independently to both check-runs and statuses endpoints, and the exact contract run is green. Fresh exact-head review remains required.

Copy link
Copy Markdown
Owner Author

@coderabbitai review

Please review exact head 1f2cd8bba3253ccc1e4f2df40c59ea1421e55193. Focus on endpoint-specific pagination assertions, workflow response identity validation, fail-closed metadata handling, and whether any synthetic evidence path can false-pass.

Copy link
Copy Markdown
Owner Author

@qodo-code-review review

Please review exact head 1f2cd8bba3253ccc1e4f2df40c59ea1421e55193, especially the per-endpoint --paginate contract and replacement of the dead length guard with exact workflow-run identity validation.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh api repos/safal207/robys-coffee-house-demo/contents/.github/workflows/ai-review-cooperation-contract.yml?ref=1f2cd8bba3253ccc1e4f2df40c59ea1421e55193 --jq '.content' | base64 -d > /tmp/contract_new.yml
gh api repos/safal207/robys-coffee-house-demo/contents/.github/workflows/ai-review-cooperation-contract.yml?ref=b2bc3852abc803defa1f9d6517502a0190a67401 --jq '.content' | base64 -d > /tmp/contract_prev.yml
echo "---- diff contract ----"
diff -u /tmp/contract_prev.yml /tmp/contract_new.yml || true

gh api repos/safal207/robys-coffee-house-demo/contents/.github/workflows/ai-review-cooperation.yml?ref=1f2cd8bba3253ccc1e4f2df40c59ea1421e55193 --jq '.content' | base64 -d > /tmp/coop_new.yml
gh api repos/safal207/robys-coffee-house-demo/contents/.github/workflows/ai-review-cooperation.yml?ref=b2bc3852abc803defa1f9d6517502a0190a67401 --jq '.content' | base64 -d > /tmp/coop_prev.yml
echo "---- diff coop workflow ----"
diff -u /tmp/coop_prev.yml /tmp/coop_new.yml || true

gh api repos/safal207/robys-coffee-house-demo/contents/scripts/fetch-review-threads.py?ref=1f2cd8bba3253ccc1e4f2df40c59ea1421e55193 --jq '.content' | base64 -d > /tmp/fetch_new2.py
gh api repos/safal207/robys-coffee-house-demo/contents/scripts/fetch-review-threads.py?ref=b2bc3852abc803defa1f9d6517502a0190a67401 --jq '.content' | base64 -d > /tmp/fetch_prev2.py
echo "---- diff fetch-review-threads.py ----"
diff -u /tmp/fetch_prev2.py /tmp/fetch_new2.py || true

Length of output: 4978


🏁 Script executed:

#!/bin/bash
set -euo pipefail
grep -n "Collect exact-head REST evidence" -A 30 /tmp/coop_new.yml | head -50
echo "---- broader context of run_id_of / normalize_workflow_checks ----"
sed -n '100,220p' /tmp/fetch_new2.py

Length of output: 7057


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,100p' /tmp/fetch_new2.py

Length of output: 3267


🏁 Script executed:

#!/bin/bash
set -euo pipefail
grep -n "ruby <<\|^RUBY\|RUBY$" -B2 -A2 /tmp/contract_new.yml
echo "---- exact bytes around ruby heredoc ----"
awk '/ruby <<'"'"'RUBY'"'"'/{f=1} f{print NR": "$0; if($0 ~ /^[[:space:]]*RUBY[[:space:]]*$/ && NR>1 && f==1 && seen++){}} ' /tmp/contract_new.yml | head -20
python3 - <<'PYEOF'
with open('/tmp/contract_new.yml','rb') as f:
    lines = f.readlines()
start=None
for i,l in enumerate(lines):
    if b"ruby <<" in l:
        start=i
        break
for i in range(start, start+12):
    print(i+1, repr(lines[i]))
PYEOF

Length of output: 5042


🏁 Script executed:

#!/bin/bash
set -euo pipefail
grep -n "paginate\|per_page=100" /tmp/contract_new.yml

Length of output: 779


@safal207, reviewed exact head 1f2cd8bba3253ccc1e4f2df40c59ea1421e55193. Both fixes confirmed correct.

1. Endpoint-specific pagination assertion — real fix. The contract test no longer does a blanket grep -Fq 'check-runs?per_page=100' / --paginate anywhere in the file (which could pass even if --paginate were misapplied to an unrelated endpoint). It now parses ai-review-cooperation.yml via YAML.load_file, isolates the Collect exact-head REST evidence step's run: text, and requires gh api --paginate ... \<newline>...check-runs?per_page=100 and the same for statuses?per_page=100 as tightly-coupled regex matches. Verified against the actual step (Line 214-243 of ai-review-cooperation.yml): both endpoints use gh api --paginate with line-continuation exactly as the regex expects.

2. Dead completeness guard replaced with real identity check — correct. len(workflows) != len(run_ids) was vacuous: the loop appends exactly one entry per run_id iterated, so it could only fail to hold if rest() had already raised. It's now replaced with item.get('id') != run_id checked immediately per fetched item, raising Workflow metadata identity mismatch — this actually validates the REST response body matches the requested run, not just call count.

Fail-closed / no false-pass path found: rest() uses subprocess.run(check=True), so any gh api failure (404, auth, etc.) raises before reaching the identity check — no silent empty/partial result can slip through. Exact-head (head_sha == expected_head), workflow_run trigger-head binding, and the trusted AI review contract success match (name/status/conclusion/head/app.slug) are all unchanged and still correct.

Heredoc style note (non-issue): the new ruby <<'RUBY' ... RUBY block uses the same indented-heredoc pattern already used 6+ times elsewhere in this same contract file; YAML's | block scalar strips the common indentation uniformly, so the terminator resolves correctly at runtime — consistent with the rest of the file, not a regression.

No valid issues found this round.

[approve]

(=^・ω・^=)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/fetch-review-threads.py (1)

56-67: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Пробрасывайте stderr gh при сбое.

При check=True CalledProcessError не содержит stderr, поэтому реальная причина сбоя API (403/404/rate-limit) теряется в CI-логе. Учитывая, что это единственный примитив вызова gh, оберните запуск и включите stderr.

♻️ Предлагаемая правка
 def run_json(command: list[str]) -> dict[str, Any] | list[Any]:
-    result = subprocess.run(
-        command,
-        check=True,
-        capture_output=True,
-        text=True,
-        encoding='utf-8',
-    )
+    try:
+        result = subprocess.run(
+            command,
+            check=True,
+            capture_output=True,
+            text=True,
+            encoding='utf-8',
+        )
+    except subprocess.CalledProcessError as exc:
+        raise RuntimeError(
+            f'{" ".join(command)} failed: {(exc.stderr or "").strip()}'
+        ) from exc
     payload = json.loads(result.stdout)
🤖 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/fetch-review-threads.py` around lines 56 - 67, Update run_json to
catch subprocess.CalledProcessError from the subprocess.run invocation and
include its stderr in the raised error so gh API failures expose their actual
cause in CI logs. Preserve the existing JSON parsing and payload-error handling
for successful commands.
🤖 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.

Outside diff comments:
In `@scripts/fetch-review-threads.py`:
- Around line 56-67: Update run_json to catch subprocess.CalledProcessError from
the subprocess.run invocation and include its stderr in the raised error so gh
API failures expose their actual cause in CI logs. Preserve the existing JSON
parsing and payload-error handling for successful commands.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 466be2f0-68ac-4f0f-bf5a-5843b5a44dfb

📥 Commits

Reviewing files that changed from the base of the PR and between b2bc385 and 1f2cd8b.

📒 Files selected for processing (2)
  • .github/workflows/ai-review-cooperation-contract.yml
  • scripts/fetch-review-threads.py
📜 Review details
⏰ Context from checks skipped due to timeout. (6)
  • GitHub Check: Update current-head attestation status
  • GitHub Check: CodeRabbit
  • GitHub Check: build-smoke / build-smoke
  • GitHub Check: VISUAL-001 screenshot diff
  • GitHub Check: DAST-001 passive web scan
  • GitHub Check: Verify exact-head independent review
⚠️ CI failures not shown inline (2)

GitHub Actions: Bot review disposition contract / 0_REVIEW-LEDGER exact-head findings.txt: fix: derive workflow evidence in trusted cooperation reporter

Conclusion: failure

View job details

##[group]Run actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b
 with:
   script: const owner = context.repo.owner;
const repo = context.repo.repo;
const payloadPr = context.payload.pull_request;
const issuePr = context.payload.issue?.pull_request;
const prNumber = payloadPr?.number ?? (issuePr ? context.payload.issue.number : null);
if (!prNumber) {
  core.notice('Event is not associated with a pull request.');
  return;
}
const pr = (await github.rest.pulls.get({ owner, repo, pull_number: prNumber })).data;
const head = pr.head.sha.toLowerCase();
const [reviewComments, reviews, issueComments, statuses] = await Promise.all([
  github.paginate(
    github.rest.pulls.listReviewComments,
    { owner, repo, pull_number: prNumber, per_page: 100 },
  ),
  github.paginate(
    github.rest.pulls.listReviews,
    { owner, repo, pull_number: prNumber, per_page: 100 },
  ),
  github.paginate(
    github.rest.issues.listComments,
    { owner, repo, issue_number: prNumber, per_page: 100 },
  ),
  github.paginate(
    github.rest.repos.listCommitStatusesForRef,
    { owner, repo, ref: head, per_page: 100 },
  ),
]);
const reviewHeads = new Map(
  reviews.map((review) => [review.id, review.commit_id?.toLowerCase()]),
);
const reviewBots = new Set([
  'chatgpt-codex-connector',
  'chatgpt-codex-connector[bot]',
  'coderabbitai',
  'coderabbitai[bot]',
  'github-advanced-security',
  'github-advanced-security[bot]',
]);
const trustedAssociations = new Set(['OWNER', 'MEMBER', 'COLLABORATOR']);
const exactHeadBody = (body) => new RegExp(`(^|[^0-9a-f])${head}([^0-9a-f]|$)`, 'i').test(body ?? '');
const timeOf = (item) => Math.max(
  0,
  ...[item.submitted_at, item.created_at, item.updated_at]
    .map((value) => Date.parse(value ?? 0))
    .filter(Number.isFinite),
);
const isDispositionReply = (reply) => {
  const body = (reply.body ?? '').replaceAll('`', '');
  const disposition = /^Disposition:\s*(accepted|rejected-with-evidence|superseded)\s*$/im.test(body);
  cons...

GitHub Actions: Bot review disposition contract / REVIEW-LEDGER exact-head findings: fix: derive workflow evidence in trusted cooperation reporter

Conclusion: failure

View job details

##[group]Run actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b
 with:
   script: const owner = context.repo.owner;
const repo = context.repo.repo;
const payloadPr = context.payload.pull_request;
const issuePr = context.payload.issue?.pull_request;
const prNumber = payloadPr?.number ?? (issuePr ? context.payload.issue.number : null);
if (!prNumber) {
  core.notice('Event is not associated with a pull request.');
  return;
}
const pr = (await github.rest.pulls.get({ owner, repo, pull_number: prNumber })).data;
const head = pr.head.sha.toLowerCase();
const [reviewComments, reviews, issueComments, statuses] = await Promise.all([
  github.paginate(
    github.rest.pulls.listReviewComments,
    { owner, repo, pull_number: prNumber, per_page: 100 },
  ),
  github.paginate(
    github.rest.pulls.listReviews,
    { owner, repo, pull_number: prNumber, per_page: 100 },
  ),
  github.paginate(
    github.rest.issues.listComments,
    { owner, repo, issue_number: prNumber, per_page: 100 },
  ),
  github.paginate(
    github.rest.repos.listCommitStatusesForRef,
    { owner, repo, ref: head, per_page: 100 },
  ),
]);
const reviewHeads = new Map(
  reviews.map((review) => [review.id, review.commit_id?.toLowerCase()]),
);
const reviewBots = new Set([
  'chatgpt-codex-connector',
  'chatgpt-codex-connector[bot]',
  'coderabbitai',
  'coderabbitai[bot]',
  'github-advanced-security',
  'github-advanced-security[bot]',
]);
const trustedAssociations = new Set(['OWNER', 'MEMBER', 'COLLABORATOR']);
const exactHeadBody = (body) => new RegExp(`(^|[^0-9a-f])${head}([^0-9a-f]|$)`, 'i').test(body ?? '');
const timeOf = (item) => Math.max(
  0,
  ...[item.submitted_at, item.created_at, item.updated_at]
    .map((value) => Date.parse(value ?? 0))
    .filter(Number.isFinite),
);
const isDispositionReply = (reply) => {
  const body = (reply.body ?? '').replaceAll('`', '');
  const disposition = /^Disposition:\s*(accepted|rejected-with-evidence|superseded)\s*$/im.test(body);
  cons...
🧰 Additional context used
📓 Path-based instructions (2)
.github/workflows/**

⚙️ CodeRabbit configuration file

.github/workflows/**: Проверяй least privilege, pinning сторонних Actions по commit SHA,
безопасность pull_request против pull_request_target, отсутствие выполнения
недоверенного PR-кода с write-token и корректность merge/status gates.

Files:

  • .github/workflows/ai-review-cooperation-contract.yml
scripts/**

⚙️ CodeRabbit configuration file

scripts/**: Ищи недетерминированность, небезопасную работу с путями и shell quoting,
скрытое ослабление проверок, сетевые зависимости, утечки секретов и случаи,
когда проверка может ложно завершиться успешно.

Files:

  • scripts/fetch-review-threads.py
🪛 Ruff (0.15.20)
scripts/fetch-review-threads.py

[warning] 162-165: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (6)
scripts/fetch-review-threads.py (4)

70-87: LGTM!


125-131: LGTM!


134-220: LGTM!


232-239: LGTM!

.github/workflows/ai-review-cooperation-contract.yml (2)

75-75: LGTM!


264-283: Проверка --paginate теперь привязана к конкретным endpoint (check-runs, statuses), удаление пагинации у любого из них делает контракт красным — прошлое замечание закрыто. Grep-строки соответствуют фактическим фрагментам scripts/fetch-review-threads.py.

LGTM!

Copy link
Copy Markdown
Owner Author

@qodo-code-review review

Please complete exact-head review for 1f2cd8bba3253ccc1e4f2df40c59ea1421e55193. All current inline threads are resolved; CodeRabbit has submitted native exact-head APPROVED and all exact-head Actions are green.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant