Skip to content

fix(#230): detect self-review 422 and degrade gracefully when REVIEW_TOKEN missing#271

Open
fullsend-ai-coder[bot] wants to merge 1 commit into
mainfrom
agent/230-fix-self-review-422
Open

fix(#230): detect self-review 422 and degrade gracefully when REVIEW_TOKEN missing#271
fullsend-ai-coder[bot] wants to merge 1 commit into
mainfrom
agent/230-fix-self-review-422

Conversation

@fullsend-ai-coder

Copy link
Copy Markdown

Summary

  • post-review.sh: Replaces the hard REVIEW_TOKEN guard with a soft fallback to GITHUB_TOKEN plus a warning. The agent's sticky comment, label logic, and stale-review cleanup still run even without a minted token — only the formal review submission may fail.
  • postreview.go: Warns when falling back to GITHUB_TOKEN. Detects self-review 422 errors from CreatePullRequestReview and surfaces an actionable message pointing operators to set REVIEW_TOKEN via the mint-token action.
  • Adds isSelfReviewError helper with 5 test cases covering GitHub's known error phrasings, unrelated 422s, non-422 errors, and non-API errors.

Root cause

When REVIEW_TOKEN is undefined (e.g., mint step failure or missing workflow config), post-review.sh hard-failed at line 21 before reaching fullsend post-review. If the guard was bypassed and the Go CLI fell back to GITHUB_TOKEN, the review submission hit a 422 because GitHub rejects self-reviews when the token identity matches the PR author.

Test plan

  • go test -race -run "Review|SelfReview" ./internal/cli/... — 73 tests pass
  • go vet ./... — clean
  • shellcheck on modified post-review.sh — clean
  • post-review-test.sh — all label tests pass
  • scan-secrets --staged — clean
  • gitlint --commit HEAD — clean

Closes #230

…TOKEN missing

When REVIEW_TOKEN is undefined, post-review.sh hard-failed before
reaching fullsend post-review. When the Go CLI fell back to
GITHUB_TOKEN, the review submission hit a 422 because GitHub
rejects self-reviews (PR author = token identity).

Changes:
- post-review.sh: fall back to GITHUB_TOKEN with a warning instead
  of hard-failing, so the agent's sticky comment and label work
  still completes
- postreview.go: warn when falling back to GITHUB_TOKEN; detect
  self-review 422 errors and surface an actionable message pointing
  operators to set REVIEW_TOKEN via the mint-token action
- Add isSelfReviewError helper with tests

Closes #230
@github-actions

Copy link
Copy Markdown

E2E tests did not run

E2E tests run automatically for org/repo members and collaborators on pull requests.

For other contributors, a maintainer must add the ok-to-test label after the latest push.

See E2E testing guide for details.

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 10, 2026

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure · Started 12:58 PM UTC · Completed 12:59 PM UTC
Commit: c776c24 · View workflow run →

@guyoron1

Copy link
Copy Markdown
Owner

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 10, 2026

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure · Started 3:57 PM UTC · Completed 4:09 PM UTC
Commit: d8e3df7 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review

Verdict: comment — medium-severity findings worth noting; none blocking.

This is a well-scoped bug fix for a real production problem (issue #230: 9+ failures across 9 days). The approach — soft fallback from REVIEW_TOKEN to GITHUB_TOKEN with self-review 422 detection — is sound. The forge abstraction is respected (client.CreatePullRequestReview), no gh CLI calls are added in Go code, tests cover the main paths, and the shell script change is correct.

Findings

1. isSelfReviewError false-positive risk — overly broad "cannot" matching

Severity medium
Category logic-error
File internal/cli/postreview.goisSelfReviewError

The matching logic uses a disjunctive OR:

return strings.Contains(combined, "your own") ||
    strings.Contains(combined, "can not") ||
    strings.Contains(combined, "cannot")

The "can not" and "cannot" legs match independently of the self-referential "your own" check. GitHub's review creation API returns other 422 errors containing "cannot" that are not self-review errors — for example, "You cannot request changes on a draft pull request". These would be wrapped with the misleading message about token identity, causing operators to chase a minted-token fix for an unrelated issue.

Impact: The review still fails either way (no data loss or security issue), but the error message is misleading. Operators may waste time on incorrect remediation.

Suggested fix: Require "your own" as the primary signal. Use "cannot"/"can not" only conjunctively:

return strings.Contains(combined, "your own")

Or, if broader matching is desired:

hasSelfRef := strings.Contains(combined, "your own")
hasDenial := strings.Contains(combined, "can not") || strings.Contains(combined, "cannot")
return hasSelfRef && hasDenial

2. Missing test case for the false-positive scenario

Severity medium
Category test-adequacy
File internal/cli/postreview_test.goTestIsSelfReviewError

The 5 test cases cover the happy paths well (self-review detection, non-422, non-API error, unrelated 422), but none exercises a 422 with "cannot" that is not a self-review error. Adding this case would expose the false-positive bug described above:

{
    name: "422 draft PR cannot request changes",
    err:  &gh.APIError{StatusCode: 422, Message: "You cannot request changes on a draft pull request"},
    want: false, // Would FAIL with current implementation
},

3. Header comment says REVIEW_TOKEN is "Required" but it's now optional

Severity medium
Category stale-documentation
File internal/scaffold/fullsend-repo/scripts/post-review.sh
Line 12

The file header (line 11–12) still reads:

# Required environment variables:
#   REVIEW_TOKEN    — token with pull-requests:write on the target repo

The PR makes REVIEW_TOKEN optional (falls back to GITHUB_TOKEN), but the header comment was not updated. Since this comment is in the same file being modified, it should be updated to reflect the new behavior.

Suggested fix: Change line 11–12 to something like:

# Environment variables:
#   REVIEW_TOKEN    — (optional) token with pull-requests:write; falls back to GITHUB_TOKEN

4. User guide references REVIEW_TOKEN without explaining it's optional

Severity low
Category incomplete-documentation
File docs/guides/user/running-agents-locally.md
Line 149

The "Review agent" section shows REVIEW_TOKEN={github-pat} in the env file example without noting it's now optional. Users following this guide may think REVIEW_TOKEN is mandatory.

What looks good

  • Authorization & scope: PR traces directly to issue [RTK+Ponytail] post-review.sh fails with 422 when REVIEW_TOKEN undefined after PR #2389 #230; scope matches the issue's authorization (soft fallback + self-review detection). No scope creep.
  • Forge abstraction respected: Go code uses client.CreatePullRequestReview — no forge violations.
  • Scaffold is the right place: internal/scaffold/fullsend-repo/scripts/post-review.sh is the upstream template; no other copies need updating per the layered content model.
  • Security clean: The ::warning:: message is a static string (no variable interpolation, no GHA workflow command injection). Token masking works correctly after fallback. No secrets leaked in error messages.
  • Existing tests preserved: Import reordering in postreview_test.go is purely cosmetic (fixes Go import grouping). No existing test logic or assertions were weakened.
  • Error wrapping chain correct: errors.As correctly unwraps through %w chains from CreatePullRequestReview.
  • Shell script fallback correct: ${REVIEW_TOKEN:-} / ${GITHUB_TOKEN:?...} pattern handles all cases (set, empty, unset).

Labels: PR fixes a bug in the review post-script and CLI error handling

Previous run

Review

Verdict: comment — one medium finding worth noting; no blocking issues.

This PR addresses issue #230 by converting the hard REVIEW_TOKEN guard in post-review.sh to a soft fallback to GITHUB_TOKEN, and adding isSelfReviewError detection in the Go CLI to surface clear error messages when the review submission hits a 422 self-review rejection. The approach is sound — it prevents the hard crash, preserves the preferred minting path, and gives operators actionable guidance when the fallback token triggers a self-review 422.

The test coverage is adequate (5 cases covering wrapped errors, alternate wording, non-422, unrelated 422, and non-API errors), the tokenFallback logic has no edge-case gaps, and the shell script changes are clean (static ::warning:: string, ::add-mask:: still runs correctly after fallback).

Findings

1. isSelfReviewError matching is overly broad — false positive risk

Severity: medium · Category: false-positive-logic · File: internal/cli/postreview.go

The function matches any 422 APIError whose combined message contains "can not" or "cannot" (case-insensitive). These are extremely generic English words. While the call site is narrow (only invoked when CreatePullRequestReview fails), GitHub could return other 422 errors from that endpoint containing "cannot" — e.g., a hypothetical "Cannot submit review on a closed pull request" — which would be misidentified as a self-review error, producing a misleading message about token identity.

The "your own" match alone is sufficient for the known GitHub self-review messages ("Can not approve your own pull request", "You cannot review your own pull request"). The bare "can not" and "cannot" matches add false-positive surface area without additional correct-detection value.

For comparison, the existing isBranchProtectionError in github.go uses domain-specific terms ("protected", "required status", "rule violation") that are unlikely to appear in unrelated errors. "cannot" is generic English.

Suggested fix: Remove the standalone "can not" and "cannot" matches. Keep "your own", or use tighter compound matches like strings.Contains(combined, "can not approve") or strings.Contains(combined, "cannot review").

2. Script header comment is stale

Severity: low · Category: stale-documentation · File: internal/scaffold/fullsend-repo/scripts/post-review.sh, lines 11–12

The file header still lists REVIEW_TOKEN under "Required environment variables" but the implementation now treats it as optional with a fallback to GITHUB_TOKEN. The header should reflect the new behavior.

Suggested fix: Update the header to mark REVIEW_TOKEN as optional and document the GITHUB_TOKEN fallback.

3. Error helper placement diverges from established pattern

Severity: low · Category: code-organization · File: internal/cli/postreview.go

The existing is*Error helpers (isBranchProtectionError, isAlreadyExistsError) live in internal/forge/github/github.go alongside the APIError type, using msg as the variable name and taking *APIError directly. The new isSelfReviewError lives in internal/cli/postreview.go, takes error (using errors.As), and uses combined as the variable name.

The different interface (error vs *APIError) is defensible since the function is called from consumer code. But placing it alongside its siblings in the github package (exported or with the same *APIError signature) would be more consistent.

Suggested fix: Consider moving to internal/forge/github/github.go with the same *APIError parameter signature as the existing helpers, and calling errors.As at the call site in postreview.go instead.

@guyoron1

Copy link
Copy Markdown
Owner

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 10, 2026

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure · Started 4:30 PM UTC · Completed 4:40 PM UTC
Commit: d8e3df7 · View workflow run →

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.

[RTK+Ponytail] post-review.sh fails with 422 when REVIEW_TOKEN undefined after PR #2389

1 participant