PR Review and Document
Run a Codex PR review and publish the results to the canonical pr-review-toolkit PR comment.
Contract
This skill owns all review state writes. Use .pr-review-cache/pr-#.json as the only PR review state file. Do not create extra Codex cache files, extra PR comments, commits, pushes, or direct gh api comment updates.
codex-review-pass owns review analysis only. It launches the six read-only subagents and returns a normalized review bundle. This skill converts that bundle into canonical markdown, updates metadata, and writes through cache-write-comment.sh. If the codex-review-pass skill body is not already loaded, read ${PR_REVIEW_TOOLKIT_ROOT}/codex/skills/codex-review-pass/SKILL.md before invoking Step 4 so the six-agent review contract and subagent prompts are available.
Find the toolkit root in this order:
Use PR_REVIEW_TOOLKIT_ROOT when set. This is the supported path.
If PR_REVIEW_TOOLKIT_ROOT is unset, derive the packaged plugin root from the skill path. This SKILL.md lives at <root>/codex/skills/<skill-name>/SKILL.md, so <root> is exactly three levels up. Ensure SKILL_PATH is set in the environment to the absolute path of this SKILL.md before running the snippet:
: "${SKILL_PATH:?SKILL_PATH must be set to the absolute path of this SKILL.md}"
PR_REVIEW_TOOLKIT_ROOT="$(cd "$(dirname "$SKILL_PATH")/../../.." && pwd)"
Verify both <root>/.codex-plugin/plugin.json and <root>/scripts/cache-write-comment.sh exist. If either is missing, treat derivation as failed and proceed to step 3.
Stop and ask the dev agent for PR_REVIEW_TOOLKIT_ROOT.
Canonicalize the root before using helper scripts:
PR_REVIEW_TOOLKIT_ROOT="$(cd "$PR_REVIEW_TOOLKIT_ROOT" && pwd)"
Use only these scripts for review state:
"${PR_REVIEW_TOOLKIT_ROOT}/scripts/get-pr-number.sh"
"${PR_REVIEW_TOOLKIT_ROOT}/scripts/cache-read-comment.sh"
"${PR_REVIEW_TOOLKIT_ROOT}/scripts/cache-write-comment.sh"
"${PR_REVIEW_TOOLKIT_ROOT}/scripts/cache-sync.sh"
"${PR_REVIEW_TOOLKIT_ROOT}/scripts/extract-content-hash.sh"
"${PR_REVIEW_TOOLKIT_ROOT}/scripts/disambiguate-stale-source.sh"
"${PR_REVIEW_TOOLKIT_ROOT}/scripts/review-metadata-upgrade.sh"
"${PR_REVIEW_TOOLKIT_ROOT}/scripts/review-metadata-replace.sh"
Before running the workflow, verify helper scripts are executable and scripts/lib/common.sh is readable.
Workflow
Get the PR number with get-pr-number.sh.
Read the existing canonical review comment:
set +e
EXISTING_CONTENT=$("${PR_REVIEW_TOOLKIT_ROOT}/scripts/cache-read-comment.sh" "$PR_NUMBER")
rc=$?
set -e
case $rc in
0) MODE=append ;;
2) MODE=bootstrap ;;
*) echo "cache-read-comment.sh failed with exit $rc" >&2; exit "$rc" ;;
esac
In append mode, extract the cache content_hash for CAS via the shared helper. The helper does file-existence check, jq read with rc capture, regex validation, and triggers cache-sync.sh on any failure (with rc propagation and post-recovery validation to prevent infinite retry loops):
set -euo pipefail
set +e
EXPECTED_CONTENT_HASH=$("${PR_REVIEW_TOOLKIT_ROOT}/scripts/extract-content-hash.sh" "$PR_NUMBER")
rc=$?
set -e
case $rc in
0) ;;
2) echo "Cache was refreshed; re-read EXISTING_CONTENT and retry from Step 2." >&2; exit 2 ;;
*) exit "$rc" ;;
esac
The helper centralizes the contract documented in cache-write-comment.sh:36-44 and is unit-tested in tests/extract-content-hash-test.sh (6 cases including missing file, missing field, malformed hash, and recovery failure paths). See scripts/extract-content-hash.sh for the implementation.
Run codex-review-pass and provide the PR number, current diff context, changed files, existing review content, and any user-requested scope. The pass must return a bundle whose Agents completed: line names exactly these six agents: code-reviewer, code-simplifier, silent-failure-hunter, type-design-analyzer, pr-test-analyzer, comment-analyzer. Verify with:
set -euo pipefail
EXPECTED_AGENTS=$(printf '%s\n' \
code-reviewer code-simplifier silent-failure-hunter \
type-design-analyzer pr-test-analyzer comment-analyzer | sort -u)
if [ -z "$BUNDLE" ]; then
echo "error: codex-review-pass produced no bundle" >&2
exit 2
fi
# Defensively strip CR so a CRLF-emitting producer doesn't leave a stray
# \r on the last agent name, which would cause comm against the LF-only
# EXPECTED set to mark "comment-analyzer\r" as missing.
# Parse "Agents completed:" with continuation-line support. The producer
# contract (codex-review-pass Output Contract) pins this to a single physical
# line, but we tolerate soft-wrapped continuation (lines starting with one or
# more spaces) so a renderer that wraps long lines cannot trigger a false
# "missing agents" abort. The `printed` sentinel + END guard avoids
# double-emitting `buf` when a non-continuation line follows (awk's `exit`
# always runs END, so a naive `print buf; exit` plus `END { print buf }`
# would emit twice — benign here because of the downstream `sort -u`, but
# the duplication corrupts AGENTS_LINE for any debug logging or any future
# check that uses per-line count).
AGENTS_LINE=$(printf '%s\n' "$BUNDLE" | tr -d '\r' | awk '
/^- *Agents completed:/ { collecting=1; sub(/^- *Agents completed: */, ""); buf=$0; next }
collecting && /^[[:space:]]+/ { sub(/^[[:space:]]+/, " "); buf = buf $0; next }
collecting { print buf; printed=1; exit }
END { if (collecting && !printed) print buf }
')
if [ -z "$AGENTS_LINE" ]; then
echo "error: codex-review-pass bundle does not contain an 'Agents completed:' line" >&2
exit 2
fi
ACTUAL_AGENTS=$(printf '%s\n' "$AGENTS_LINE" \
| tr ',' '\n' \
| awk '{$1=$1; if (length($0)) print}' \
| sort -u)
MISSING=$(comm -23 <(printf '%s\n' "$EXPECTED_AGENTS") <(printf '%s\n' "$ACTUAL_AGENTS"))
if [ -n "$MISSING" ]; then
echo "error: codex-review-pass returned an incomplete bundle. Missing agents:" >&2
while IFS= read -r a; do printf ' - %s\n' "$a" >&2; done <<< "$MISSING"
echo "Do not bootstrap or append from a partial bundle. Surface the bundle's Follow-up notes and abort." >&2
exit 2
fi
This check applies in both bootstrap and append modes — a partial bundle is never published.
Convert the returned review bundle into canonical review sections:
### 🔴 Critical Issues
### 🟡 Important Issues
### 💡 Suggestions
### ✨ Strengths
### 📋 Type Design Ratings
### 🎯 Action Plan
Below the ## 🤖 PR Review heading, render a **Reviewer Sources:** line in fixed order Claude, Gemini, Codex. Include a source only if it has participated: Claude when review_sources.claude.last_reviewed_at != null, Gemini when review_sources.gemini.last_integrated_at != null or review_sources.gemini.consumed_comment_ids is non-empty, Codex when review_sources.codex.last_reviewed_at != null. (Step 8 sets review_sources.codex.last_reviewed_at, so after this skill finishes the line will always include Codex.)
Append only new Codex findings. Preserve existing [Gemini], [Codex], and untagged Claude issues. Treat untagged issues as Claude issues.
Upgrade metadata to schema 1.1. In append mode pipe the existing comment; in bootstrap mode pipe a minimal seed so the upgrade script can produce a complete 1.1 envelope:
if [ "$MODE" = "append" ]; then
UPGRADE_INPUT="$EXISTING_CONTENT"
else
UPGRADE_INPUT=$'<!-- pr-review-metadata\n{}\n-->\n'
fi
METADATA_JSON=$(printf '%s\n' "$UPGRADE_INPUT" \
| "${PR_REVIEW_TOOLKIT_ROOT}/scripts/review-metadata-upgrade.sh" \
--stdin --last-writer pr-review-and-document)
Update metadata (use jq against $METADATA_JSON):
last_writer: pr-review-and-document
skill: pr-review-and-document
review_sources.codex.last_reviewed_head: current HEAD SHA
review_sources.codex.last_reviewed_at: UTC timestamp
review_sources.codex.posted_finding_ids: stable IDs from the review bundle
review_sources.codex.agents_run: the six Codex review agents
review_sources.claude.agents_run: preserve existing value, or [] on Codex bootstrap
- top-level
agents_run: preserve as the Claude compatibility mirror, or [] on Codex bootstrap
Increment PR-global review_round only when this run adds new findings. Empty refreshes update Codex source timestamps without changing counts or existing statuses.
Replace the hidden metadata block with review-metadata-replace.sh. The script requires a metadata JSON file path; pipe the comment over stdin:
METADATA_FILE=$(mktemp)
trap 'rm -f "$METADATA_FILE"' EXIT
printf '%s' "$METADATA_JSON" > "$METADATA_FILE"
UPDATED_CONTENT=$(printf '%s\n' "$EXISTING_CONTENT" \
| "${PR_REVIEW_TOOLKIT_ROOT}/scripts/review-metadata-replace.sh" \
--stdin --metadata-file "$METADATA_FILE")
In bootstrap mode there is no $EXISTING_CONTENT to replace into; assemble the canonical sections around the metadata block directly.
Write the comment through cache-write-comment.sh --stdin "$PR_NUMBER" with --expected-content-hash "$EXPECTED_CONTENT_HASH" when present:
if [ -n "${EXPECTED_CONTENT_HASH:-}" ]; then
printf '%s\n' "$UPDATED_CONTENT" \
| "${PR_REVIEW_TOOLKIT_ROOT}/scripts/cache-write-comment.sh" \
--stdin "$PR_NUMBER" --expected-content-hash "$EXPECTED_CONTENT_HASH"
else
printf '%s\n' "$UPDATED_CONTENT" \
| "${PR_REVIEW_TOOLKIT_ROOT}/scripts/cache-write-comment.sh" \
--stdin "$PR_NUMBER"
fi
Handle cache-write-comment.sh exit codes (see cache-write-comment.sh:22-25):
0: success.
1: covers two distinct failure modes — disambiguate via the shared helper. The helper does file-existence + jq-rc + stale-flag inspection and prints the right recovery command to stderr:
set +e
"${PR_REVIEW_TOOLKIT_ROOT}/scripts/disambiguate-stale-source.sh" "$PR_NUMBER"
disambig_rc=$?
set -e
case $disambig_rc in
1)
# Nominal: recovery advice printed to stderr; follow it.
exit 1
;;
10)
# Cannot disambiguate (cache missing or malformed JSON);
# the script's stderr names the underlying cause. Surface
# the diagnostic to the user and abort — `--sync-from-cache`
# would itself fail with no cache to sync from.
echo "Cannot disambiguate cache-write-comment.sh exit 1 cause; manual intervention required." >&2
exit 10
;;
*)
echo "disambiguate-stale-source.sh exited unexpectedly (rc=$disambig_rc)" >&2
exit "$disambig_rc"
;;
esac
Unit-tested in tests/disambiguate-stale-source-test.sh (cases: stale=true, stale=false, field missing, cache absent -> rc=10, invalid JSON -> rc=10, empty arg).
2: local error; abort.
3: remote is newer. Re-fetch the canonical comment with ${PR_REVIEW_TOOLKIT_ROOT}/scripts/cache-sync.sh "$PR_NUMBER" (it already does a force-refresh internally), then redo Steps 2-11 against the fresh content.
4: CAS hash mismatch. Re-run Step 2 (cache-read-comment.sh) to refresh $EXISTING_CONTENT, re-run Step 3 to recapture and re-validate EXPECTED_CONTENT_HASH, re-merge new Codex findings into the newer content, and retry once. If the retry also exits 4, stop and report CAS conflict: another writer holds the lock with the current content hash.
Bootstrap Mode
When no canonical comment exists, create a new comment with the standard <!-- pr-review-metadata marker, summary table, canonical issue sections, strengths, type ratings, and action plan. Bootstrap must not run concurrently with another producer. If duplicate canonical comments are detected, stop and ask the dev agent to keep only the .pr-review-cache/pr-#.json source_comment_id comment.
Canonical Finding Format
Use this details format for Codex findings:
<details>
<summary><b>N. ⚠️ [Codex] Issue title</b></summary>
**Source:** Codex
**Agents:** code-reviewer, pr-test-analyzer
**File:** `path/to/file.ts:42`
**Finding ID:** `codex:path:symbol:kind:hash`
**Problem:** ...
**Fix:** ...
</details>
Actionable findings must live in the canonical severity sections and be counted in the summary table. Use a ### 🟠 Codex Follow-up Notes section only for non-canonical notes such as a failed subagent, validation observations, or duplicate-risk notes.
Output Contract
End with:
PR review comment:
- PR: #123
- Mode: bootstrap | append
- New Codex findings: N
- Agents run: code-reviewer, code-simplifier, silent-failure-hunter, type-design-analyzer, pr-test-analyzer, comment-analyzer
- Comment URL: ...
Review state:
- Cache: .pr-review-cache/pr-123.json
- Metadata schema: 1.1
- Review round: N
1---2name: pr-review-and-document-23description: Use when asked to review a PR with Codex and save, document, post, or update the review results. This skill owns PR comment/cache state, invokes codex-review-pass for the six-subagent review bundle, and publishes one canonical review comment through .pr-review-cache/pr-#.json.4---56# PR Review and Document78Run a Codex PR review and publish the results to the canonical pr-review-toolkit PR comment.910## Contract1112This skill owns all review state writes. Use `.pr-review-cache/pr-#.json` as the only PR review state file. Do not create extra Codex cache files, extra PR comments, commits, pushes, or direct `gh api` comment updates.1314`codex-review-pass` owns review analysis only. It launches the six read-only subagents and returns a normalized review bundle. This skill converts that bundle into canonical markdown, updates metadata, and writes through `cache-write-comment.sh`. If the `codex-review-pass` skill body is not already loaded, read `${PR_REVIEW_TOOLKIT_ROOT}/codex/skills/codex-review-pass/SKILL.md` before invoking Step 4 so the six-agent review contract and subagent prompts are available.1516Find the toolkit root in this order:17181. Use `PR_REVIEW_TOOLKIT_ROOT` when set. This is the supported path.192. If `PR_REVIEW_TOOLKIT_ROOT` is unset, derive the packaged plugin root from the skill path. This SKILL.md lives at `<root>/codex/skills/<skill-name>/SKILL.md`, so `<root>` is exactly three levels up. Ensure `SKILL_PATH` is set in the environment to the absolute path of this SKILL.md before running the snippet:2021 ```bash22 : "${SKILL_PATH:?SKILL_PATH must be set to the absolute path of this SKILL.md}"23 PR_REVIEW_TOOLKIT_ROOT="$(cd "$(dirname "$SKILL_PATH")/../../.." && pwd)"24 ```2526 Verify both `<root>/.codex-plugin/plugin.json` and `<root>/scripts/cache-write-comment.sh` exist. If either is missing, treat derivation as failed and proceed to step 3.273. Stop and ask the dev agent for `PR_REVIEW_TOOLKIT_ROOT`.2829Canonicalize the root before using helper scripts:3031```bash32PR_REVIEW_TOOLKIT_ROOT="$(cd "$PR_REVIEW_TOOLKIT_ROOT" && pwd)"33```3435Use only these scripts for review state:3637```bash38"${PR_REVIEW_TOOLKIT_ROOT}/scripts/get-pr-number.sh"39"${PR_REVIEW_TOOLKIT_ROOT}/scripts/cache-read-comment.sh"40"${PR_REVIEW_TOOLKIT_ROOT}/scripts/cache-write-comment.sh"41"${PR_REVIEW_TOOLKIT_ROOT}/scripts/cache-sync.sh"42"${PR_REVIEW_TOOLKIT_ROOT}/scripts/extract-content-hash.sh"43"${PR_REVIEW_TOOLKIT_ROOT}/scripts/disambiguate-stale-source.sh"44"${PR_REVIEW_TOOLKIT_ROOT}/scripts/review-metadata-upgrade.sh"45"${PR_REVIEW_TOOLKIT_ROOT}/scripts/review-metadata-replace.sh"46```4748Before running the workflow, verify helper scripts are executable and `scripts/lib/common.sh` is readable.4950## Workflow51521. Get the PR number with `get-pr-number.sh`.532. Read the existing canonical review comment:5455 ```bash56 set +e57 EXISTING_CONTENT=$("${PR_REVIEW_TOOLKIT_ROOT}/scripts/cache-read-comment.sh" "$PR_NUMBER")58 rc=$?59 set -e6061 case $rc in62 0) MODE=append ;;63 2) MODE=bootstrap ;;64 *) echo "cache-read-comment.sh failed with exit $rc" >&2; exit "$rc" ;;65 esac66 ```67683. In append mode, extract the cache `content_hash` for CAS via the shared helper. The helper does file-existence check, jq read with rc capture, regex validation, and triggers `cache-sync.sh` on any failure (with rc propagation and post-recovery validation to prevent infinite retry loops):6970 ```bash71 set -euo pipefail72 set +e73 EXPECTED_CONTENT_HASH=$("${PR_REVIEW_TOOLKIT_ROOT}/scripts/extract-content-hash.sh" "$PR_NUMBER")74 rc=$?75 set -e7677 case $rc in78 0) ;;79 2) echo "Cache was refreshed; re-read EXISTING_CONTENT and retry from Step 2." >&2; exit 2 ;;80 *) exit "$rc" ;;81 esac82 ```8384 The helper centralizes the contract documented in `cache-write-comment.sh:36-44` and is unit-tested in `tests/extract-content-hash-test.sh` (6 cases including missing file, missing field, malformed hash, and recovery failure paths). See [`scripts/extract-content-hash.sh`](../../../../../scripts/extract-content-hash.sh) for the implementation.854. Run `codex-review-pass` and provide the PR number, current diff context, changed files, existing review content, and any user-requested scope. The pass must return a bundle whose `Agents completed:` line names exactly these six agents: `code-reviewer`, `code-simplifier`, `silent-failure-hunter`, `type-design-analyzer`, `pr-test-analyzer`, `comment-analyzer`. Verify with:8687 ```bash88 set -euo pipefail8990 EXPECTED_AGENTS=$(printf '%s\n' \91 code-reviewer code-simplifier silent-failure-hunter \92 type-design-analyzer pr-test-analyzer comment-analyzer | sort -u)9394 if [ -z "$BUNDLE" ]; then95 echo "error: codex-review-pass produced no bundle" >&296 exit 297 fi9899 # Defensively strip CR so a CRLF-emitting producer doesn't leave a stray100 # \r on the last agent name, which would cause comm against the LF-only101 # EXPECTED set to mark "comment-analyzer\r" as missing.102 # Parse "Agents completed:" with continuation-line support. The producer103 # contract (codex-review-pass Output Contract) pins this to a single physical104 # line, but we tolerate soft-wrapped continuation (lines starting with one or105 # more spaces) so a renderer that wraps long lines cannot trigger a false106 # "missing agents" abort. The `printed` sentinel + END guard avoids107 # double-emitting `buf` when a non-continuation line follows (awk's `exit`108 # always runs END, so a naive `print buf; exit` plus `END { print buf }`109 # would emit twice — benign here because of the downstream `sort -u`, but110 # the duplication corrupts AGENTS_LINE for any debug logging or any future111 # check that uses per-line count).112 AGENTS_LINE=$(printf '%s\n' "$BUNDLE" | tr -d '\r' | awk '113 /^- *Agents completed:/ { collecting=1; sub(/^- *Agents completed: */, ""); buf=$0; next }114 collecting && /^[[:space:]]+/ { sub(/^[[:space:]]+/, " "); buf = buf $0; next }115 collecting { print buf; printed=1; exit }116 END { if (collecting && !printed) print buf }117 ')118119 if [ -z "$AGENTS_LINE" ]; then120 echo "error: codex-review-pass bundle does not contain an 'Agents completed:' line" >&2121 exit 2122 fi123124 ACTUAL_AGENTS=$(printf '%s\n' "$AGENTS_LINE" \125 | tr ',' '\n' \126 | awk '{$1=$1; if (length($0)) print}' \127 | sort -u)128129 MISSING=$(comm -23 <(printf '%s\n' "$EXPECTED_AGENTS") <(printf '%s\n' "$ACTUAL_AGENTS"))130 if [ -n "$MISSING" ]; then131 echo "error: codex-review-pass returned an incomplete bundle. Missing agents:" >&2132 while IFS= read -r a; do printf ' - %s\n' "$a" >&2; done <<< "$MISSING"133 echo "Do not bootstrap or append from a partial bundle. Surface the bundle's Follow-up notes and abort." >&2134 exit 2135 fi136 ```137138 This check applies in both bootstrap and append modes — a partial bundle is never published.1395. Convert the returned review bundle into canonical review sections:140 - `### 🔴 Critical Issues`141 - `### 🟡 Important Issues`142 - `### 💡 Suggestions`143 - `### ✨ Strengths`144 - `### 📋 Type Design Ratings`145 - `### 🎯 Action Plan`146147 Below the `## 🤖 PR Review` heading, render a `**Reviewer Sources:**` line in fixed order `Claude, Gemini, Codex`. Include a source only if it has participated: `Claude` when `review_sources.claude.last_reviewed_at != null`, `Gemini` when `review_sources.gemini.last_integrated_at != null` or `review_sources.gemini.consumed_comment_ids` is non-empty, `Codex` when `review_sources.codex.last_reviewed_at != null`. (Step 8 sets `review_sources.codex.last_reviewed_at`, so after this skill finishes the line will always include `Codex`.)1486. Append only new Codex findings. Preserve existing `[Gemini]`, `[Codex]`, and untagged Claude issues. Treat untagged issues as Claude issues.1497. Upgrade metadata to schema `1.1`. In append mode pipe the existing comment; in bootstrap mode pipe a minimal seed so the upgrade script can produce a complete 1.1 envelope:150151 ```bash152 if [ "$MODE" = "append" ]; then153 UPGRADE_INPUT="$EXISTING_CONTENT"154 else155 UPGRADE_INPUT=$'<!-- pr-review-metadata\n{}\n-->\n'156 fi157158 METADATA_JSON=$(printf '%s\n' "$UPGRADE_INPUT" \159 | "${PR_REVIEW_TOOLKIT_ROOT}/scripts/review-metadata-upgrade.sh" \160 --stdin --last-writer pr-review-and-document)161 ```1621638. Update metadata (use `jq` against `$METADATA_JSON`):164 - `last_writer`: `pr-review-and-document`165 - `skill`: `pr-review-and-document`166 - `review_sources.codex.last_reviewed_head`: current HEAD SHA167 - `review_sources.codex.last_reviewed_at`: UTC timestamp168 - `review_sources.codex.posted_finding_ids`: stable IDs from the review bundle169 - `review_sources.codex.agents_run`: the six Codex review agents170 - `review_sources.claude.agents_run`: preserve existing value, or `[]` on Codex bootstrap171 - top-level `agents_run`: preserve as the Claude compatibility mirror, or `[]` on Codex bootstrap1729. Increment PR-global `review_round` only when this run adds new findings. Empty refreshes update Codex source timestamps without changing counts or existing statuses.17310. Replace the hidden metadata block with `review-metadata-replace.sh`. The script requires a metadata JSON file path; pipe the comment over stdin:174175 ```bash176 METADATA_FILE=$(mktemp)177 trap 'rm -f "$METADATA_FILE"' EXIT178 printf '%s' "$METADATA_JSON" > "$METADATA_FILE"179180 UPDATED_CONTENT=$(printf '%s\n' "$EXISTING_CONTENT" \181 | "${PR_REVIEW_TOOLKIT_ROOT}/scripts/review-metadata-replace.sh" \182 --stdin --metadata-file "$METADATA_FILE")183 ```184185 In bootstrap mode there is no `$EXISTING_CONTENT` to replace into; assemble the canonical sections around the metadata block directly.18611. Write the comment through `cache-write-comment.sh --stdin "$PR_NUMBER"` with `--expected-content-hash "$EXPECTED_CONTENT_HASH"` when present:187188 ```bash189 if [ -n "${EXPECTED_CONTENT_HASH:-}" ]; then190 printf '%s\n' "$UPDATED_CONTENT" \191 | "${PR_REVIEW_TOOLKIT_ROOT}/scripts/cache-write-comment.sh" \192 --stdin "$PR_NUMBER" --expected-content-hash "$EXPECTED_CONTENT_HASH"193 else194 printf '%s\n' "$UPDATED_CONTENT" \195 | "${PR_REVIEW_TOOLKIT_ROOT}/scripts/cache-write-comment.sh" \196 --stdin "$PR_NUMBER"197 fi198 ```19920012. Handle `cache-write-comment.sh` exit codes (see `cache-write-comment.sh:22-25`):201 - `0`: success.202 - `1`: covers two distinct failure modes — disambiguate via the shared helper. The helper does file-existence + jq-rc + stale-flag inspection and prints the right recovery command to stderr:203204 ```bash205 set +e206 "${PR_REVIEW_TOOLKIT_ROOT}/scripts/disambiguate-stale-source.sh" "$PR_NUMBER"207 disambig_rc=$?208 set -e209210 case $disambig_rc in211 1)212 # Nominal: recovery advice printed to stderr; follow it.213 exit 1214 ;;215 10)216 # Cannot disambiguate (cache missing or malformed JSON);217 # the script's stderr names the underlying cause. Surface218 # the diagnostic to the user and abort — `--sync-from-cache`219 # would itself fail with no cache to sync from.220 echo "Cannot disambiguate cache-write-comment.sh exit 1 cause; manual intervention required." >&2221 exit 10222 ;;223 *)224 echo "disambiguate-stale-source.sh exited unexpectedly (rc=$disambig_rc)" >&2225 exit "$disambig_rc"226 ;;227 esac228 ```229230 Unit-tested in `tests/disambiguate-stale-source-test.sh` (cases: stale=true, stale=false, field missing, cache absent -> rc=10, invalid JSON -> rc=10, empty arg).231232 - `2`: local error; abort.233 - `3`: remote is newer. Re-fetch the canonical comment with `${PR_REVIEW_TOOLKIT_ROOT}/scripts/cache-sync.sh "$PR_NUMBER"` (it already does a force-refresh internally), then redo Steps 2-11 against the fresh content.234 - `4`: CAS hash mismatch. Re-run Step 2 (`cache-read-comment.sh`) to refresh `$EXISTING_CONTENT`, re-run Step 3 to recapture and re-validate `EXPECTED_CONTENT_HASH`, re-merge new Codex findings into the newer content, and retry once. If the retry also exits `4`, stop and report `CAS conflict: another writer holds the lock` with the current content hash.235236## Bootstrap Mode237238When no canonical comment exists, create a new comment with the standard `<!-- pr-review-metadata` marker, summary table, canonical issue sections, strengths, type ratings, and action plan. Bootstrap must not run concurrently with another producer. If duplicate canonical comments are detected, stop and ask the dev agent to keep only the `.pr-review-cache/pr-#.json` `source_comment_id` comment.239240## Canonical Finding Format241242Use this details format for Codex findings:243244```markdown245<details>246<summary><b>N. ⚠️ [Codex] Issue title</b></summary>247248**Source:** Codex249**Agents:** code-reviewer, pr-test-analyzer250**File:** `path/to/file.ts:42`251**Finding ID:** `codex:path:symbol:kind:hash`252253**Problem:** ...254255**Fix:** ...256257</details>258```259260Actionable findings must live in the canonical severity sections and be counted in the summary table. Use a `### 🟠 Codex Follow-up Notes` section only for non-canonical notes such as a failed subagent, validation observations, or duplicate-risk notes.261262## Output Contract263264End with:265266```text267PR review comment:268- PR: #123269- Mode: bootstrap | append270- New Codex findings: N271- Agents run: code-reviewer, code-simplifier, silent-failure-hunter, type-design-analyzer, pr-test-analyzer, comment-analyzer272- Comment URL: ...273274Review state:275- Cache: .pr-review-cache/pr-123.json276- Metadata schema: 1.1277- Review round: N278```