Skip to content

feat(review): add REVIEW_FINDING_SEVERITY_THRESHOLD config var - #2341

Merged
ralphbean merged 9 commits into
mainfrom
feat/review-severity-threshold
Jun 18, 2026
Merged

feat(review): add REVIEW_FINDING_SEVERITY_THRESHOLD config var#2341
ralphbean merged 9 commits into
mainfrom
feat/review-severity-threshold

Conversation

@ralphbean

@ralphbean ralphbean commented Jun 16, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds REVIEW_FINDING_SEVERITY_THRESHOLD config var for the review agent, letting repo owners suppress low-severity findings (default: low, which drops info-level findings)
  • Agent prompt reads the var and omits findings below the threshold from both narrative and structured output
  • Post-script filters the findings array as defense-in-depth before posting inline comments
  • Post-script downgrades request-changes/reject to comment when filtering removes all findings
  • Validates threshold value with a warning on unrecognized strings
  • Adds ### Variables subsection to all docs/agents/*.md files
  • Updates hack/lint-agent-docs to require the ### Variables subsection under ## Configuration and extension

Test plan

  • ./hack/lint-agent-docs passes with new ### Variables check
  • make script-test passes (18 post-review tests including 6 severity filter + 3 verdict-downgrade tests)
  • make lint — clean
  • Review that agent prompt section reads naturally and gives clear instructions
  • Review that docs/agents/review.md Variables table is accurate

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown

Site preview

Preview: https://6e3e555e-site.fullsend-ai.workers.dev

Commit: 890e31dd45ff10d46abc4d618eaa0be5384fcb6d

@codecov

codecov Bot commented Jun 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 16, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:04 PM UTC · Completed 4:18 PM UTC
Commit: f33d467 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review

Findings

Medium

  • [injection] internal/scaffold/fullsend-repo/scripts/post-review.sh:79 — The REVIEW_FINDING_SEVERITY_THRESHOLD value is interpolated unsanitized into a GHA ::warning:: workflow command in the invalid-value catch-all branch. If the environment variable contains embedded newlines (which bypass the case pattern matching), the echo output could inject additional GHA workflow commands. The same script already sanitizes label values before interpolation into workflow commands (lines 190–197), establishing the expected pattern. While the attack surface is limited (the env var is set by repo owners, not PR authors), unsanitized interpolation into workflow commands is a recognized anti-pattern.
    Remediation: Sanitize the value before interpolating it into the ::warning:: message — strip newlines, carriage returns, and :: delimiters, matching the sanitization already applied to label values later in the same script.

Low

  • [edge-case] internal/scaffold/fullsend-repo/scripts/post-review.sh:82severity_rank maps unrecognized severity values to rank 1 (same as low). The jq filter's else 1 branch has the same behavior. In practice, findings come from a schema-validated JSON file where severity is constrained, so the risk is minimal.

  • [test-integrity] internal/scaffold/fullsend-repo/scripts/post-review-test.sh:113 — The test file reimplements severity_rank() and filter_findings_json() independently of the production code in post-review.sh. The unit-level filter tests exercise only the test's own copy of the logic, not the production jq filter. The integration tests at the end of the file (which invoke the real post-review.sh) mitigate this by providing actual production coverage.

  • [naming-inconsistency] internal/scaffold/fullsend-repo/env/review.env — ADR 0049 uses REVIEW_SEVERITY_THRESHOLD in its examples, but the implementation uses REVIEW_FINDING_SEVERITY_THRESHOLD (with FINDING inserted). Either the ADR or the implementation should be updated for consistency.

  • [scope-expansion] hack/lint-agent-docs — The PR adds ### Variables enforcement and sections to all six agent docs, but the PR title mentions only REVIEW_FINDING_SEVERITY_THRESHOLD. The broader scope is clearly declared in the PR body and is a reasonable preparatory step for future config vars.

  • [awk-pattern-consistency] hack/lint-agent-docs:145 — The new awk command uses a different code-fence detection pattern (f{next}) compared to existing awk commands in the same file (lines 79, 86 use f==0 && ...). Both are functionally equivalent but stylistically inconsistent.

  • [section-separator-consistency] hack/lint-agent-docs:125 — The new Variables check section omits a trailing separator after the check completes, unlike the established pattern in earlier sections.

  • [documentation-style] docs/agents/code.md — The added ### Variables subsections contain None. (with period). Consider whether None (without period) better matches ADR-0049's format.

  • [documentation-completeness] docs/guides/user/running-agents-locally.md:148 — The running-agents-locally guide's example fullsend-review.env file does not mention the new REVIEW_FINDING_SEVERITY_THRESHOLD variable. Borderline since the variable is optional with a safe default.

Previous run

Review

Findings

Medium

  • [injection] internal/scaffold/fullsend-repo/scripts/post-review.sh:79 — The REVIEW_FINDING_SEVERITY_THRESHOLD value is interpolated unsanitized into a GHA ::warning:: workflow command in the invalid-value catch-all branch. If the environment variable contains embedded newlines (which bypass the case pattern matching since no fixed pattern matches), the echo output could inject additional GHA workflow commands. The same script already sanitizes label values before interpolation into workflow commands (lines 190-197), establishing the expected pattern. While the attack surface is limited (the env var is set by repo owners, not PR authors, and ::set-env:: is disabled by default), unsanitized interpolation into workflow commands is a recognized anti-pattern.
    Remediation: Sanitize the value before interpolating it into the ::warning:: message — strip newlines, carriage returns, and :: delimiters, matching the sanitization already applied to label values later in the same script.

Low

  • [edge-case] internal/scaffold/fullsend-repo/scripts/post-review.sh:82severity_rank maps unrecognized severity values to rank 1 (same as low). The jq filter's else 1 branch has the same behavior. In practice, findings come from a schema-validated JSON file where severity is constrained, so the risk is minimal.

  • [naming-alignment] internal/scaffold/fullsend-repo/env/review.env — The variable name REVIEW_FINDING_SEVERITY_THRESHOLD differs from the ADR-0049 example which uses REVIEW_SEVERITY_THRESHOLD (without FINDING). Both follow the {AGENT}_{SETTING_NAME} convention, but either the ADR or the implementation should be updated for consistency.

  • [documentation-completeness] docs/guides/user/running-agents-locally.md:148 — The running-agents-locally guide provides an example fullsend-review.env file for local review agent runs, but it does not mention the new REVIEW_FINDING_SEVERITY_THRESHOLD configuration variable. This is borderline since the variable is optional with a safe default, but noting for completeness.


Labels: PR modifies review agent scripts, prompts, env config, and agent docs

Previous run (2)

Review

Findings

Low

  • [documentation-currency] docs/agents/fix.md — The new ### Variables section says "None" but fix-agent.env contains hardcoded config values (ITERATION_CAP=5, ITERATION_CAP_HUMAN=10, STRATEGY_ESCALATION_THRESHOLD=3, MAX_RETRIES=1, TIMEOUT_SECONDS=1500). While these are hardcoded constants rather than user-configurable passthrough variables like REVIEW_FINDING_SEVERITY_THRESHOLD, a user could still override them via the layered customization system. Consider either documenting them or adding a note clarifying that "Variables" refers to user-tunable env vars set via CI env: blocks.

  • [documentation-currency] docs/agents/code.md — Same pattern: the new ### Variables section says "None" but code-agent.env contains MAX_RETRIES=1 and TIMEOUT_SECONDS=2100. See fix.md finding above.

  • [test-integrity] internal/scaffold/fullsend-repo/scripts/post-review-test.sh — The severity-filter and verdict-downgrade test logic (severity_rank, filter_findings_json, filter_and_downgrade) reimplements the production logic from post-review.sh rather than sourcing it. This is a common and reasonable pattern for shell script testing (avoiding sourcing a script with side effects), but creates a divergence risk if one copy is updated without the other.

  • [edge-case] internal/scaffold/fullsend-repo/scripts/post-review.sh:82severity_rank maps unrecognized severity values to rank 1 (same as low). The jq filter's else 1 branch has the same behavior. In practice, findings come from a schema-validated JSON file where severity is constrained, so the risk is minimal.

  • [undocumented-convention] internal/scaffold/fullsend-repo/env/review.envREVIEW_FINDING_SEVERITY_THRESHOLD is the first agent-prefixed env var. Existing env vars use unprefixed names (PR_NUMBER, MAX_RETRIES, etc.). The prefix is a reasonable convention evolution that disambiguates agent-specific tuning vars from generic pipeline vars, but the naming convention is not yet documented.

  • [missing-authorization] internal/scaffold/fullsend-repo/agents/review.md — Non-trivial change (312 additions across 11 files) with no linked GitHub issue. Consider creating a tracking issue for the severity threshold feature.

  • [scope-creep] hack/lint-agent-docs — PR title mentions only REVIEW_FINDING_SEVERITY_THRESHOLD but changes also add ### Variables subsections to 6 agent docs and enforce the new structure via lint. The broader scope is documented in the PR body.

  • [documentation-completeness] docs/guides/user/customizing-agents.md — The customizing agents guide lists env/ as overridable but does not explain how to tune agent behavior via env vars. The new Variables sections in agent docs make this gap more visible.

Previous run (3)

Review

Findings

High

  • [logic-error] internal/scaffold/fullsend-repo/scripts/post-review.sh:90 — The post-script filters findings below the severity threshold but never downgrades the action when all findings are removed. Schema validation (minItems: 1 on findings) runs before the post-script, so it cannot catch this. If filtering removes all findings from a request-changes result, the post-script will pass {action: "request-changes", findings: []} to the downstream consumer, violating the expected contract. Additionally, the agent prompt instructs downgrade to approve but the post-script does not implement this, creating an inconsistency. The prompt's instruction to downgrade to approve (rather than comment) is questionable — silently approving when findings were present but filtered is a weaker safety posture than downgrading to comment.
    Remediation: After filtering, check if findings array is empty and action is request-changes. If so, rewrite action to comment (not approve) and remove the empty findings key. Align the agent prompt to also use comment instead of approve.

  • [missing-referenced-adr] internal/scaffold/fullsend-repo/agents/review.md — The PR title references "ADR 0047" and the body claims this change "Builds on docs(adr): ADR 0047 — agent configuration env var convention #2334 (ADR 0047 — agent configuration env var convention)" but ADR 0047 does not exist in the repository. The docs/ADRs/ directory contains ADRs numbered up to 0046. The architectural foundation claimed for this change is absent.
    Remediation: Create ADR 0047 first establishing the agent configuration env var convention, or update the PR description to remove the false reference and explain the design rationale directly.

Medium

  • [missing-authorization] internal/scaffold/fullsend-repo/agents/review.md — This is a non-trivial change (220 additions across 11 files) with no linked GitHub issue. The PR adds severity filtering and modifies the agent contract, architectural documents, and test infrastructure.
    Remediation: Create a GitHub issue documenting the need for REVIEW_FINDING_SEVERITY_THRESHOLD, link it to the PR.

  • [test-inadequate] internal/scaffold/fullsend-repo/scripts/post-review-test.sh:155 — The severity-filter tests verify finding counts but do not test the verdict-downgrade scenario: when filtering removes all findings from a request-changes action. The filter function is duplicated in the test rather than sourced from post-review.sh.
    Remediation: Add a test case for empty findings after filtering with request-changes action.

Low

  • [documentation-accuracy] docs/agents/review.md:71 — The docs state the post-script "filters the findings array as defense-in-depth" but do not mention the post-script is missing verdict-downgrade logic. Creates impression of complete fallback when the post-script only partially implements the feature.

  • [scope-creep] hack/lint-agent-docs — PR adds ### Variables sections to 6 agent docs and modifies lint-agent-docs to enforce the new section, but PR title only mentions REVIEW_FINDING_SEVERITY_THRESHOLD. The broader doc restructuring goes beyond the stated feature scope.

  • [architectural-coherence] internal/scaffold/fullsend-repo/env/review.env — No established convention for agent-prefixed config env vars (REVIEW_*, CODE_*, etc.) documented in AGENTS.md or any ADR. Related to the missing ADR 0047 finding.

  • [edge-case] internal/scaffold/fullsend-repo/scripts/post-review.sh:82severity_rank maps unrecognized severity values to rank 1 (same as low). Misspelled severity strings are silently treated as low rather than flagged.

@fullsend-ai-review fullsend-ai-review 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.

See the review comment for full details.


threshold_rank=$(severity_rank "$REVIEW_FINDING_SEVERITY_THRESHOLD")

if jq -e '.findings' "${RESULT_FILE}" >/dev/null 2>&1; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[high] logic-error

The post-script filters findings below the severity threshold but never downgrades the action when all findings are removed. If filtering removes all findings from a request-changes result, the result will contain {action: request-changes, findings: []} — an empty array that violates the schema's minItems: 1 constraint. The agent prompt instructs downgrade to 'approve' but the post-script does not implement this, and 'approve' is a weaker safety posture than 'comment'.

Suggested fix: After filtering, check if findings array is empty and action is request-changes. If so, rewrite action to 'comment' (not 'approve') and remove the empty findings key. Align the agent prompt to also use 'comment' instead of 'approve'.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 2174998. Post-script now downgrades to comment (not approve) when filtering empties the findings array, and deletes the findings key so we don't violate minItems: 1.

echo " actual count: '${actual_count}'"
FAILURES=$((FAILURES + 1))
return
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] test-inadequate

The severity-filter tests verify finding counts but do not test the verdict-downgrade scenario: when filtering removes all findings from a request-changes action. The filter function is duplicated in the test rather than sourced from post-review.sh.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Added three test cases in 2174998 — request-changes with all findings filtered, reject with all filtered, and partial filtering (no downgrade).

Comment thread docs/agents/review.md
2. **Post-script (runner):** The post-script filters the `findings`
array as defense-in-depth before posting. Set it in the CI workflow
`env:` block.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] documentation-accuracy

The docs state the post-script 'filters the findings array as defense-in-depth' but do not mention the post-script is missing verdict-downgrade logic.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Updated the docs in 2174998 — now describes the downgrade behavior directly.

info) echo 0 ;;
low) echo 1 ;;
medium) echo 2 ;;
high) echo 3 ;;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] edge-case

severity_rank maps unrecognized severity values to rank 1 (same as low). Misspelled severity strings silently treated as low rather than flagged.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The input validation on the threshold value (added in 2174998) covers this — unrecognized strings now get a ::warning:: and fall back to low before severity_rank is ever called.

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review Squad Report — 6 agents (2x claude-coder, 2x claude-researcher, 1x gemini-code-review, 1x cursor-code-review) · Models: Claude, Gemini, Codex

5 new inline comments posted (MEDIUM+). 2 findings already covered by the existing fullsend-ai-review comments (verdict downgrade at post-review.sh:90, test coverage at post-review-test.sh:155) — skipped to avoid duplicates.

Summary: The severity filtering implementation is clean for the happy path, but the post-script is missing the verdict-downgrade logic when filtering empties all findings (H1, already flagged), and the downgrade target should be comment not approve (H2, new). Input validation on the threshold value (M1) is the most broadly agreed-upon fix across all 6 agents.


This filtering applies to the narrative body text and the structured
findings equally. If filtering removes all findings from a
`request-changes` verdict, downgrade the verdict to `approve`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[high] safety — downgrade target should be comment, not approve

The prompt instructs the agent to downgrade to approve when filtering removes all findings. But approve triggers the ready-for-merge label (post-review.sh:242) and signals the PR is safe to merge. A PR that originally had request-changes findings — but all fell below the severity threshold — should not get an explicit approval.

The safer target is comment, which yields the requires-manual-review label: "the review ran, no blocking findings remain, but a human should still look."

Suggestion: Change to: "downgrade the verdict to comment"

Flagged by 2/6 review agents (Claude) — consensus

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agreed — comment is the right target. Changed in 2174998.

# post-script enforces it. The filter runs before ACTION is read so
# that verdict recalculation (if all findings are removed) is possible.
# ---------------------------------------------------------------------------
REVIEW_FINDING_SEVERITY_THRESHOLD="${REVIEW_FINDING_SEVERITY_THRESHOLD:-low}"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[medium] input-validation — no validation on REVIEW_FINDING_SEVERITY_THRESHOLD

The variable defaults to low if unset but is never validated. A typo like hgih or uppercase HIGH silently maps to rank 1 (low) via the severity_rank wildcard case, giving different behavior than intended with no warning.

Suggestion:

case "$REVIEW_FINDING_SEVERITY_THRESHOLD" in
  info|low|medium|high|critical) ;;
  *) echo "::warning::Invalid REVIEW_FINDING_SEVERITY_THRESHOLD='${REVIEW_FINDING_SEVERITY_THRESHOLD}', defaulting to 'low'"
     REVIEW_FINDING_SEVERITY_THRESHOLD="low" ;;
esac

Flagged by 5/6 review agents (Claude, Gemini, Codex) — strong consensus

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Added the validation with a ::warning:: in 2174998. Falls back to low on unrecognized values.

# ---------------------------------------------------------------------------
# Severity filtering: drop findings below the configured threshold.
# Defense-in-depth — the agent should already have filtered, but the
# post-script enforces it. The filter runs before ACTION is read so

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[medium] documentation — defense-in-depth limitation not documented

The post-script filters the structured findings array but cannot filter the narrative body text (free-form markdown). If the agent's prompt-based filtering fails — the exact scenario defense-in-depth covers — below-threshold findings will still appear in the review body while their inline comments are suppressed.

Suggestion: Add to the comment block:

# Note: this only filters the structured findings array. Narrative body
# filtering relies solely on the agent prompt.

Flagged by 1/6 review agents (Claude) — single-agent finding

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I'm going to leave this one as-is. The comment already says "defense-in-depth" which communicates that it's a partial backstop. Documenting every thing it doesn't do feels like it'd grow without bound.

Comment thread hack/lint-agent-docs
else
echo " $doc_basename: OK"
fi
done

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[medium] lint-gap — ### Variables check is not positional

The awk command checks that ### Variables exists anywhere in the file outside fenced code blocks. It does not verify it appears between ## Configuration and extension and the next ## heading. A doc with ### Variables under the wrong section would pass.

Suggestion: Combine into a single awk pass that tracks section context:

awk 'BEGIN{f=0;c=0} /^```/{f=1-f;next} f{next}
  /^## Configuration and extension/{c=1;next} /^## /{c=0}
  c && /^### Variables/{found=1} END{exit !found}' "$doc_path"

Flagged by 4/6 review agents (Claude, Gemini) — consensus

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch — made the awk check positional in 2174998. It now tracks whether we're inside ## Configuration and extension before looking for ### Variables.

Comment thread docs/agents/review.md Outdated
Comment on lines 64 to 76
1. **Sandbox (agent inference):** The review agent reads it from the
environment and omits findings below the threshold from its output
(`body` and `findings` array). Set it in `env/review.env` or via the
CI workflow `env:` block.
2. **Post-script (runner):** The post-script filters the `findings`
array as defense-in-depth before posting. Set it in the CI workflow
`env:` block.

Set the same value in both places. If they differ, the more restrictive
value wins for inline comments (post-script filters what the agent
already filtered).

## Source

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[medium] documentation — docs imply two-place configuration unnecessarily

The docs say "Set the same value in both places." In practice, the user only needs to set it once in the workflow env: block — review.env passes it through to the sandbox via export REVIEW_FINDING_SEVERITY_THRESHOLD="${REVIEW_FINDING_SEVERITY_THRESHOLD}", and the post-script reads it from the runner environment directly. The current wording creates unnecessary confusion.

Suggestion: Simplify to:

Set REVIEW_FINDING_SEVERITY_THRESHOLD in the CI workflow env: block. The env file passes it to the sandbox automatically, and the post-script reads it from the runner environment. No separate configuration is needed.

Also mention the verdict-downgrade behavior: when filtering removes all findings from a request-changes verdict, the review is downgraded to comment (requires manual review).

Flagged by 2/6 review agents (Claude) — consensus

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Simplified in 2174998. Dropped the two-places wording and added the downgrade behavior.

fullsend-ai-coder Bot added a commit that referenced this pull request Jun 18, 2026
…lback

Per human feedback on PR #2415: all findings whose line is outside a
diff hunk now fall back to file-level comments, not just medium+.
Removed isMediumPlusSeverity() helper and info-severity filtering —
severity-based filtering will be handled by a separate configuration
variable introduced in #2341.

Addresses review feedback on #2415
@ralphbean
ralphbean force-pushed the feat/review-severity-threshold branch from f33d467 to 2174998 Compare June 18, 2026 14:34
@ralphbean ralphbean changed the title feat(review): add REVIEW_FINDING_SEVERITY_THRESHOLD config var (ADR 0047) feat(review): add REVIEW_FINDING_SEVERITY_THRESHOLD config var Jun 18, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 18, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:37 PM UTC · Completed 2:53 PM UTC
Commit: 2174998 · View workflow run →

*) echo "::warning::Invalid REVIEW_FINDING_SEVERITY_THRESHOLD='${REVIEW_FINDING_SEVERITY_THRESHOLD}', defaulting to 'low'"
REVIEW_FINDING_SEVERITY_THRESHOLD="low" ;;
esac

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] edge-case

severity_rank maps unrecognized severity values to rank 1 (same as low). A misspelled severity string would be silently treated as low-severity. Risk is minimal since findings come from schema-validated JSON.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Covered by the input validation at lines 79–83 (added in 2174998). Unrecognized values get a ::warning:: and fall back to low before severity_rank is called.

@fullsend-ai-review fullsend-ai-review Bot added the ready-for-merge All reviewers approved — ready to merge label Jun 18, 2026

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review Squad — Round 2

Agents: 4 (Claude ×2, Gemini, Codex)
Findings posted: 7 (1 critical, 1 high, 4 medium, 1 low)
Skipped (already posted from round 1): input-validation, lint-gap, docs two-place config, defense-in-depth limitation

New findings this round

# Sev Finding Consensus
1 critical Branch conflicts with main — missing label_actions + CLEANUP_FILES 1/4 (verified)
2 high Empty findings: [] on approve/comment violates schema minItems: 1 3/4
3 medium Agent prompt omits reject from downgrade instruction 2/4
4 medium No test coverage for approve/comment with all findings filtered 2/4
5 medium Duplicated severity-ranking logic across 3 locations 3/4
6 medium Agent prompt "if set" ambiguous with empty string from env 1/4
7 low DOWNGRADE_RESULT temp file not in trap cleanup 2/4

Prior round status

Author responded to 9/10 threads from round 1 claiming fixes in 2174998, but 0/10 threads are resolved on GitHub. Thread 7 (defense-in-depth docs) was intentionally declined. Thread 10 (new fullsend-ai-review finding) has no response yet.

Assisted-by: Claude (review), Gemini (review), Codex (review)

set -euo pipefail

FILTERED_RESULT="" MODIFIED_RESULT=""
trap 'rm -f "${FILTERED_RESULT:-}" "${MODIFIED_RESULT:-}"' EXIT

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[critical] rebase-required — branch conflicts with main

The PR merge state is CONFLICTING. Main has been refactored to use a CLEANUP_FILES=() array pattern with trap 'rm -f "${CLEANUP_FILES[@]}"' EXIT and CLEANUP_FILES+=() accumulation. Main also added an entire label_actions validation section (~65 lines for sanitizing label names, validating control labels, checking repo label existence). Both are missing from this branch.

After rebasing, the severity-filtering temp files (FILTERED_RESULT, DOWNGRADE_RESULT) should integrate into the CLEANUP_FILES+=() pattern instead of the separate trap here.

Flagged by 1/4 review agents (Claude) — verified via git show origin/main and gh pr view --json mergeable

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Rebased onto main in 7c24049. CLEANUP_FILES pattern integrated, label_actions processing picked up cleanly.

Comment on lines +119 to +129
if [ "${filtered_count}" -eq 0 ]; then
original_action=$(jq -r '.action' "${FILTERED_RESULT}")
if [ "${original_action}" = "request-changes" ] || [ "${original_action}" = "reject" ]; then
echo "All findings removed by severity filter — downgrading '${original_action}' to 'comment'"
DOWNGRADE_RESULT=$(mktemp)
jq 'del(.findings) | .action = "comment"' "${FILTERED_RESULT}" > "${DOWNGRADE_RESULT}"
RESULT_FILE="${DOWNGRADE_RESULT}"
rm -f "${FILTERED_RESULT}"
FILTERED_RESULT="${DOWNGRADE_RESULT}"
fi
fi

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[high] schema-violation — empty findings: [] left for approve/comment actions

The downgrade logic only handles request-changes and reject. If an approve or comment result has findings (e.g., info-level actionable findings) and filtering removes all of them, the result retains "findings": []. The schema specifies "minItems": 1 for findings (schemas/review-result.schema.json), so this empty array fails schema validation and triggers a harness retry.

Suggested fix: After filtering, unconditionally del(.findings) when count is 0 for all actions, not just request-changes/reject. Only downgrade the action for request-changes/reject:

if [ "${filtered_count}" -eq 0 ]; then
  original_action=$(jq -r '.action' "${FILTERED_RESULT}")
  DOWNGRADE_RESULT=$(mktemp)
  if [ "${original_action}" = "request-changes" ] || [ "${original_action}" = "reject" ]; then
    echo "All findings removed by severity filter — downgrading '${original_action}' to 'comment'"
    jq 'del(.findings) | .action = "comment"' "${FILTERED_RESULT}" > "${DOWNGRADE_RESULT}"
  else
    jq 'del(.findings)' "${FILTERED_RESULT}" > "${DOWNGRADE_RESULT}"
  fi
  RESULT_FILE="${DOWNGRADE_RESULT}"
  rm -f "${FILTERED_RESULT}"
  FILTERED_RESULT="${DOWNGRADE_RESULT}"
fi

Flagged by 3/4 review agents (Claude, Gemini) — strong consensus

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 7c24049. The post-script now unconditionally del(.findings) when filtered_count is 0, regardless of action. Added test cases for comment and approve with all findings filtered.


This filtering applies to the narrative body text and the structured
findings equally. If filtering removes all findings from a
`request-changes` verdict, downgrade the verdict to `comment`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[medium] prompt-gap — downgrade instruction omits reject verdict

The prompt says "If filtering removes all findings from a request-changes verdict, downgrade the verdict to comment." but the post-script also downgrades reject (line 121). Without this, the agent could produce a reject verdict with zero findings.

Suggestion: Update to: "If filtering removes all findings from a request-changes or reject verdict, downgrade the verdict to comment."

Flagged by 2/4 review agents (Claude, Codex) — consensus

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 7c24049. Prompt now says "request-changes or reject."

Comment on lines +256 to +261
run_downgrade_test "reject-all-filtered-downgrade" \
"$ALL_INFO_REJECT" "low" "comment" "false"

# Partial filtering: some findings remain → no downgrade
run_downgrade_test "request-changes-partial-filter-no-downgrade" \
"$MIXED_FINDINGS" "medium" "request-changes" "true"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[medium] test-gap — no coverage for approve/comment with all findings filtered

Tests cover request-changes and reject downgrade but not approve or comment actions with findings that get completely filtered. This is the path that triggers the empty findings: [] schema violation (see companion finding on post-review.sh).

Suggestion: Add test cases:

COMMENT_ALL_INFO='{"action":"comment","body":"text","head_sha":"abc123","findings":[
  {"severity":"info","category":"style","file":"a.go","description":"x"}
]}'
run_downgrade_test "comment-all-filtered-removes-findings" \
  "$COMMENT_ALL_INFO" "low" "comment" "false"

APPROVE_ALL_INFO='{"action":"approve","body":"LGTM","head_sha":"abc123","findings":[
  {"severity":"info","category":"style","file":"a.go","description":"x","actionable":true}
]}'
run_downgrade_test "approve-all-filtered-removes-findings" \
  "$APPROVE_ALL_INFO" "low" "approve" "false"

Flagged by 2/4 review agents (Claude, Gemini) — consensus

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Added in 7c24049comment-all-filtered-removes-findings and approve-all-filtered-removes-findings.

Comment on lines +105 to +114

severity_rank() {
case "$1" in
info) echo 0 ;;
low) echo 1 ;;
medium) echo 2 ;;
high) echo 3 ;;
critical) echo 4 ;;
*) echo 1 ;;
esac

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[medium] maintenance — duplicated severity-ranking logic

severity_rank() and the jq filter are re-implemented here rather than sourced from post-review.sh. If the production logic changes (e.g., new severity levels, rank order), the test copy must be manually kept in sync. This matches the existing pattern in this test file (header says "reimplements... so we can test it without network access"), so it's a known tradeoff.

Suggestion: Add a cross-reference comment: # Mirrors severity_rank() in post-review.sh — keep in sync

Flagged by 3/4 review agents (Claude, Codex) — strong consensus

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Added cross-ref comment during the rebase: "Mirrors severity_rank() in post-review.sh — keep in sync".

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Already there — line 104 has # Mirrors severity_rank() in post-review.sh — keep in sync, added in 7c24049.

Comment on lines +57 to +64
If `$REVIEW_FINDING_SEVERITY_THRESHOLD` is set, omit findings below
that severity level. The severity order from lowest to highest is:

info < low < medium < high < critical

When the threshold is `low` (the default), suppress `info`-level
findings — do not mention them in the review body and do not include
them in the `findings` array. When unset, treat the threshold as `low`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[medium] prompt-ambiguity — "If set" vs empty string

The prompt says "If $REVIEW_FINDING_SEVERITY_THRESHOLD is set" but review.env always exports it — potentially as an empty string when the user doesn't configure it. The agent sees an empty string, not an unset variable, and the phrasing "If set" could confuse the model into applying filtering with an empty threshold.

Suggestion: Change to: "If $REVIEW_FINDING_SEVERITY_THRESHOLD is set to a non-empty value, use that as the minimum severity. When unset or empty, treat the threshold as low."

Flagged by 1/4 review agents (Claude) — single-agent finding

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 7c24049. Changed to "If set to a non-empty value" with explicit "When unset or empty, treat the threshold as low."

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 7c24049. Prompt now reads "set to a non-empty value... When unset or empty, treat the threshold as low."

Comment on lines +21 to +22
FILTERED_RESULT="" MODIFIED_RESULT=""
trap 'rm -f "${FILTERED_RESULT:-}" "${MODIFIED_RESULT:-}"' EXIT

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[low] cleanup-gap — DOWNGRADE_RESULT temp file not in trap

DOWNGRADE_RESULT is created at line 123 via mktemp but not declared in the trap here. It's cleaned up indirectly via FILTERED_RESULT="${DOWNGRADE_RESULT}" reassignment at line 127, which is subtle — a future maintainer may not realize the cleanup depends on that reassignment.

Suggestion: Moot after rebase (should use CLEANUP_FILES+=() from main), but if the current pattern is kept: add DOWNGRADE_RESULT="" to line 21 and include it in the trap.

Flagged by 2/4 review agents (Claude, Codex) — consensus

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Moot after rebase — DOWNGRADE_RESULT now uses CLEANUP_FILES+=() like everything else.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Moot after the rebase — DOWNGRADE_RESULT now uses CLEANUP_FILES+=() at line 126.

ralphbean and others added 8 commits June 18, 2026 13:46
Every agent doc now has a ### Variables subsection under
"Configuration and extension" for consistency per ADR 0047.
Agents with no config vars state "None."

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
Adds the Variables subsection to the review agent doc with the
REVIEW_FINDING_SEVERITY_THRESHOLD config var — minimum severity
for reported findings (default: low).

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
The agent doc linter now checks that every agent doc with a
"Configuration and extension" section also has a "### Variables"
subsection, per ADR 0047.

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
The review.env file now carries REVIEW_FINDING_SEVERITY_THRESHOLD
into the sandbox so the review agent can self-filter findings
below the configured severity.

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
The review agent prompt now reads REVIEW_FINDING_SEVERITY_THRESHOLD
and omits findings below the configured level from both narrative
and structured output. Default: low (suppresses info).

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
The post-review script now reads REVIEW_FINDING_SEVERITY_THRESHOLD
(default: low) and drops findings below that level from the result
JSON before posting. Defense-in-depth for the agent-side filtering.

Includes test cases for the filtering logic.

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
…filter

Address review feedback on the severity threshold feature:

- Post-script now downgrades request-changes/reject to comment when
  filtering removes all findings (prevents empty findings array that
  violates schema minItems: 1 constraint)
- Agent prompt changed from approve to comment as downgrade target —
  comment gets requires-manual-review label, which is the right safety
  posture for "had findings but all were below threshold"
- Validate REVIEW_FINDING_SEVERITY_THRESHOLD: warn and default to low
  on unrecognized values instead of silently mapping to low
- Lint check for ### Variables now verifies positional placement under
  ## Configuration and extension, not just existence anywhere
- Simplified docs/agents/review.md config instructions (removed
  confusing "set in two places" wording, added downgrade behavior)
- Three new test cases covering verdict-downgrade scenarios

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
- Agent prompt: mention reject alongside request-changes in downgrade
  instruction
- Agent prompt: clarify "if set to a non-empty value" instead of
  ambiguous "if set"
- docs/agents/review.md: mention reject in downgrade behavior

Rebase onto main already handled: CLEANUP_FILES pattern, label_actions
integration, empty-findings del for approve/comment, and
approve/comment test cases.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 18, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 6:01 PM UTC · Completed 6:14 PM UTC
Commit: 7c24049 · View workflow run →

@ralphbean
ralphbean requested a review from waynesun09 June 18, 2026 18:11

@fullsend-ai-review fullsend-ai-review 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.

See the review comment for full details.

# ---------------------------------------------------------------------------
REVIEW_FINDING_SEVERITY_THRESHOLD="${REVIEW_FINDING_SEVERITY_THRESHOLD:-low}"

case "$REVIEW_FINDING_SEVERITY_THRESHOLD" in

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] injection

The REVIEW_FINDING_SEVERITY_THRESHOLD value is interpolated unsanitized into a GHA ::warning:: workflow command in the invalid-value catch-all branch. If the environment variable contains embedded newlines, the echo output could inject additional GHA workflow commands. The same script already sanitizes label values before interpolation into workflow commands, establishing the expected pattern.

Suggested fix: Sanitize the value before interpolating it into the ::warning:: message — strip newlines, carriage returns, and :: delimiters, matching the sanitization already applied to label values later in the same script.

case "$REVIEW_FINDING_SEVERITY_THRESHOLD" in
info|low|medium|high|critical) ;;
*) echo "::warning::Invalid REVIEW_FINDING_SEVERITY_THRESHOLD='${REVIEW_FINDING_SEVERITY_THRESHOLD}', defaulting to 'low'"
REVIEW_FINDING_SEVERITY_THRESHOLD="low" ;;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] edge-case

severity_rank maps unrecognized severity values to rank 1 (same as low). The jq filter else 1 branch has the same behavior. In practice, findings come from a schema-validated JSON file where severity is constrained, so the risk is minimal.

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment agent/review Review agent component/harness Agent harness, config, and skills loading component/docs User-facing documentation and removed ready-for-merge All reviewers approved — ready to merge labels Jun 18, 2026

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review Squad — Round 3

Agents: 5 (Claude ×3, Gemini, Codex)
New findings posted: 1 (medium)
Already posted (skipped): injection in ::warning:: (posted by fullsend-ai-review bot, awaiting response)
False positives removed: 2 (empty array set -u from Codex — pre-existing code from main; request_changes vs request-changes mismatch from Gemini — also pre-existing)

Code is in good shape after two rounds of fixes. The one new medium finding is the lack of integration test coverage for the severity filtering path through the real post-review.sh.

Assisted-by: Claude (review), Gemini (review), Codex (review)

info) echo 0 ;;
low) echo 1 ;;
medium) echo 2 ;;
high) echo 3 ;;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[medium] test-gap — severity filter tests exercise mirrored logic, not production code

The severity_rank(), filter_findings_json(), and filter_and_downgrade() functions here are reimplementations of the production logic in post-review.sh. These tests pass — but they're testing their own copy, not the real script. If the production jq filter or severity_rank() diverges (e.g., a new severity level added to production but not here), these tests still pass while production behaves differently.

The integration tests at the bottom of this file (via run_label_test) do invoke the real post-review.sh, but none of them set REVIEW_FINDING_SEVERITY_THRESHOLD to a non-default value with below-threshold findings. A regression in the interaction between severity filtering and subsequent logic (protected-path checks, label-actions, fullsend post-review invocation) would not be caught.

Suggestion: Add at least one integration test that exports REVIEW_FINDING_SEVERITY_THRESHOLD=medium with a request-changes result containing only low-severity findings, then asserts: exit 0, action downgraded to comment, and requires-manual-review label applied.

Flagged by 3/5 review agents (Claude ×3) — strong consensus

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Added integration tests in 890e31d. Two new tests run the real post-review.sh with REVIEW_FINDING_SEVERITY_THRESHOLD=medium and a request-changes result with only low findings — asserts the downgrade to comment and the requires-manual-review label.

Add integration tests that exercise the production severity filtering
code path by running the real post-review.sh with
REVIEW_FINDING_SEVERITY_THRESHOLD=medium and request-changes results
containing only low-severity findings. Asserts the action is downgraded
to comment and the requires-manual-review label is applied.

Addresses round 3 review feedback on PR #2341.

Signed-off-by: Ralph Bean <rbean@redhat.com>
Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 18, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:00 PM UTC · Completed 9:14 PM UTC
Commit: 890e31d · View workflow run →

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM — all findings from 4 rounds of multi-agent review have been addressed.

The severity filtering implementation is correct: threshold validation with safe fallback, proper jq filtering pipeline, verdict downgrade to comment for request-changes/reject, CLEANUP_FILES integration, and comprehensive test coverage including integration tests against the real post-review.sh.

One minor open item from the prior round (unsanitized env var in ::warning:: at post-review.sh:81) — low practical risk since the variable is set by repo owners, not PR authors.

Assisted-by: Claude (review), Gemini (review), Codex (review)

@ralphbean
ralphbean added this pull request to the merge queue Jun 18, 2026
@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment and removed requires-manual-review Review requires human judgment labels Jun 18, 2026
Merged via the queue into main with commit a66ef9d Jun 18, 2026
21 of 24 checks passed
@ralphbean
ralphbean deleted the feat/review-severity-threshold branch June 18, 2026 21:30
@fullsend-ai-retro

fullsend-ai-retro Bot commented Jun 18, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ✅ Success · Started 9:35 PM UTC · Completed 9:44 PM UTC
Commit: 890e31d · View workflow run →

@fullsend-ai-retro

Copy link
Copy Markdown

Retro Analysis: PR #2341 — Review severity threshold

Workflow went well overall. The review agent and human reviewer caught real bugs across 5 review rounds, including a safety-critical issue (verdict downgrade to approve instead of comment), an empty findings array schema violation, and missing input validation. The author addressed all findings across 4 commits, resulting in a well-tested feature.

Timeline

  1. Round 1 (2026-06-16): Bot review caught logic error in verdict downgrade, test gaps, docs issues
  2. Round 2 (2026-06-17): Human reviewer independently flagged the same safety issue plus input validation and lint gaps
  3. Round 3 (2026-06-18): Bot re-review re-raised the same low-severity severity_rank edge case from round 1
  4. Round 4 (2026-06-18): Human reviewer found rebase conflict, empty findings for approve/comment actions, prompt-gap
  5. Round 5 (2026-06-18): Bot + human found workflow command injection risk and test gap (mirrored vs production logic)

Final review agent run (dispatch 27788837576) completed successfully in ~13 minutes.

Improvement opportunities (all covered by existing issues)

No new proposals filed — all identified patterns are already tracked by existing open issues.

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

Labels

agent/review Review agent component/docs User-facing documentation component/harness Agent harness, config, and skills loading requires-manual-review Review requires human judgment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants