Skip to content

feat(review-agent): skip review dispatch that cannot produce a review - #6587

Open
guyoron1 wants to merge 7 commits into
fullsend-ai:mainfrom
guyoron1:chore/review-routing-hygiene
Open

feat(review-agent): skip review dispatch that cannot produce a review#6587
guyoron1 wants to merge 7 commits into
fullsend-ai:mainfrom
guyoron1:chore/review-routing-hygiene

Conversation

@guyoron1

@guyoron1 guyoron1 commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Heyaa : )

This started from tallying dispatches where the review agent runs a full inference and cannot produce a useful review. Three cases, now skipped before dispatch:

  • Drafts — review runs once the PR is marked ready, not on every draft push.
  • fullsend-no-review label — mirroring the fullsend-no-fix label the fix agent already honours (ADR 0034 pattern).
  • Docs prose — every changed file matches docs/*.md minus docs/ADRs/**, with a notice in the job log and summary.

An explicit /fs-review bypasses all three.

The docs glob is deliberately narrow, because the obvious patterns are dangerous: docs/* would swallow the TypeScript under docs/.vitepress/ and the published docs/normative/** schema; *.md would swallow SKILL.md/AGENTS.md — markdown that is executable agent instruction, not prose; and a new ADR is among the things most worth reviewing.

Two things are never skippable: lockfiles (a lockfile-only diff can repoint a transitive dependency's resolved/integrity without touching package.json — supply-chain relevant, so Renovate PRs keep their review) and truncated file listings (GitHub caps /pulls/{n}/files at 3000 entries without erroring).

TestReviewRoutingSkips / TestReviewRoutingDocsSkip pin the routing conditions and assert the dangerous broad globs are absent — verified meaningful by reverting each guard and watching them fail. go test ./internal/scaffold/ and pre-commit run --all-files pass, including the ADR lint hooks.

Note: the ADR is 0096 — 0090 and 0091 were both taken upstream while this branch was in flight. Known follow-up, disclosed in the ADR: fullsend-no-review requires manual label creation; there's no /fs-review-stop command yet, unlike /fs-fix-stop.

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

1 similar comment
@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.

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown

Site preview

Preview: https://9baa9ce7-site.fullsend-ai.workers.dev

Commit: 4b3ef74e06183c650936336dcd26b8b00fd61b06

@codecov

codecov Bot commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@guyoron1

Copy link
Copy Markdown
Contributor Author

Implements #1715 (skip review dispatch when PR is in draft state). Adjacent to #2390 and #2809, which ask for the same family of pre-dispatch skips.

@guyoron1
guyoron1 force-pushed the chore/review-routing-hygiene branch from 05d40b9 to ef01fdd Compare September 1, 2026 11:56
@guyoron1
guyoron1 marked this pull request as ready for review September 1, 2026 12:57
@guyoron1
guyoron1 requested a review from a team as a code owner September 1, 2026 12:57
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Skip unnecessary automatic review dispatches

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Prevent automatic reviews for drafts, opted-out PRs, and documentation-prose-only diffs.
• Preserve explicit /fs-review, ADR, lockfile, and truncated-file-list review coverage.
• Mirror routing gates in scaffolds and add regression tests and operational documentation.
Diagram

graph TD
  A["Review trigger"] --> B{"Explicit /fs-review?"} -- "No" --> C{"Draft or blocked?"} -- "No" --> D["Review stage"] --> E{"Docs prose only?"} -- "No" --> F["Dispatch review"]
  B -- "Yes" --> F
  C -- "Yes" --> G["Skip dispatch"]
  E -- "Yes" --> H["Log skip notice"] --> G
Loading
High-Level Assessment

Centralized pre-dispatch filtering is the appropriate approach because it avoids inference cost while retaining an explicit escape hatch. Native path filters cannot safely express the ADR, lockfile, API-truncation, and command-bypass semantics, while suppressing review inside the agent would occur too late to prevent wasted dispatches.

Files changed (5) +309 / -11

Enhancement (1) +67 / -5
reusable-dispatch.ymlGate automatic review dispatches before agent execution +67/-5

Gate automatic review dispatches before agent execution

• Skips automatic review routing for drafts and PRs carrying 'fullsend-no-review', while leaving explicit '/fs-review' commands unaffected. Adds conservative changed-file inspection that suppresses documentation-prose-only reviews but fails open for API errors, empty or truncated listings, ADRs, lockfiles, and other files.

.github/workflows/reusable-dispatch.yml

Tests (1) +80 / -0
workflow_call_alignment_test.goPin review-routing skip contracts +80/-0

Pin review-routing skip contracts

• Adds regression coverage for draft and label gates across both dispatch workflows, including the explicit-command bypass. Verifies the per-repo documentation check uses narrow safe patterns, records skips, and does not exempt lockfiles.

internal/scaffold/workflow_call_alignment_test.go

Documentation (2) +149 / -1
0096-skip-provably-unnecessary-review-dispatch.mdRecord the unnecessary-review dispatch decision +137/-0

Record the unnecessary-review dispatch decision

• Documents the three skip conditions, their safety boundaries, and the explicit-command escape hatch. It also records per-repo versus scaffold scope and the deferred '/fs-review-stop' follow-up.

docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md

review.mdDocument automatic review skips and control label +12/-1

Document automatic review skips and control label

• Explains draft, 'fullsend-no-review', and documentation-only skip behavior for users. Adds the new control label and clarifies that '/fs-review' always remains available.

docs/agents/review.md

Other (1) +13 / -5
dispatch.ymlMirror draft and label routing gates in scaffold +13/-5

Mirror draft and label routing gates in scaffold

• Threads draft status into generated per-org dispatch workflows and applies the same automatic routing gates for drafts and 'fullsend-no-review'. Updates the workflow size allowance for the added routing logic.

internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml

@qodo-code-review

qodo-code-review Bot commented Sep 1, 2026

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Action required

1. Planned command lacks issue link 📜 Skill insight ≡ Correctness
Description
The not-yet-implemented /fs-review-stop command is documented as follow-up work without a `>
**Planned:**` callout or issue link. Readers cannot trace the planned feature and may mistake the
prose for an informal commitment.
Code

docs/agents/review.md[61]

+| `fullsend-no-review` | Prevents automatic (bot-triggered) review runs on this PR. Mirrors the [fix agent](fix.md)'s `fullsend-no-fix` label. Explicit `/fs-review` commands are unaffected. Currently applied manually (no `/fs-review-stop` command yet — see [ADR 0096](../ADRs/0096-skip-provably-unnecessary-review-dispatch.md)). |
Relevance

●●● Strong

The documentation explicitly describes an unimplemented feature, making the required planned callout
and issue link a straightforward compliance fix.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 1062082 requires every planned feature mention to use a > **Planned:** blockquote and include
an issue link. The new control-label documentation states that /fs-review-stop does not exist yet
but provides neither.

docs/agents/review.md[61-61]
docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[74-82]
Skill: writing-user-docs

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 guide mentions the unimplemented `/fs-review-stop` feature without the required planned-feature callout and tracking link.

## Issue Context
Create or reference the relevant issue and use the exact `> **Planned:**` blockquote format.

## Fix Focus Areas
- docs/agents/review.md[61-61]
- docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[74-82]

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


2. ADR links outside Context 📜 Skill insight ⚙ Maintainability
Description
The related ADR cross-references are placed between Status and Context rather than inside the
Context section. This also introduces a free-standing block that does not follow the ADR template's
section order.
Code

docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[R23-26]

+Builds on [ADR 0034](0034-centralized-shim-routing-via-dispatch.md)
+(centralized dispatch routing, and the `fullsend-no-fix` label pattern this
+ADR mirrors) and [ADR 0054](0054-require-authorization-on-all-agent-dispatch-paths.md)
+(authorization on all agent dispatch paths — unaffected by this change).
Relevance

●●● Strong

Recent ADR reviews accepted restructuring and improving cross-reference placement and format.

PR-#6083
PR-#5798

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 1062094 requires related ADR references in Context, while rule 1062099 requires the template
structure. The references are inserted after Status and before the ## Context heading.

docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[19-28]
docs/ADRs/0000-adr-template.md[14-25]
Skill: writing-adrs

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

## Issue description
Related ADR references appear outside the Context section and disrupt the required template structure.

## Issue Context
Preserve the references but incorporate them concisely into the Context section.

## Fix Focus Areas
- docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[19-30]

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


3. Accepted ADR lacks architecture update ✓ Resolved 📜 Skill insight ⚙ Maintainability
Description
ADR 0096 is newly accepted, but this PR does not modify docs/architecture.md with a linked
Decided: entry. The accepted routing decision therefore is not reflected in the repository's
living architecture overview.
Code

docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[R2-3]

+title: "96. Skip provably unnecessary review dispatch"
+status: Accepted
Relevance

●●● Strong

Recent ADR precedent accepts adding architecture cross-references when accepted decisions introduce
repository-wide behavior.

PR-#5016
PR-#6083

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rules 1062062 and 1062100 require docs/architecture.md to be updated whenever a new ADR is
accepted. ADR 0096 declares status: Accepted, while no docs/architecture.md modification appears
in the PR diff.

Rule 1062062: Update architecture overview and problem docs when ADR is accepted
docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[1-3]
Skill: writing-adrs

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 new Accepted ADR has no corresponding architecture overview update.

## Issue Context
Add a surgical `Decided:` entry under the dispatch-related component and link ADR 0096; update related problem documents where the decision resolves an open question.

## Fix Focus Areas
- docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[1-21]
- docs/architecture.md[46-60]
- docs/problems/agent-architecture.md[1-1]
- docs/problems/code-review.md[1-1]

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


View high (3)
4. Files request uses POST ✓ Resolved 🐞 Bug ≡ Correctness
Description
Adding -F per_page=100 makes gh api use POST unless --method GET is specified, but the
pull-request files endpoint is a GET operation. The request therefore enters the fail-open handler
without setting skipped=true, so documentation-only PRs continue dispatching reviews.
Code

.github/workflows/reusable-dispatch.yml[555]

+          FILES=$(gh api "repos/${SOURCE_REPO}/pulls/${PR_NUMBER}/files" --paginate -F per_page=100 --jq '.[].filename') || {
Relevance

●●● Strong

Concrete GitHub CLI method bug causes fail-open behavior; workflow correctness findings are
typically accepted.

PR-#6167
PR-#1688

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new command supplies a form field without forcing GET, and its failure branch exits without
producing the skip output. The review is suppressed only when the later classification writes
skipped=true.

.github/workflows/reusable-dispatch.yml[555-558]
.github/workflows/reusable-dispatch.yml[576-580]
.github/workflows/reusable-dispatch.yml[111-113]

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 changed-files request supplies `-F per_page=100` without explicitly selecting GET, causing `gh api` to send a POST request and making the documentation-only skip ineffective.

## Issue Context
The existing failure handler intentionally continues review dispatch when the API call fails, so this error does not fail the workflow but prevents every intended docs-only skip.

## Fix Focus Areas
- .github/workflows/reusable-dispatch.yml[555-558]
- internal/scaffold/workflow_call_alignment_test.go[653-654]

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


5. ADR records three decisions 📜 Skill insight ⚙ Maintainability
Description
ADR 0096 separately decides draft suppression, label-based suppression, and documentation-only
suppression. These are three independently applicable routing decisions and must not be recorded as
one ADR.
Code

docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[R54-56]

+Three additive skips in `reusable-dispatch.yml`'s "Determine stage" step.
+
+### 1. Draft skip
Relevance

●● Moderate

The rule is explicit, but historical evidence does not clearly establish rejection or acceptance of
combining related routing decisions.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 1062089 requires exactly one decision per ADR. The Decision section explicitly introduces
Three additive skips and then defines three separately numbered policies.

docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[52-56]
docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[65-84]
Skill: writing-adrs

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

## Issue description
ADR 0096 records three distinct review-routing decisions in one Decision section.

## Issue Context
Each ADR must record exactly one decision; create separate ADRs with their own context and consequences.

## Fix Focus Areas
- docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[52-112]

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


6. Renames bypass prose filter ✓ Resolved 🐞 Bug ≡ Correctness
Description
The API query extracts only each file's new filename, so renaming an ADR or executable VitePress
source into a docs/*.md path is classified as prose-only. Such a PR is skipped even though it
removes or relocates content the change explicitly declares review-worthy.
Code

.github/workflows/reusable-dispatch.yml[555]

+          FILES=$(gh api "repos/${SOURCE_REPO}/pulls/${PR_NUMBER}/files" --paginate -F per_page=100 --jq '.[].filename') || {
Relevance

●● Moderate

Rename handling is a credible correctness gap, but no closely matching historical precedent confirms
team treatment.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The query discards status and previous_filename, after which the loop accepts any destination
matching docs/*.md. The repository explicitly excludes ADRs and VitePress TypeScript from the
skip, and docs/.vitepress/config.ts confirms these paths contain executable implementation code.

.github/workflows/reusable-dispatch.yml[555-575]
.github/workflows/reusable-dispatch.yml[543-551]
docs/.vitepress/config.ts[1-18]
docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[92-100]

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 documentation-only filter ignores pull-file rename metadata and checks only the destination filename. A substantive file renamed into a matching Markdown path can therefore suppress review.

## Issue Context
Pull-file rename records include the old path separately as `previous_filename`. Either reject renamed files conservatively or classify both old and new paths, while preserving the 3000-file truncation safeguard.

## Fix Focus Areas
- .github/workflows/reusable-dispatch.yml[555-575]
- internal/scaffold/workflow_call_alignment_test.go[645-669]

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



Remediation recommended

7. Per-org changes not disclosed ✓ Resolved 📘 Rule violation § Compliance
Description
The PR modifies routing in the deprecated per-org scaffold, but its description does not explicitly
disclose that deprecated mode is affected or reference ADR 0044. The required deprecation warning is
therefore absent from the review context.
Code

internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml[R201-204]

+                  if [[ "${EVENT_ACTION}" == "ready_for_review" || "${PR_IS_DRAFT}" != "true" ]] && ! has_label "fullsend-no-review" "${PR_LABELS}"; then
+                    if [[ "${PR_USER_LOGIN}" =~ \[bot\]$ ]] || is_event_actor_authorized "${PR_USER_LOGIN}" triage; then
+                      STAGE="review"
+                    fi
Relevance

●●● Strong

The PR changes deprecated scaffold routing and omits the required explicit ADR 0044 disclosure from
its description.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 2795055 requires any change touching per-org installation content to be explicitly called out
in the PR description with an ADR 0044 reference. The scaffold routing is modified, and the new ADR
itself identifies that scaffold as the deprecated per-org mode, but the supplied PR description
contains no such disclosure.

Rule 2795055: Flag and avoid changes to deprecated per-org installation mode content (ADR 0044)
internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml[196-204]
docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[126-134]

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 PR changes deprecated per-org installation scaffold behavior without the required PR-description disclosure.

## Issue Context
Update the PR description to state that deprecated per-org installation behavior is touched and explicitly reference ADR 0044.

## Fix Focus Areas
- internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml[196-210]
- docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[126-134]

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


8. Consequences use multiple sentences ✓ Resolved 📜 Skill insight ⚙ Maintainability
Description
Several Consequences bullets contain multiple sentences and extended implementation rationale. The
required format is 3–5 bullets with exactly one sentence per bullet.
Code

docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[R120-123]

+- `/fs-review` remains a complete escape hatch for all three skips. A
+  maintainer can always force a review on a draft, a no-review-labeled PR, or
+  a prose-only PR by commenting.
+- `fullsend-no-review` currently requires manual label creation and
Relevance

●●● Strong

One-sentence-per-bullet is a deterministic ADR formatting requirement, and the bullets visibly
violate it.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 1062091 requires each Consequences bullet to be one sentence. For example, the escape-hatch
bullet consists of two sentences, and later bullets contain additional multi-sentence detail.

docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[120-125]
docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[126-134]
Skill: writing-adrs

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 ADR Consequences bullets contain multiple sentences.

## Issue Context
Keep 3–5 bullets and rewrite every bullet as one concise sentence.

## Fix Focus Areas
- docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[114-137]

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



Informational

9. ADR Context is oversized 📜 Skill insight ⚙ Maintainability
Description
The Context contains an introductory paragraph, three substantial numbered blocks, and a concluding
paragraph. This exceeds the required 1–3 short paragraphs and restates operational detail at length.
Code

docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[R34-36]

+There is no way to skip a review that provably shouldn't run:
+
+1. **Draft PRs.** Opening a draft or pushing to it dispatches a review
Relevance

● Weak

The team previously rejected shortening an oversized ADR Context section despite the same template
requirement.

PR-#2582

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 1062090 requires 1–3 short Context paragraphs. ADR 0096 uses an introductory block, three
multi-line numbered explanations, and a separate conclusion.

docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[28-39]
docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[40-50]
Skill: writing-adrs

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 ADR Context exceeds the required length and paragraph count.

## Issue Context
Summarize the motivation in no more than three short paragraphs and link the related problem documents for detail.

## Fix Focus Areas
- docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[28-50]

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


10. Workflow size limit weakened 📘 Rule violation ▣ Testability
Description
The per-file workflow limit is raised from 612 to 620, allowing the newly enlarged workflow to pass
instead of extracting its inline logic. This weakens an existing linter specifically guarding
against workflow logic accumulation.
Code

internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml[2]

+# lint-workflow-size: max-lines=620
Relevance

● Weak

Recent, closely matching findings explicitly rejected raising workflow-size limits instead of
refactoring scaffold workflow logic.

PR-#5578
PR-#5244

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 1062076 prohibits weakening linters to make changes pass. The modified override raises the
accepted maximum, while the linter documentation identifies extraction into scripts as the
remediation for workflow growth.

Rule 1062076: Do not weaken tests or linters to make failures pass
internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml[2-2]
hack/lint-workflow-size[45-57]

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-size lint limit was raised to accommodate additional inline workflow logic.

## Issue Context
The size linter directs oversized workflow logic into reusable scripts rather than increasing the per-file override.

## Fix Focus Areas
- internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml[1-2]
- hack/lint-workflow-size[45-57]

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


11. ADR exceeds 100 lines 📜 Skill insight ⚙ Maintainability
Description
ADR 0096 contains 123 content lines after its frontmatter, exceeding the 100-line maximum. Its
extensive implementation detail should be reduced or moved to supporting documentation.
Code

docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[R135-137]

+- Adds one paginated `gh api` call per review dispatch to fetch the
+  changed-file list — incurred only when a review would otherwise run, so it
+  never adds cost on top of a dispatch that was going to happen anyway.
Relevance

● Weak

A closely matching ADR length-limit finding was explicitly rejected by the team.

PR-#2582

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 1062092 limits ADR content excluding frontmatter to 100 lines. The frontmatter ends at line 13
and content continues through line 137, producing 123 content lines.

docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[13-15]
docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[135-137]
Skill: writing-adrs

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

## Issue description
ADR 0096 exceeds the maximum permitted content length.

## Issue Context
There are 123 lines after the frontmatter; retain the decision record and move implementation detail elsewhere.

## Fix Focus Areas
- docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[15-137]

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


Grey Divider

Context sources
✅ Compliance rules (platform): 72 rules
Review mode: ⚖️ Balanced

Grey Divider

Tip of the day
💡 Did you know, you can turn these tips off under Display preferences

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +54 to +56
Three additive skips in `reusable-dispatch.yml`'s "Determine stage" step.

### 1. Draft skip

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Adr records three decisions 📜 Skill insight ⚙ Maintainability

ADR 0096 separately decides draft suppression, label-based suppression, and documentation-only
suppression. These are three independently applicable routing decisions and must not be recorded as
one ADR.
Agent Prompt
## Issue description
ADR 0096 records three distinct review-routing decisions in one Decision section.

## Issue Context
Each ADR must record exactly one decision; create separate ADRs with their own context and consequences.

## Fix Focus Areas
- docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[52-112]

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

Comment thread docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md
Comment thread docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md Outdated
Comment on lines +23 to +26
Builds on [ADR 0034](0034-centralized-shim-routing-via-dispatch.md)
(centralized dispatch routing, and the `fullsend-no-fix` label pattern this
ADR mirrors) and [ADR 0054](0054-require-authorization-on-all-agent-dispatch-paths.md)
(authorization on all agent dispatch paths — unaffected by this change).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

7. Adr links outside context 📜 Skill insight ⚙ Maintainability

The related ADR cross-references are placed between Status and Context rather than inside the
Context section. This also introduces a free-standing block that does not follow the ADR template's
section order.
Agent Prompt
## Issue description
Related ADR references appear outside the Context section and disrupt the required template structure.

## Issue Context
Preserve the references but incorporate them concisely into the Context section.

## Fix Focus Areas
- docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[19-30]

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

Comment thread docs/agents/review.md
| `ready-for-merge` | The review agent approved the PR. No blocking findings. |
| `requires-manual-review` | The review agent found issues that require human judgment — it could not confidently approve or reject. |
| `rejected` | The review agent rejected the PR and the post-script closed it. |
| `fullsend-no-review` | Prevents automatic (bot-triggered) review runs on this PR. Mirrors the [fix agent](fix.md)'s `fullsend-no-fix` label. Explicit `/fs-review` commands are unaffected. Currently applied manually (no `/fs-review-stop` command yet — see [ADR 0096](../ADRs/0096-skip-provably-unnecessary-review-dispatch.md)). |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

8. Planned command lacks issue link 📜 Skill insight ≡ Correctness

The not-yet-implemented /fs-review-stop command is documented as follow-up work without a `>
**Planned:**` callout or issue link. Readers cannot trace the planned feature and may mistake the
prose for an informal commitment.
Agent Prompt
## Issue description
The guide mentions the unimplemented `/fs-review-stop` feature without the required planned-feature callout and tracking link.

## Issue Context
Create or reference the relevant issue and use the exact `> **Planned:**` blockquote format.

## Fix Focus Areas
- docs/agents/review.md[61-61]
- docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md[74-82]

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

Comment thread internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml
Comment thread .github/workflows/reusable-dispatch.yml Outdated
Comment thread .github/workflows/reusable-dispatch.yml Outdated

@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 sweep — 5 MEDIUM findings, 4 posted inline below; the one below targets a file not in this diff.

MEDIUM — User-facing bugfix guide still says closing the PR is the way to stop review (docs/guides/user/bugfix-workflow.md:169)

Verified on the PR head: docs/guides/user/bugfix-workflow.md:169 reads 'review is triggered automatically by PR events (pull_request_target), so closing the PR is the way to stop review dispatch'. This PR adds the fullsend-no-review label specifically as the lever to stop automatic review, and adds draft/docs-only skips, but the guide is not in the diff. The trigger descriptions at :83 ('review agents take over automatically'), :130/:134 ('Triggered by: pull_request_target events (PR opened, push to PR branch, ...)') and :183 ('Push commits ... the review agents will re-review') likewise do not mention the new skip exceptions. docs/agents/review.md was updated; this user-facing guide was not.

Suggestion: Update the 'Stopping automation' bullet at :169 to name the fullsend-no-review label as the way to stop automatic review (closing the PR is no longer the only option), and add a short note to the Stage 3 trigger description (:134) that drafts, fullsend-no-review-labeled PRs and docs-prose-only diffs are skipped unless /fs-review is used.

Comment thread .github/workflows/reusable-dispatch.yml Outdated
[[ -z "$f" ]] && continue
case "$f" in
docs/ADRs/*) skippable=false; break ;;
docs/*.md) ;;

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.

MEDIUMdocs/*.md skip swallows normative contract and contributing markdown the ADR says it protects

bash case globs match /, so the docs/*.md) ;; arm treats every nested markdown file under docs/ as skippable prose. Verified with the step's exact case block on the PR head: docs/normative/normalized-event/v1/README.md (NormalizedEvent v1 routing-input spec, ADR 0061), docs/normative/normalized-event/v1/jira-poll-adapter.md, docs/normative/prescript-output/v1/README.md (pre-script output contract) and docs/contributing/workflow-contracts.md (the sync contract governing the file this PR edits) all resolve to SKIP. ADR 0096 line 95 argues docs/* is dangerous precisely because it would swallow docs/normative/**, but the chosen pattern still swallows the normative prose specs alongside the JSON schema. A PR changing the documented meaning of a NormalizedEvent field without touching the JSON would receive no automatic review. docs/agents/review.md:47 documents only the ADR exclusion.

Suggestion: Add explicit non-skippable arms before docs/*.md: docs/normative/*|docs/contributing/*) skippable=false; break ;; (or invert to an allow-list of low-stakes prose directories such as docs/guides/*). Update ADR 0096 §3 and docs/agents/review.md:47 to state exactly which directories are excluded, and extend TestReviewRoutingDocsSkip to pin the new arms.


- Fewer wasted review runs: drafts, `fullsend-no-review`-labeled PRs, and
documentation-prose-only PRs no longer trigger automatic review dispatch,
reducing inference cost with no loss of coverage — nothing that would have

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 — ADR 0096 asserts 'no loss of coverage' without evidence or threat-model evaluation

The Consequences bullet states 'no loss of coverage — nothing that would have been usefully reviewed is skipped' as settled fact, and the PR body says the work started from 'tallying dispatches', but no tally, cost figure, or sample of skipped PRs appears in the PR or ADR. Verified counter-evidence on head: (a) the glob already skips docs/normative/*.md and docs/contributing/*.md contract prose (see the comment at reusable-dispatch.yml:572); (b) the review agent's charter in fullsend-ai/agents docs/review.md lists 'docs currency' as a review dimension, and docs/agents/*.md + docs/contributing/*.md are the user-facing product surface — a docs-only follow-up to docs/agents/review.md (the page this PR edits) would itself be skipped; (c) docs/contributing/design-decisions.md:5 ranks external prompt injection as the top threat, and docs-only PRs are a low-friction path to land instruction-like prose that later agents read, with the automated reviewer disabled for exactly that path. The draft and label skips are self-evidently zero-loss; the docs skip is a judgement call shipped as fact. This is distinct from the existing threads on ADR format/length.

Suggestion: Either attach the dispatch tally (count and share of docs-only review runs over a stated window) and record in Consequences that the trade-off was evaluated against the injection threat model, or narrow the skip to an allow-list of low-stakes prose paths. In both cases soften the 'no loss of coverage' wording to describe the actual trade-off.

Comment thread docs/agents/review.md
The `/fs-review` command does not accept arguments. The review agent also runs automatically when a PR is opened,
synchronized (new commits pushed), or moved out of draft by a user with triage-level repository permission or higher.

### Automatic skips

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 — fullsend-ai/agents docs/review.md 'Triggers' section is now stale (needs a companion PR in the agents repo)

Verified against the live fullsend-ai/agents main: docs/review.md 'Triggers' states the review agent runs automatically when 'A PR/MR is opened' and 'New commits are pushed to a PR/MR (synchronized)', with no draft, fullsend-no-review, or docs-only caveat, and its Control labels table has no fullsend-no-review row. After this PR those statements are wrong for drafts, labeled PRs and docs-prose-only PRs. That document lives in fullsend-ai/agents (ADR 0058 split) and cannot be fixed from this repo; the PR and ADR reference no companion change.

Suggestion: Open a companion PR in fullsend-ai/agents updating docs/review.md Triggers (draft exclusion, fullsend-no-review label, docs-only skip with the exact exclusions) and its Control labels table, and link it from this PR and ADR 0096. Do not attempt to fix it via internal/scaffold/ here.

assert.Contains(t, s, "id: docs-lockfile-check")
assert.Contains(t, s, "steps.docs-lockfile-check.outputs.skipped != 'true'",
"job-level stage output must account for the docs skip")
assert.Contains(t, s, `if: steps.route.outputs.stage == 'review' && steps.role-check.outputs.skipped != 'true' && steps.agent-check.outputs.skipped != '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.

MEDIUMTestReviewRoutingDocsSkip pins substrings, not behaviour: passed green against a 404ing step and does not lock the /fs-review bypass guard

Verified on head. (1) The if: assertion at :651 checks only if: steps.route.outputs.stage == 'review' && steps.role-check.outputs.skipped != 'true' && steps.agent-check.outputs.skipped != 'true' and omits the trailing && github.event_name != 'issue_comment'; if that guard were removed the test still passes and the docs skip would apply to explicit /fs-review runs, contradicting ADR 0096's escape-hatch guarantee. (2) The gh-api pin at :653 matches the current command, which (per the existing thread at reusable-dispatch.yml:555) issues a POST and 404s, so the test passed CI while the feature it guards can never fire. (3) The two NotContains assertions at :663/:665 check literal strings (*.md|docs/*, package-lock.json|yarn.lock) that no plausible regression would produce, so they are trivially true. The PR body's claim that the tests were 'verified meaningful by reverting each guard' holds for TestReviewRoutingSkips' regexes but not for this function. hack/lint-workflow-size's own error text says logic belongs in scripts/ where it can be tested.

Suggestion: Add an explicit assertion that the docs-lockfile-check if: includes github.event_name != 'issue_comment'. When fixing the POST bug (the pin at :653 must change anyway), extract the step body to .github/scripts/check-docs-only-diff.sh and add a shell test that runs it with a stubbed gh on PATH covering: docs-only listing, a docs/ADRs/ entry, a docs/normative/ entry, a 3000-line listing, and a gh failure (fail-open). Keep the YAML test as a wiring check only, and cite a real dispatch run where the skip fired.

@guyoron1

guyoron1 commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

@waynesun09 Thanks. Three commits: c0f5bf7, 4d1defb, 8696c93.

The skip now matches docs/ADRs/, docs/normative/, docs/contributing/, docs/reference/ and docs/.vitepress/ before docs/*.md. Two bugs went with it: -F per_page=100 made the call a POST, which 404s, so once merged it would have fail-opened every run; and renames were judged only on the new path.

TestReviewRoutingDocsSkip is now a wiring check pinning the whole if:, issue_comment guard included. Rather than extract the body to .github/scripts/, I run the shipped run: itself against a stubbed gh. Each guard fails a case when reverted.

With no tally, ADR 0096 states what a skipped diff can contain and the residual risk instead of claiming none. The guide names fullsend-no-review. docs/review.md in fullsend-ai/agents needs its own PR there.

@guyoron1
guyoron1 force-pushed the chore/review-routing-hygiene branch from 8696c93 to 4b3ef74 Compare September 6, 2026 08:09
@guyoron1

guyoron1 commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto main at 42b3b710 and force-pushed. Same four commits with the same patches; the rebase only picks up the HTTPS submodule URL that main switched to in 5793f6e8, which is what the red Build Site job was failing on. The branch still carried the git@ URL for the experiments submodule, and the runner cannot clone that over SSH.

New commit ids, in order:

  • d0cb34a2 feat(review-agent): skip review dispatch that cannot produce a review
  • ed8ff19e fix(dispatch): protect contract markdown from the docs-only skip
  • 9cba71f2 test(scaffold): run the docs skip instead of matching its text
  • 4b3ef74e docs(review): record what the review skips actually do

These replace the ids in my previous comment. Nothing else changed.

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

Automated review sweep — 4 findings inline (2 HIGH, 2 MEDIUM).

Comment thread .github/workflows/reusable-dispatch.yml Outdated
while IFS= read -r f; do
[[ -z "$f" ]] && continue
case "$f" in
docs/ADRs/*|docs/normative/*|docs/contributing/*|docs/reference/*|docs/.vitepress/*)

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 — Docs-prose skip assumes VitePress markdown is inert outside docs/.vitepress/ — it isn't

The docs-only skip treats every docs/*.md file as inert prose except five protected prefixes (docs/ADRs/, docs/normative/, docs/contributing/, docs/reference/, docs/.vitepress/). ADR 0096 (line 102) frames docs/.vitepress/ as 'executable' as if that's the only executable surface, but VitePress compiles every markdown file under the configured srcDir (not just .vitepress/), and any of those files may contain a root-level <script setup> block that runs Vue/JS at build time. Verified on PR head: docs/.vitepress/config.ts's srcExclude (line 194) only excludes **/agents/icons/** and **/testing/** — the rest of docs/ (including docs/v/, docs/guides/, docs/agents/**) is compiled. docs/v/index.md is a live counterexample: it is not under any protected prefix, matches the docs/*.md skippable arm, and already contains <script setup> importing a third-party package (@lando/vitepress-theme-default-plus). A future PR editing such a page's script, or adding a head: frontmatter key that injects a <script src=...> tag, would be classified as 'documentation prose' and skip the review agent — even though docs/contributing/design-decisions.md ranks external prompt injection as the top threat the review agent exists to catch.

Suggestion: Either reject any docs/*.md diff that introduces a <script, <style>, or head: frontmatter key (detectable from the diff content, not just the path), or replace the denylist-of-five-prefixes approach with an allowlist of directories verified to be pure prose. At minimum, update ADR 0096's threat model to acknowledge VitePress markdown is not inert outside .vitepress/, and add docs/v/** (and any other page using <script setup> or custom head:) to the protected set.

pull-requests: read
outputs:
stage: ${{ steps.role-check.outputs.skipped != 'true' && steps.agent-check.outputs.skipped != 'true' && steps.pr-check.outputs.skipped != 'true' && steps.route.outputs.stage || '' }}
stage: ${{ steps.role-check.outputs.skipped != 'true' && steps.agent-check.outputs.skipped != 'true' && steps.pr-check.outputs.skipped != 'true' && steps.docs-lockfile-check.outputs.skipped != 'true' && steps.route.outputs.stage || '' }}

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 — A skipped review dispatch never clears a stale ready-for-merge label

docs/architecture.md's Coordinator merge algorithm section states the invariant: 'each review run start (including push-triggered re-review) clears ready-for-merge together with ready-for-review so merge approval is never stale after new commits.' Verified on head that this clearing is not implemented anywhere in the workflow YAML — it happens only as part of the 'Run review agent' step itself (line ~1001), which is gated by steps.route.outputs.stage (line 111), and that output is forced empty whenever role-check, agent-check, pr-check, or the new docs-lockfile-check report skipped=true. So on a docs-only push, or a push to a PR just labeled fullsend-no-review, to a PR that already carries ready-for-merge from an earlier round, the job never starts and the label-clearing logic embedded in the review agent run never fires — the stale ready-for-merge label survives unreviewed new commits. The PR's own doc edit to docs/guides/user/bugfix-workflow.md (line 101) implicitly concedes this: it changes 'ready-for-merge is never stale — it always reflects the current PR head' to 'unless the push lands on a draft, on a fullsend-no-review-labeled PR, or is documentation prose only. Short of those, ready-for-merge is never stale' — narrowing the guarantee without saying what happens in the carved-out cases. GitHub's own draft-merge block partially covers the draft case, but not the docs-only or fullsend-no-review cases.

Suggestion: On the docs-lockfile-check and fullsend-no-review skip paths (and ideally the draft path too, for defense in depth), explicitly strip ready-for-merge and ready-for-review via a cheap gh api ... DELETE /labels/{name} step before short-circuiting, so the label can never point at a superseded head. Update docs/guides/user/bugfix-workflow.md and docs/architecture.md to describe the actual (fixed) behavior once this lands.

Comment thread .github/workflows/reusable-dispatch.yml Outdated
while IFS= read -r f; do
[[ -z "$f" ]] && continue
case "$f" in
docs/ADRs/*|docs/normative/*|docs/contributing/*|docs/reference/*|docs/.vitepress/*)

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 — Protected-docs prefix list omits docs/architecture.md and docs/cli/**, both load-bearing

The five protected prefixes (docs/ADRs/, docs/normative/, docs/contributing/, docs/reference/, docs/.vitepress/) do not cover docs/architecture.md (the living architecture overview contributors are told to keep current — and which this very PR should have updated but didn't) or docs/cli/**.md (the manually maintained CLI contract; verified 7 files exist under docs/cli/ on PR head: README.md, agent.md, github.md, inference.md, mint.md, repos.md, run.md). Because bash case glob matching treats * as matching /, both docs/architecture.md and docs/cli/agent.md fall through to the bare docs/*.md) ;; skippable arm and would silently skip automatic review today. This is a distinct, narrower gap from the already-flagged normative/contributing coverage (a prior review comment on this PR at reusable-dispatch.yml:579 already got docs/normative/ and docs/contributing/ added as protected prefixes — architecture.md and docs/cli/ were not part of that fix and remain unprotected).

Suggestion: Add docs/architecture.md and docs/cli/ to the protected-prefix case arms (or move to an allowlist of low-risk directories such as docs/guides/, docs/problems/, docs/agents/ instead of a denylist over an open-ended docs/ tree).

review on a draft, a labeled PR, or a prose-only PR at any time.
- `fullsend-no-review` must be created and applied by hand until an
`/fs-review-stop` command exists, unlike `/fs-fix-stop`.
- The prose skip is per-repo only because the per-org mode is deprecated

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 — ADR 0096's scaffold-mirroring exemption for docs-lockfile-check is self-contradicting

ADR 0096's Consequences (lines 144-148) justify not mirroring docs-lockfile-check into internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml by saying it is 'per-repo only because the per-org mode is deprecated (ADR 0044) and docs/contributing/workflow-contracts.md scopes cross-mode sync to payload construction, stage routing, and secret threading — the draft and label checks, being stage routing, are mirrored into the scaffold as usual.' Verified on head: docs-lockfile-check is structurally identical to role-check/agent-check/pr-check — all four gate the job's stage output via the same steps.X.outputs.skipped != 'true' chain (reusable-dispatch.yml:111) — and role-check/agent-check/pr-check are confirmed present and mirrored in the scaffold's dispatch.yml (verified: id: role-check at line 406, id: agent-check at 429/430, id: pr-check at 249, with no docs-lockfile-check step between agent-check (ending line 452) and the next step (line 454) in the scaffold file). The ADR's own reasoning is circular: per-org being deprecated (ADR 0044) applies equally to the draft/label checks, yet those ARE mirrored 'as usual' despite the same deprecation fact — so deprecation cannot be what's actually driving the asymmetry, and workflow-contracts.md's text does not itself distinguish 'stage routing' from 'post-route gate' the way the ADR now claims.

Suggestion: Either mirror docs-lockfile-check into the scaffold dispatch.yml for consistency with its three sibling gates, or replace the current circular justification in ADR 0096 and docs/contributing/workflow-contracts.md with a rule that actually distinguishes 'this specific gate' from role-check/agent-check/pr-check/draft/label routing — e.g., ground it in something docs-lockfile-check alone requires (a paginated cross-API call not needed by per-org's config-driven routing) rather than the deprecation argument, which does not hold up against the mirrored siblings.

Three cases where the review agent is dispatched, runs a full inference,
and cannot say anything useful (ADR 0096).

Drafts no longer trigger an automatic review on open or push — the review
fires once the PR is marked ready. A fullsend-no-review label suppresses
automatic dispatch, mirroring the fullsend-no-fix label the fix agent
already honours. And a PR whose changed files are all documentation prose
under docs/ is skipped with a notice in the job summary.

That last pattern is deliberately narrow, because bash case globs match
'/': docs/* would swallow the TypeScript under docs/.vitepress and the
normative event schema, and a bare *.md would swallow skills/*/SKILL.md,
AGENTS.md and CLAUDE.md — markdown that is executable agent instruction
rather than prose. ADRs are excluded because a new ADR is among the
things most worth reviewing, and lockfiles are not skippable at all:
npm resolves from package-lock.json, so a lockfile-only diff can repoint
a transitive dependency without touching package.json. A truncated file
listing never skips either, since GitHub caps the files endpoint at 3000
entries and stops paginating without erroring.

An explicit /fs-review bypasses all three skips.

The draft and label checks are mirrored into the per-org scaffold, which
docs/contributing/workflow-contracts.md requires for stage routing. The
docs skip is not: that installation mode is deprecated (ADR 0044) and the
requirement is scoped to routing, payload construction, and secret
threading, none of which this step is.

Rebased onto current main; renumbered the new ADR from 0091 to 0096 to
avoid a collision with 0091-per-agent-runtime-model-effort.md, which
landed upstream after this branch was cut.

Signed-off-by: guy oron <goron@redhat.com>
bash case globs match '/', so the docs/*.md arm reached every nested
markdown file under docs/ — including docs/normative/, whose prose specs
are the contracts other repos build payloads against, and
docs/contributing/workflow-contracts.md, which governs this workflow. ADR
0096 argued docs/* was dangerous for exactly that reason and then shipped
a pattern with the same reach. The contract directories now match first:
docs/ADRs/, docs/normative/, docs/contributing/, docs/reference/ and
docs/.vitepress/.

Two other ways a non-prose change could have slipped through the check.
-F per_page=100 makes gh api issue a POST, which the files endpoint
answers with a 404 — the step then took its fail-open path on every run,
so no PR was ever actually skipped; per_page moves into the query string.
And the listing named only each file's new path, so renaming a Go file to
docs/notes.md read as prose; previous_filename is now classified too.

Signed-off-by: guy oron <goron@redhat.com>
TestReviewRoutingDocsSkip pinned substrings, which is why it stayed green
while the step it guards could never fire: the gh-api assertion matched
the POST form of the call exactly. Its two NotContains assertions checked
literals no plausible regression would produce, and the if: assertion
omitted the issue_comment guard that keeps /fs-review a complete escape
hatch, so removing that guard would not have failed anything.

The wiring half now asserts the whole if: expression, guard included. The
decision itself is not observable from the YAML text, so a runtime test
parses the step's run: body out of the workflow and executes it against a
stubbed gh — the same shape as TestShimStopFixAuthorizationRuntime. It
covers prose-only, each protected prefix, markdown outside docs/, a
lockfile, a rename into docs/, a 3000-entry listing and an api failure,
and the stub refuses anything that is not a GET. Reverting any one of the
four guards fails at least one case.

Signed-off-by: guy oron <goron@redhat.com>
The ADR's Consequences claimed "no loss of coverage — nothing that would
have been usefully reviewed is skipped". That was asserted, not measured,
and after the prefix fix it is still not quite true: docs currency is one
of the review agent's own dimensions, and prose is where instruction-like
text a later agent reads would land, with external prompt injection at
the top of the threat order in docs/contributing/design-decisions.md. The
bullets now say which diffs stop being reviewed, what one can and cannot
contain, and where the residual risk sits — bounded by what the check can
see rather than by trust, with /fs-review always available. Decision §3
gains the protected prefixes, the rename handling and the GET.

docs/agents/review.md lists the exclusions rather than only ADRs. The
user guide said closing the PR was the way to stop review dispatch; the
fullsend-no-review label is, and it leaves /fs-review working. Stage 3
gains a "Not triggered when" line, and the three other places that
promised a review on every push now say otherwise.

The companion Triggers section in fullsend-ai/agents docs/review.md is
stale for the same reason and needs its own PR there — it cannot be
edited from this repo.

Signed-off-by: guy oron <goron@redhat.com>
The docs-prose skip classified by path alone and treated docs/ minus a
denylist of contract directories as inert. Neither held. VitePress
compiles every markdown page under docs/ into a Vue component — config.ts
srcExclude only drops icons and testing/ — so a page on a prose path can
carry a root-level <script setup> (docs/v/index.md already does), a
<style> block, a head: frontmatter key that injects tags, {{ }}
expressions evaluated during SSG, or bound attributes on raw HTML, and
still be classified as prose. And the denylist kept missing pages:
docs/architecture.md and docs/cli/**, both load-bearing, fell through to
the docs/*.md arm because case globs match '/'.

Prose is now an allowlist — docs/guides/, docs/problems/, docs/agents/,
docs/glossary.md — so a new directory is reviewed until someone lists it,
and each allowlisted page is read at the PR head and keeps its review if
it carries any of those constructs outside fenced or inline code, where
VitePress renders text verbatim. An unreadable page never skips, and a
removed page is not read. grep reads the whole page instead of -q: under
pipefail an early exit can SIGPIPE awk on a long page and turn a hit into
a miss. previous_filename moves to the last tsv column because read with
IFS=tab collapses an empty middle field.

The runtime test's gh stub now serves page bodies, and covers each
executable construct, markup inside code, an unreadable page, a removed
page, and the pages the allowlist deliberately leaves out.

Signed-off-by: guy oron <goron@redhat.com>
docs/architecture.md holds that every review run start clears
ready-for-merge together with ready-for-review, so merge approval is
never stale after new commits. That clearing lives in the review run
itself, so each skip this branch added — draft, fullsend-no-review,
documentation prose — left a ready-for-merge applied to an earlier head
standing on commits nobody reviewed. The guide had been narrowed to admit
as much instead of saying what happens.

A clear-stale-merge-labels job now runs on every pull_request_target
synchronize whose composite stage output is not review and removes both
labels; a 404 means the label was not there, anything else fails the job
so the stale label is visible. It is a job of its own so the route job,
which parses untrusted event data, keeps its read-only token; the
per-repo shim already grants the dispatch job issues and pull-requests
write. The scaffold gets the same script as the last step of its single
job, with its token widened the same way. As a side effect a push by an
actor below triage, which never routed, also clears the labels.

The alignment test pins the job's wiring and the route job's read-only
permissions, requires both workflows to run the same script, and runs
that script against a stub gh for the 200, 404 and 403 cases.

Signed-off-by: guy oron <goron@redhat.com>
ADR 0096 justified not mirroring docs-lockfile-check into the per-org
scaffold by per-org's deprecation, while mirroring the draft and label
skips "as usual" despite the same deprecation. role-check, agent-check
and pr-check are all mirrored too, and two of them post-date ADR 0044,
so neither the deprecation date nor "stage routing" separates the cases.

The rule that does is correctness parity versus optimisation: whatever
decides whether an event may dispatch, which stage it reaches, or which
labels it leaves behind is mirrored, because the two modes must agree on
that; a step that declines a dispatch that would be valid, to save
inference spend, is not, because a mode scheduled for removal gets no
optimisations. Recorded in the ADR and in workflow-contracts.md, where
the sync contract is defined.

Signed-off-by: guy oron <goron@redhat.com>
@guyoron1
guyoron1 force-pushed the chore/review-routing-hygiene branch from 4b3ef74 to b998b8d Compare September 10, 2026 12:45
@guyoron1

Copy link
Copy Markdown
Contributor Author

@waynesun09 thanks — every thread addressed; rebased onto current main and force-pushed.

09-08:

  • .github/workflows/reusable-dispatch.yml:577 (VitePress markdown not inert) — fixed in d79011a. Prose is an allowlist (docs/guides/, docs/problems/, docs/agents/, docs/glossary.md) and each page is read at the PR head: <script>/<style>, head:, {{ }} or a bound attribute outside code keeps the review.
  • .github/workflows/reusable-dispatch.yml:111 (stale ready-for-merge) — fixed in 906ffdb. A clear-stale-merge-labels job strips both labels on every skipped synchronize; mirrored into the scaffold.
  • .github/workflows/reusable-dispatch.yml:577 (architecture.md, docs/cli/) — moot under the allowlist, d79011a; both are test cases.
  • docs/ADRs/0096-skip-provably-unnecessary-review-dispatch.md:144 — fixed in b998b8d: correctness parity is mirrored, spend optimisations are not.

09-02:

  • .github/workflows/reusable-dispatch.yml:579 (docs/*.md swallows contract prose) — fixed in 6ca0ee3, superseded by the allowlist in d79011a. TestReviewRoutingDocsSkipRuntime runs the step and pins docs/normative/, docs/contributing/, docs/reference/ as reviewed.
  • docs/agents/review.md:42 (agents docs/review.md Triggers stale) — companion PR in fullsend-ai/agents to follow (Triggers gains the three skips, Control labels gains fullsend-no-review); linked here and from ADR 0096 once open.
  • Review body, docs/guides/user/bugfix-workflow.md:169 — fixed in b92fa5f: the label is the lever, Stage 3 lists the skips.

Scaffold shim now grants issues: write / pull-requests: write — needed to clear the labels in per-org mode.

@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 sweep on head b998b8d — 5 findings inline (2 HIGH, 3 MEDIUM). Review-only; no approval or change request implied.

contents: read
id-token: write
pull-requests: read
issues: write

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 — Scaffold dispatch job requests permissions its per-org caller shim does not grant — every per-org dispatch run fails at validation

Verified on head b998b8d. This PR raises the scaffold's single dispatch job from pull-requests: read to issues: write + pull-requests: write (dispatch.yml:22-29, hunk @@ -26,7 +26,8 @@), but the per-org caller was not updated: internal/scaffold/fullsend-repo/templates/shim-workflow-call.yaml:53-57 still grants only actions: write, id-token: write, contents: read, pull-requests: read — no issues scope at all — before uses: __ORG__/.fullsend/.github/workflows/dispatch.yml@main.

A workflow_call callee may only downgrade the caller's grant; requesting more fails validation at run start ("The workflow is requesting 'issues: write', but is only allowed 'issues: none'"), so the whole per-org dispatch job dies on every event — not just the new label step.

Contrast templates/shim-per-repo.yaml:40-46, which does grant issues: write + pull-requests: write; that is why the per-repo clear-stale-merge-labels job is fine. ADR 0096 §4 papers over the gap: "the per-repo shim already grants the dispatch job issues: write and pull-requests: write. The scaffold mirrors it as a last step of its single job, whose token is widened the same way" — that conflates the per-repo shim with the per-org caller.

The new test compounds this: internal/scaffold/workflow_call_alignment_test.go:1057-1059 asserts the callee's write permissions but nothing about the caller, so it pins the broken state green.

Suggestion: Widen templates/shim-workflow-call.yaml:53-57 to grant issues: write and pull-requests: write (or drop the label-clearing step from the per-org scaffold, consistent with how docs-lockfile-check is already excluded there), and add an assertion to TestReviewSkipClearsStaleMergeLabels that the caller shim's permission set is a superset of the callee job's, so caller/callee drift cannot pass again. Also correct the ADR §4 sentence, which currently cites the per-repo shim as evidence for the per-org widening.

}
if awk '/^```/ { in_code = !in_code; next } !in_code' <<< "${BODY}" \
| sed 's/`[^`]*`//g' \
| grep -E '<script|<style|^head:|\{\{|<[A-Za-z][^>]*[[:space:]](:|@|v-|on)[A-Za-z-]+=' >/dev/null; then

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 — Executable-markup scanner is bypassable: case-sensitive tag match, line-anchored attribute regex, and skipped -vue fences

Verified empirically by running the exact pipeline from :597-599 against crafted pages. All of the following are classified as inert prose and skip review, while rendering as live content in this repo's VitePress pipeline:

  1. <SCRIPT setup> / <STYLE> — the grep is case-sensitive (<script|<style), but docs/.vitepress/config.ts:85-86 KNOWN_TAGS carries the /i flag, so escapeVueSyntax passes the uppercase < through untouched and the tag reaches the renderer. Bypass confirmed.
  2. Multiline bound attributes/directives — <[A-Za-z][^>]*[[:space:]](:|@|v-|on)[A-Za-z-]+= is matched line by line, so <span\n v-html="payload"\n/> and <div\n :title="Math.random()"\n> split the tag from the binding and match nothing. v-html is the headline case: an unreviewed prose page can inject raw HTML. Bypass confirmed.
  3. ```js-vue fences — the awk '/^```/ { in_code = !in_code; next } !in_code' toggle drops all fence content, and escapeVueSyntax also returns fence lines unchanged (config.ts:80) while VitePress omits v-pre when the info string ends in -vue, so {{ 40 + 2 }} inside a ```js-vue fence evaluates at build. Bypass confirmed.
  4. head : with a space before the colon in frontmatter — ^head: misses it; YAML accepts it. Bypass confirmed.
  5. <!--@include: path--> — VitePress build-time file inclusion is not scanned at all, letting a prose page splice arbitrary repo content into a published page. Bypass confirmed.

One thing that is not a problem and should not be changed: {{ }} inside inline code really is inert here — config.ts:523-525 sets v-pre on code_inline and escapeVueSyntax copies backtick spans through untouched — so the markup inside code is inert case at workflow_call_alignment_test.go:971 is correct as written.

This is distinct from the thread at :577, which predates the scanner and asked for it to exist; this is about the scanner's implementation being evadable.

Suggestion: Make the grep case-insensitive (grep -Ei) and tolerate whitespace before the frontmatter colon (^head[[:space:]]*:). Join lines between < and the matching > before running the attribute/directive pattern (or scan with grep -Pzo in DOTALL mode) so multiline bindings cannot split. Keep scanning inside fences whose info string ends in -vue, and add @include: to the pattern. Add adversarial cases to internal/scaffold/workflow_call_alignment_test.go for uppercase <SCRIPT>, a multiline v-html, and a js-vue fence — all false positives merely keep the review, which ADR 0096 already accepts.

echo "${DELIM}"
} >> "${GITHUB_OUTPUT}"

clear-stale-merge-labels:

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 — New label mutation runs even when the kill switch is active, in both dispatch workflows

Verified on head. In reusable-dispatch.yml the "Check kill switch" step is gated if: steps.route.outputs.stage != '' (:415-416). Draft, fullsend-no-review and below-triage skips all produce stage == '', so the kill switch is never evaluated for them and the route job succeeds. The new clear-stale-merge-labels job then runs on needs.route.outputs.stage != 'review' (:688), which is true for exactly those cases, and DELETEs ready-for-merge and ready-for-review.

So with kill_switch: true in .fullsend/config.yaml — documented as "all agent dispatch halted" — fullsend still mutates PR labels on every push to a draft or to a fullsend-no-review PR. Before this PR nothing happened on those paths, so this is new behaviour escaping the kill switch.

The scaffold is broader still: its kill-switch step is gated steps.route.outputs.stage != '' && steps.pr-check.outputs.skipped != 'true' (dispatch.yml:360-361), while the new last step at :600-603 fires on stage != 'review' || role-check skipped || agent-check skipped. Only the docs-prose skip happens to be covered, and only incidentally, because its stage was review when the kill switch ran.

Suggestion: Expose a kill_switch output from the route job (or run the kill-switch step whenever github.event.action == 'synchronize') and add it to the clear-stale-merge-labels job if: and to the scaffold's step if:. Add a test case pinning that no label DELETE is issued while the kill switch is active.

Comment thread docs/agents/review.md

The review agent does not run automatically — though `/fs-review` always works — when:

- **The PR is a draft.** Opening or pushing to a draft does not trigger a review; it runs once the PR is marked ready for review. (Applying the `ready-for-review` label to a draft still dispatches — the label is an explicit request.)

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 — Doc says the ready-for-review label is an unconditional explicit request, but two of the three skips still apply on the labeled path

Verified on head. Line 46 states: "(Applying the ready-for-review label to a draft still dispatches — the label is an explicit request.)" That is only true of the draft skip.

(a) The fullsend-no-review label suppresses the labeled path too — reusable-dispatch.yml and the scaffold both gate it with && ! has_label "fullsend-no-review" (scaffold hunks @@ -175,7 +177,7 @@ and @@ -185,12 +187,19 @@).
(b) The docs-prose skip runs on the labeled path as well: docs-lockfile-check's if: at reusable-dispatch.yml:524 excludes only github.event_name != 'issue_comment', so a labeled event flows straight into it.

Concretely: a maintainer who labels a docs-only draft PR ready-for-review expecting an explicit review gets silence. Only /fs-review is a genuine unconditional bypass, which is what ADR 0096 §1/§2 actually promise.

This is separate substance from the thread at review.md:42, which is about the stale docs/review.md in the agents repo.

Suggestion: Pick one semantics and make docs and code agree: either treat the label as explicit — add github.event.action != 'labeled' to the docs-lockfile-check if: and drop the has_label "fullsend-no-review" guard on the labeled arms — or reword line 46 to say the label is an automatic trigger subject to all three skips and that /fs-review is the only unconditional override. Pin the chosen behaviour in TestReviewRoutingSkips.

# comment still works on drafts and labeled PRs alike.
if [[ "${EVENT_ACTION}" == "ready_for_review" || "${PR_IS_DRAFT}" != "true" ]] && ! has_label "fullsend-no-review" "${PR_LABELS}"; then
if [[ "${PR_USER_LOGIN}" =~ \[bot\]$ ]] || is_event_actor_authorized "${PR_USER_LOGIN}" triage; then
STAGE="review"

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 — Behaviour-changing PR missing the ! breaking-change marker required by COMMITS.md

Verified against COMMITS.md on head (Breaking changes section, ~lines 64-117): "Default values change in ways that alter existing behavior" is listed as breaking, both the ! suffix and a BREAKING CHANGE: trailer are required, and the section closes by instructing reviewers to "Flag a missing ! as an important-severity finding" because GoReleaser builds user-facing release notes from merged PR titles.

This PR changes default dispatch behaviour for every enrolled repo: drafts, fullsend-no-review-labeled PRs and docs-prose-only PRs no longer receive an automatic review, and a synchronize that skips now deletes ready-for-merge/ready-for-review.

Checked directly: the PR title is feat(review-agent): skip review dispatch that cannot produce a review (no !), none of the seven commit subjects on the branch carry !, and the PR body contains no BREAKING CHANGE note.

Suggestion: Retitle to feat(review-agent)!: skip review dispatch that cannot produce a review and add a BREAKING CHANGE: trailer to the relevant commit body naming the three new skip conditions, the new label-clearing on skipped pushes, and /fs-review as the escape hatch.

guyoron1 added a commit to guyoron1/agents that referenced this pull request Sep 11, 2026
The Automatic skips section, the fullsend-no-review control-label row
and the ADR 0096 link documented behaviour that lives only in
fullsend-ai/fullsend#6587, which is still OPEN. On fullsend main the
reusable-dispatch review arm has no draft/no-review/prose-only skip and
the ADR 0096 URL 404s, so the page told users drafts and labeled PRs are
not reviewed while every opened/synchronize still dispatches. Remove
that unshipped content; it lands with #6587.

Also fix a live, independent inaccuracy the section left stale: review
dispatch is gated at triage-level, not write-level (reusable-dispatch
uses is_authorized/is_event_actor_authorized triage per ADR 0054).
Match fullsend docs/agents/review.md.

Signed-off-by: guy oron <goron@redhat.com>
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.

2 participants