Skip to content

feat(triage): generalize workflow-change block into block_auto_promotion - #1078

Open
rh-hemartin wants to merge 1 commit into
mainfrom
feat/2207-block-auto-promotion
Open

feat(triage): generalize workflow-change block into block_auto_promotion#1078
rh-hemartin wants to merge 1 commit into
mainfrom
feat/2207-block-auto-promotion

Conversation

@rh-hemartin

@rh-hemartin rh-hemartin commented Aug 28, 2026

Copy link
Copy Markdown
Member

Summary

Replace the single-purpose requires_workflow_changes boolean with a general-purpose block_auto_promotion object (blocked + reason). Workflow-file detection now uses that field so later gates (effort scoring) can reuse the same post-script path and attach a reason.

This is the first of two slices split from feat/2207-effort-gating. Effort estimation is not in this PR.

The prompt's second block condition ("any other condition where auto-dispatch would be premature") is deliberately provisioned ahead of the effort-scoring gate (fullsend#2207 / follow-up PR) so reviewers and agents do not need to guess the intent. This PR only implements the workflow-file gate; the second clause is the hook for that follow-up.

Ref fullsend-ai/fullsend#2207

Behavior

  • When blocked is true, the post-script appends **Auto-promotion blocked:** <reason> to the triage comment for every category.
  • For bug/documentation/performance, blocked=true also applies triaged instead of ready-to-code. Categories that already go to triaged (feature, security, other) keep that label.
  • requires_workflow_changes is removed; the schema and post-script only accept block_auto_promotion.
  • The reason is flattened for the comment (newlines become spaces; ANSI is stripped) so :: and % stay intact. A held-for-review footer is used whenever blocked is true, including feature and TRIAGE_AUTO_CODE=off.

Test plan

  • bash scripts/post-triage-test.sh
  • bash scripts/validate-output-schema-test.sh

@rh-hemartin
rh-hemartin requested a review from a team as a code owner August 28, 2026 11:43
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Generalize triage auto-promotion blocking with reasons

✨ Enhancement 🧪 Tests 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Replaces the workflow-specific flag with reusable, reasoned auto-promotion blocking.
• Preserves legacy outputs while routing blocked auto-code categories to human review.
• Sanitizes triage comments and expands schema and post-script regression coverage.
Diagram

graph TD
  A["Triage agent"] --> B["Result schema"] --> C["Post-triage script"] --> D{"Auto-code eligible?"}
  D -->|Yes| E{"Promotion blocked?"}
  D -->|No| G["Triaged"]
  E -->|No| F["Ready to code"]
  E -->|Yes| G
  E -.->|Reason| H["Held comment"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. List independent blockers
  • ➕ Represents multiple simultaneous gates without merging reasons
  • ➕ Allows future gates to add structured blocker types
  • ➖ Expands the schema and prompt contract prematurely
  • ➖ Requires aggregation and compatibility logic in the post-script
2. Keep gate-specific fields
  • ➕ Makes each blocking condition explicit
  • ➕ Minimizes change to the existing workflow-specific path
  • ➖ Duplicates routing logic as new gates arrive
  • ➖ Couples post-processing to every new triage policy

Recommendation: Use the PR's single generalized block object for this slice: it centralizes routing, carries a human-readable reason, and preserves one-release compatibility without over-designing future effort gating. Consider a blocker array only if multiple independent producers must report concurrent reasons.

Files changed (9) +364 / -82

Enhancement (3) +179 / -50
triage.mdTeach triage to emit reasoned promotion blocks +13/-4

Teach triage to emit reasoned promotion blocks

• Updates workflow-change detection and output examples to use block_auto_promotion. Clarifies category routing and instructs the agent to avoid fenced code blocks in comments.

agents/triage.md

post-triage.shBundle generalized auto-promotion gate behavior +83/-23

Bundle generalized auto-promotion gate behavior

• Updates the generated post-triage script to resolve the new gate with legacy fallback, route eligible blocked issues to triaged, sanitize untrusted text, and publish held-review guidance.

scripts/post-triage.sh

post-triage.src.shImplement generalized auto-promotion gating +83/-23

Implement generalized auto-promotion gating

• Introduces the source implementation for reasoned blocking, backward-compatible workflow handling, comment fence removal, label routing, and blocked-specific next steps.

scripts/post-triage.src.sh

Tests (2) +149 / -25
post-triage-test.shCover blocked routing and comment sanitization +115/-25

Cover blocked routing and comment sanitization

• Expands regression coverage for category routing, legacy fallback, reason footers, held-review guidance, workflow-command sanitization, and fenced-block removal.

scripts/post-triage-test.sh

validate-output-schema-test.shValidate the new promotion block schema +34/-0

Validate the new promotion block schema

• Adds valid and invalid cases for required fields, empty reasons, extra properties, omission behavior, and deprecated-field compatibility.

scripts/validate-output-schema-test.sh

Documentation (3) +22 / -6
FEATURES.mdUpdate feature guidance for generalized promotion blocks +1/-1

Update feature guidance for generalized promotion blocks

• Replaces the workflow-specific schema example with the reusable block_auto_promotion field.

FEATURES.md

code.mdClarify ready-to-code promotion behavior +1/-1

Clarify ready-to-code promotion behavior

• Documents that the triage post-script applies ready-to-code only when auto-promotion is not blocked.

docs/code.md

triage.mdDocument auto-promotion blocking and compatibility +20/-4

Document auto-promotion blocking and compatibility

• Explains blocked and unblocked outcomes, affected categories, reason handling, and the temporary requires_workflow_changes fallback.

docs/triage.md

Other (1) +14 / -1
triage-result.schema.jsonAdd the block_auto_promotion result contract +14/-1

Add the block_auto_promotion result contract

• Adds a strict blocked-and-reason object with reason length validation. Retains requires_workflow_changes as an optional deprecated property for one release.

schemas/triage-result.schema.json

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 28, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 11:45 AM UTC · Completed 12:06 PM UTC

Commit: fa3c916 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $9.18

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Action required

1. triage.md adds agent directives 📜 Skill insight ⛨ Security
Description
The changed prompt text directly instructs the triage agent to set and apply block_auto_promotion.
This introduces agent-instruction patterns in a changed configuration/prompt file, which the
checklist expressly prohibits.
Code

agents/triage.md[R383-385]

+**Blocking auto-promotion:** Use the `block_auto_promotion` field in `triage_summary` to prevent the post-triage script from auto-promoting the issue to the code agent. Set `blocked: true` with a `reason` when:
+- The fix requires modifying CI/pipeline configuration files (`.github/workflows/`, `.gitlab-ci.yml`, `.fullsend/.github/workflows/`, or enrolled-repo shim workflows) that the code agent cannot modify under current permissions.
+- Any other condition where auto-dispatch would be premature.
Relevance

●●● Strong

Prompt-instruction compliance findings are accepted when changed agent guidance contains imperative
directives.

PR-#381
PR-#326

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1538322 prohibits agent-instruction patterns in changed comments, string literals,
or configuration values. The added text uses imperative directives such as `Use the
block_auto_promotion field and Set blocked: true`, directly matching the prohibited pattern.

agents/triage.md[383-389]
Skill: code-review

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 `agents/triage.md` content contains direct agent instructions, contrary to PR Compliance ID 1538322.

## Issue Context
The added text directs the agent to use `block_auto_promotion` and specifies when to set it. Rework the change so prohibited instruction patterns are not introduced in comments, strings, or configuration values.

## Fix Focus Areas
- agents/triage.md[383-389]

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


2. False block still falls back 🐞 Bug ≡ Correctness
Description
Using jq's // empty turns an explicit block_auto_promotion.blocked: false into an empty result,
so a simultaneously present deprecated requires_workflow_changes: true incorrectly blocks
promotion. This violates the documented rule that the deprecated field is consulted only when
block_auto_promotion is absent.
Code

scripts/post-triage.src.sh[R424-426]

+    BLOCKED=$(jq -r '.triage_summary.block_auto_promotion.blocked // empty' "${RESULT_FILE}")
+    BLOCK_REASON=$(jq -r '.triage_summary.block_auto_promotion.reason // empty' "${RESULT_FILE}")
+    if [[ -z "${BLOCKED}" ]]; then
Relevance

●●● Strong

This is a deterministic jq false-value fallback bug, matching accepted requests for robust
null/boolean handling.

PR-#284
PR-#876

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The schema allows both fields and allows blocked to be false, while the documentation limits
legacy fallback to absence of the new object. In jq, false // empty emits no value, so the
subsequent empty check enters the legacy fallback and changes the result to true.

scripts/post-triage.src.sh[424-435]
schemas/triage-result.schema.json[200-212]
docs/triage.md[150-157]

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

## Issue description
An explicit `block_auto_promotion.blocked: false` is lost because jq's `//` treats `false` like a missing value, allowing the deprecated flag to override it.

## Issue Context
The schema permits both migration fields, while the documented precedence says `requires_workflow_changes` applies only when the new object is absent. Test the valid combination `blocked:false` plus `requires_workflow_changes:true`.

## Fix Focus Areas
- scripts/post-triage.src.sh[424-436]
- scripts/post-triage-test.sh[810-818]
- scripts/post-triage.sh[1349-1361]

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


3. Protected automation paths modified 📜 Skill insight § Compliance
Description
This PR modifies protected agents/ and scripts/ paths, including triage instructions and
executable automation. These governance/automation changes require human approval and must not be
auto-approved.
Code

scripts/post-triage.src.sh[R73-76]

+# Drop paired line-start fenced code blocks. An unmatched opener is left
+# in place so the remainder of the text is not deleted. Inline triple
+# backticks (not at line start) are left unchanged.
+strip_line_start_fences() {
Relevance

●● Moderate

Protected-path approval findings are mixed: some governance requests were rejected, while others
remain undetermined.

PR-#609
PR-#476
PR-#157

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1538392 mandates a finding whenever protected paths such as agents/ or scripts/
are modified. The diff adds executable processing logic under scripts/ and also changes
agents/triage.md, so the protected-path review requirement is triggered.

scripts/post-triage.src.sh[73-76]
agents/triage.md[133-133]
Skill: pr-review



Remediation recommended

4. Fence stripping is bypassable 🐞 Bug ≡ Correctness
Description
The sanitizer recognizes every line beginning with exactly three backticks as a delimiter, so valid
tilde-fenced blocks are left intact and backtick-prefixed content inside a longer/matching fence can
be mistaken for its close. Consequently the post-script can publish code content it claims to strip,
or delete the wrong region of the triage comment.
Code

scripts/post-triage.src.sh[R80-87]

+      if ($0 ~ /^```/) {
+        n++
+        pos[n] = NR
+      }
+    }
+    END {
+      for (i = 1; i + 1 <= n; i += 2) {
+        for (j = pos[i]; j <= pos[i + 1]; j++) skip[j] = 1
Relevance

●●● Strong

Fence parsing edge cases are accepted; historical linter fixes specifically broaden fence
recognition and handling.

PR-#476

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The agent contract prohibits fenced code blocks generally, but the implementation records only lines
matching ^``` and blindly removes alternating pairs. The same helper's output becomes the posted
comment, so unsupported or prematurely closed fences reach every tracker.

agents/triage.md[367-372]
scripts/post-triage.src.sh[76-97]
scripts/post-triage.src.sh[489-495]

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 sanitizer only detects `^```` and pairs matches by position rather than matching actual Markdown opening and closing delimiters. Valid fenced blocks can survive, and delimiter-like lines inside a block can terminate removal early.

## Issue Context
Track the opening delimiter character and length, accept both backtick and tilde fences, and only close on a compatible delimiter-only line. Add tests for tilde fences, longer backtick fences containing triple backticks, and non-closing backtick-prefixed lines.

## Fix Focus Areas
- scripts/post-triage.src.sh[76-97]
- scripts/post-triage-test.sh[608-618]
- scripts/post-triage.sh[1001-1022]

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


Grey Divider

Context sources
✅ Compliance rules (platform): 56 rules
✅ Skills: 4 invoked
  code-review
  code-implementation
  pr-review
  docs-review
Review mode: ⚖️ Balanced

Grey Divider

Tip of the day
💡 Did you know, you can reply 'qodo' on any finding to push back, ask questions, or dig deeper

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread scripts/post-triage.src.sh Outdated
Comment thread agents/triage.md
Comment thread scripts/post-triage.src.sh Outdated
Comment thread scripts/post-triage.src.sh Outdated
@fullsend-ai-review fullsend-ai-review Bot added the risk/moderate PR risk: moderate label Aug 28, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 28, 2026

Copy link
Copy Markdown

Risk Assessment: elevated (3/5)

Details

Score 3 (elevated) preserved via re-review anchoring: Tier 1 signals are unchanged from the prior assessment (9 files, 389 lines in the 300-799 band, medium blast radius, 5 protected paths, test ratio 0.22, established non-bot author), and the head SHA delta since the last review is a single comment-only addition with no functional change. Tier 2 remains very elevated with extreme churn and fix/revert history concentrated in scripts/post-triage.sh (22 commits/30d, 45 fix/revert commits/90d, up to 10 distinct authors on docs/code.md), consistent with the prior review's Tier 2 measurement. No new signals (protected paths, dependency changes, security sensitivity) were introduced, so the composite score remains 3.

Previous run

Risk Assessment: elevated (3/5)

Details

Score 3 (elevated) preserved from prior assessment: Tier 1 signals are essentially unchanged (9 files, 387 lines in same 300-799 band, medium blast, 5 protected paths, test ratio 0.22), and Tier 2 remains very elevated with extreme fix/revert churn on post-triage.sh (55 all-time, 22 in 30d) and high multi-author contention (up to 10 distinct authors), partially offset by zero security sensitivity and no CI or dependency changes; composite 0.50x2.125 + 0.30x4.75 + 0.20x2.5 = 2.99 ~ 3.

Previous run (2)

Risk Assessment: elevated (3/5)

Details

Elevated risk maintained at 3: 9 files with 361 changed lines and medium blast radius, 5 protected paths, test ratio 0.22. Very high fix/revert churn on post-triage.sh and multi-author contention keep Tier 2 elevated. Linked issue is well-scoped but Tier 2 churn signals sustain the elevated composite.

Previous run (3)

Risk Assessment: elevated (3/5)

Details

Elevated risk maintained at 3, consistent with prior assessment: 9 files with 504 changed lines and large blast radius across the triage subsystem, 5 protected paths modified, test ratio at 0.22. Tier 2 continues to show very high fix/revert churn and high commit frequency, confirming an actively unstable area. Linked issue is well-scoped (priority/medium feature) which provides mild downward pressure, but the sustained Tier 2 churn keeps the composite at elevated.

Previous run (4)

Risk Assessment: elevated (3/5)

Details

Elevated risk maintained at 3, consistent with prior assessment: 9 files with 504 changed lines and large blast radius across the triage subsystem, 5 protected paths modified, test ratio at 0.22. Tier 2 continues to show very high fix/revert churn and high commit frequency, confirming an actively unstable area. Linked issue is well-scoped (priority/medium feature) which provides mild downward pressure, but the sustained Tier 2 churn keeps the composite at elevated.

Previous run (5)

Risk Assessment: elevated (3/5)

Details

Elevated risk (up from prior moderate): 9 files with 504 changed lines and large blast radius across the triage subsystem, 5 protected paths modified, test ratio at 0.22, and very high fix/revert churn (3-40 per file in 90d) indicating an actively unstable area; score increased from 2 to 3 due to blast radius upgrading to large and sustained high churn in Tier 2.

Previous run (6)

Risk Assessment: moderate (2/5)

Details

Moderate risk: 9 files with 445 changed lines across the triage subsystem, 5 protected paths modified, no CI/dependency changes, established contributor, but elevated churn in the triage scripts and docs (20-36 commits in 30 days).

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 28, 2026

Copy link
Copy Markdown

Review

Findings

Medium

  • [breaking-schema-change] schemas/triage-result.schema.json:203 — The requires_workflow_changes boolean field remains removed from triage_summary and replaced with block_auto_promotion. Because triage_summary declares additionalProperties: false, any triage agent output still containing requires_workflow_changes is rejected by schema validation (covered by the requires-workflow-changes-rejected test). The producer (agents/triage.md), the schema, and the consumer (post-triage.sh/post-triage.src.sh) are all updated together in this one PR within this one repository, so a run binds all three from a single checkout — this is an intentional, coordinated flag-day rather than a staggered cross-repo break. Unchanged since the prior review of this PR (e48f683).
    Remediation: Document in the PR description or release notes that this is an intentional flag-day change requiring atomic deployment. If staggered deployment is ever needed, add a transitional anyOf accepting either shape for one release cycle.

  • [protected-path] agents/triage.md, scripts/post-triage.sh, scripts/post-triage.src.sh, scripts/post-triage-test.sh, scripts/validate-output-schema-test.sh — Five files under protected paths (agents/, scripts/) are modified. The PR links to dispatch: no effort-based gating before auto-promoting issues to coder fullsend#2207 and explains the rationale for these changes (generalizing the auto-promotion blocking mechanism). Human approval is always required for protected-path changes, regardless of context.

Low

  • [scope-creep] scripts/post-triage-test.sh:602 — The new test comment-fenced-code-block-preserved exercises the action: "insufficient" code path and asserts that a fenced code block in the general comment field survives unmangled. This does not trace to issue #2207 or to the block_auto_promotion work: the PR's only new sanitization helper, _comment_sanitize(), is applied exclusively to BLOCK_REASON under the action: "sufficient" branch and never touches COMMENT, so this test does not guard the new feature's sanitization behavior — it is an unrelated regression test added in the same commit. Unchanged since the prior review of this PR (e48f683).

Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run

Review

Findings

High

  • [breaking-schema-change] schemas/triage-result.schema.json:203 — The requires_workflow_changes boolean field is removed from triage_summary and replaced with block_auto_promotion. Because triage_summary declares additionalProperties: false, any triage agent output still containing requires_workflow_changes will be rejected by schema validation. The agent prompt, schema, and post-script are all updated in the same PR within the same repository, so deployment is coordinated — but a race window exists if a running agent instance completes after the new schema is deployed but before it picks up the new prompt.
    Remediation: Document in the PR description or release notes that this is an intentional flag-day change requiring atomic deployment. If staggered deployment is possible, add a transitional anyOf accepting either shape for one release cycle.

Medium

  • [behavioral-contract-change] scripts/post-triage.sh:1410 — The GHA stdout warning string changed from ::warning::Triage detected workflow file changes required (#325) to ::warning::Skipping ready-to-code — auto-promotion blocked (see comment for details). The issue reference (#325) is removed.
    Remediation: Document the warning string change in release notes if any external tooling matches the old string.

  • [protected-path] agents/triage.md, scripts/post-triage.sh, scripts/post-triage.src.sh, scripts/post-triage-test.sh, scripts/validate-output-schema-test.sh — Five files under protected paths (agents/, scripts/) are modified. The PR links to dispatch: no effort-based gating before auto-promoting issues to coder fullsend#2207 and describes the rationale for these changes. Human approval is always required for protected-path changes, regardless of context.

  • [schema-constraint-tightening] schemas/triage-result.schema.json:209block_auto_promotion.reason has minLength: 1 and is required even when blocked: false. A producer emitting {"blocked": false} without a reason will fail schema validation. The documented path for unblocked cases is to omit block_auto_promotion entirely. The test blocked-empty-reason-gets-fallback exercises a defense-in-depth path unreachable under normal schema validation.
    Remediation: Add a comment to the test noting it exercises a defense-in-depth path. Consider whether reason should be optional when blocked: false.

Low

  • [behavioral-change] scripts/post-triage.sh:1409 — The GHA ::warning:: annotation is only emitted when both BLOCKED=true and AUTO_CODE_ALLOWED=true. When auto-code is disabled and blocked, no warning appears on stdout. The block reason is still appended to the tracker comment for all categories.

  • [scope-creep] scripts/post-triage-test.sh:603 — The test comment-fenced-code-block-preserved validates that fenced code blocks survive unchanged in the triage comment. This assertion does not trace to issue #2207 or to the block_auto_promotion work.

  • [error-handling-idiom] scripts/post-triage.sh:1402 — The empty-reason fallback ("No reason provided") handles a case the schema (minLength: 1) already prevents. This is defense-in-depth for unvalidated callers, but the intent is not documented.
    Remediation: Add an inline comment such as # Defence-in-depth: schema enforces minLength:1 but guard against unvalidated callers.

  • [interface-contract-change] scripts/post-triage.src.sh — The REQUIRES_WORKFLOW and WORKFLOW_BLOCKED variable names are replaced with BLOCKED and AUTO_PROMOTION_BLOCKED. Any external script sourcing this file and referencing old variable names will silently get unset values.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (2)

Review

Findings

Medium

  • [protected-path] agents/triage.md, scripts/post-triage.sh, scripts/post-triage.src.sh, scripts/post-triage-test.sh, scripts/validate-output-schema-test.sh — Five files under protected paths (agents/, scripts/) are modified. The PR links to dispatch: no effort-based gating before auto-promoting issues to coder fullsend#2207 and describes the rationale for these changes (generalizing the auto-promotion blocking mechanism). Human approval is always required for protected-path changes, regardless of context.

Low

  • [behavioral-change] scripts/post-triage.sh, scripts/post-triage.src.sh — The old code unconditionally emitted ::warning::Triage detected workflow file changes required (#325) whenever requires_workflow_changes was true, regardless of AUTO_CODE_ALLOWED. The new code only emits the warning when both BLOCKED=true and AUTO_CODE_ALLOWED=true. When auto-code is disabled and blocked=true, no warning is logged — an intentional narrowing of observability scope.
Previous run (3)

Review

Findings

Medium

  • [protected-path] agents/triage.md, scripts/post-triage.sh, scripts/post-triage.src.sh, scripts/post-triage-test.sh, scripts/validate-output-schema-test.sh — Five files under protected paths (agents/, scripts/) are modified. The PR links to dispatch: no effort-based gating before auto-promoting issues to coder fullsend#2207 and describes the rationale for these changes (generalizing the auto-promotion blocking mechanism). Human approval is always required for protected-path changes, regardless of context.

Low

  • [logic-inconsistency] scripts/post-triage.sh, scripts/post-triage.src.sh — The fence-stripping guard for BLOCK_REASON checks only backtick fences (grep -q '^\``') but not tilde fences (~~~), while the equivalent guard for COMMENT correctly checks both (grep -qE '^(```|~~~)'). In practice this is harmless because _gha_sanitize()` strips newlines first, making multi-line fences impossible — the guard is effectively dead code after sanitization.

  • [deprecation-lifecycle] schemas/triage-result.schema.json — The deprecation comment for requires_workflow_changes says "Kept optional for one release" but no tracking mechanism (issue reference, version target) ensures removal. A follow-up issue would prevent the dual-path logic from becoming permanent.

  • [behavioral-change] scripts/post-triage.sh, scripts/post-triage.src.sh — The old code unconditionally emitted ::warning::Triage detected workflow file changes required (#325) whenever requires_workflow_changes was true, regardless of AUTO_CODE_ALLOWED. The new code only emits the warning when both BLOCKED=true and AUTO_CODE_ALLOWED=true. When auto-code is disabled and blocked=true, no warning is logged — an intentional narrowing of observability scope.

Previous run (4)

Review

Findings

Medium

  • [protected-path] agents/triage.md, scripts/post-triage.sh, scripts/post-triage.src.sh, scripts/post-triage-test.sh, scripts/validate-output-schema-test.sh — Five files under protected paths (agents/, scripts/) are modified. The PR links to dispatch: no effort-based gating before auto-promoting issues to coder fullsend#2207 and describes the rationale for these changes (generalizing the auto-promotion blocking mechanism). Human approval is always required for protected-path changes, regardless of context.

Low

  • [logic-inconsistency] scripts/post-triage.sh, scripts/post-triage.src.sh — The fence-stripping guard for BLOCK_REASON checks only backtick fences (grep -q '^\``') but not tilde fences (~~~), while the equivalent guard for COMMENT correctly checks both (grep -qE '^(```|~~~)'). In practice this is harmless because _gha_sanitize()` strips newlines first, making multi-line fences impossible — the guard is effectively dead code after sanitization.

  • [schema-runtime-mismatch] scripts/post-triage-test.sh — The schema enforces reason with minLength: 1, but the post-script has a fallback for empty BLOCK_REASON ("No reason provided"). The test blocked-empty-reason-gets-fallback exercises a state the schema disallows. This is defense-in-depth; a brief comment noting it exercises a schema-invalid state for robustness would clarify intent.

  • [deprecation-lifecycle] schemas/triage-result.schema.json — The deprecation comment for requires_workflow_changes says "Kept optional for one release" but no tracking mechanism (issue reference, version target) ensures removal. A follow-up issue would prevent the dual-path logic from becoming permanent.

  • [precedence-semantics] scripts/post-triage.sh — When both block_auto_promotion and requires_workflow_changes are present, block_auto_promotion wins unconditionally via has(). This precedence rule is correctly tested (unblocked-wins-over-deprecated-workflow-flag) but only documented in inline comments — no schema-level annotation communicates it to external consumers.

Previous run (5)

Review

Findings

Medium

  • [protected-path] agents/triage.md, scripts/post-triage.sh, scripts/post-triage.src.sh, scripts/post-triage-test.sh, scripts/validate-output-schema-test.sh — Five files under protected paths (agents/, scripts/) are modified. The PR links to dispatch: no effort-based gating before auto-promoting issues to coder fullsend#2207 and describes the rationale for these changes (generalizing the auto-promotion blocking mechanism). Human approval is always required for protected-path changes, regardless of context.

Low

  • [logic-inconsistency] scripts/post-triage.sh — The fence-stripping guard for BLOCK_REASON checks only backtick fences (grep -q '^\``') but not tilde fences (~~~), while the equivalent guard for COMMENT correctly checks both (grep -qE '^(```|~~~)'). In practice this is harmless because _gha_sanitize()strips newlines first, making multi-line fences impossible, but the inconsistency is worth aligning. The same applies toscripts/post-triage.src.sh`.

  • [scope-creep] agents/triage.md — The PR changes the comment field instructions from “Include the proposed test case as a fenced code block” to “Do not include fenced code blocks; summarize test cases and fixes in prose.” This behavioral change is reinforced by the new strip_line_start_fences() function and ~15 associated tests, but is not mentioned in the PR title or description.

  • [code-duplication] scripts/post-triage.sh, scripts/post-triage.src.sh — Both files now contain identical copies of strip_line_start_fences(), the block_auto_promotion gate logic (~40 lines), and the held-for-review footer, extending a pre-existing duplication pattern.

  • [deprecation-lifecycle] schemas/triage-result.schema.json — The deprecation comment for requires_workflow_changes says “Kept optional for one release” but no tracking mechanism ensures removal. A follow-up issue would prevent the dual-path logic from becoming permanent.

  • [schema-runtime-mismatch] scripts/post-triage.sh — The schema enforces reason with minLength: 1, but the post-script has a fallback for empty BLOCK_REASON ("No reason provided"). The test blocked-empty-reason-gets-fallback exercises a state the schema disallows. This is defense-in-depth — adding a comment would clarify the intent.

Previous run (6)

Review

Findings

High

  • [logic-error] scripts/post-triage.sh:1345 — jq's // (alternative operator) treats false as falsy, so jq -r '.triage_summary.block_auto_promotion.blocked // empty' produces no output when blocked is false. This causes BLOCKED to be empty, falling into the if [[ -z "${BLOCKED}" ]] branch which reads the deprecated requires_workflow_changes field instead. If both fields are present with conflicting values (e.g., block_auto_promotion.blocked: false and requires_workflow_changes: true), the deprecated field incorrectly wins. The same issue exists in scripts/post-triage.src.sh.
    Remediation: Check for the presence of the block_auto_promotion object rather than the truthiness of blocked. Use jq -r 'if .triage_summary | has("block_auto_promotion") then "yes" else "no" end' and branch on that, then use .blocked | tostring inside.

  • [protected-path] agents/triage.md, scripts/post-triage.sh, scripts/post-triage.src.sh, scripts/post-triage-test.sh, scripts/validate-output-schema-test.sh — Five files under protected paths (agents/, scripts/) are modified. The PR has no linked issue, so there is insufficient context to verify authorization for modifying governance and infrastructure files. Human approval is required for protected-path changes.

Medium

  • [missing-authorization] — The PR body references branch feat/2207-effort-gating and mentions this is the first of two slices, but no issue is linked (no Closes #N or Ref #N). Per AGENTS.md section 3, non-trivial changes (400+ lines across 9 files) require explicit authorization via a linked issue.
    Remediation: Link the authorizing issue using Closes #NNNN or Ref #NNNN.

  • [sanitization-pattern] scripts/post-triage.src.shBLOCK_REASON is sanitized for :: sequences via a while-loop, but is not passed through _gha_sanitize() like other untrusted values in this file. While BLOCK_REASON does not currently reach stdout (it flows into COMMENT which is posted via --body-file), the inconsistency with the file's sanitization patterns creates fragility for future maintainers. The same applies to scripts/post-triage.sh.
    Remediation: Either pass BLOCK_REASON through _gha_sanitize() or add a comment explaining why the colon-only sanitization is sufficient.

Low

  • [scope-creep] agents/triage.md:367 — The PR changes the comment field instructions from "Include the proposed test case as a fenced code block" to "Do not include fenced code blocks; summarize test cases and fixes in prose." This is related to the new strip_line_start_fences() function (both are about preventing fenced blocks in comments), but the behavioral change to agent commenting style is not mentioned in the PR title or description.

  • [test-inadequate] scripts/post-triage-test.sh — No test covers the scenario where both block_auto_promotion.blocked: false and requires_workflow_changes: true are present. This would exercise the jq false // empty precedence bug and document the intended field priority.

  • [test-inadequate] scripts/post-triage-test.sh — No test verifies the conditional "Next steps" footer text for blocked issues ("This issue was held for review. Run /fs-code only after confirming the concerns above.").


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 28, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 12:42 PM UTC · Completed 1:02 PM UTC

Commit: 361fb43 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $6.68

@fullsend-ai-review fullsend-ai-review Bot added risk/elevated PR risk: elevated and removed risk/moderate PR risk: moderate labels Aug 28, 2026
@fullsend-ai-review
fullsend-ai-review Bot dismissed their stale review August 28, 2026 13:02

Superseded by updated review

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Aug 28, 2026
@rh-hemartin
rh-hemartin force-pushed the feat/2207-block-auto-promotion branch from 361fb43 to f09b3d7 Compare September 1, 2026 09:02
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 1, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:04 AM UTC · Completed 9:46 AM UTC

Commit: f09b3d7 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high

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

Five inline findings on scripts/post-triage.src.sh (1 high, 4 medium), all concerning the new fence stripper and the block_auto_promotion comment path.

Comment thread scripts/post-triage.src.sh Outdated
Comment thread scripts/post-triage.src.sh Outdated
Comment thread scripts/post-triage.src.sh Outdated
Comment thread scripts/post-triage.src.sh Outdated
Comment thread scripts/post-triage.src.sh Outdated
@rh-hemartin
rh-hemartin force-pushed the feat/2207-block-auto-promotion branch from f09b3d7 to 551a2af Compare September 3, 2026 05:30
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 3, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:31 AM UTC · Completed 5:51 AM UTC

Commit: 551a2af · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $7.60

@rh-hemartin
rh-hemartin force-pushed the feat/2207-block-auto-promotion branch from 551a2af to 0362b88 Compare September 3, 2026 08:04
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 3, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:05 AM UTC · Completed 8:27 AM UTC

Commit: 0362b88 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $6.98

Comment thread scripts/post-triage.src.sh Outdated
Comment thread scripts/post-triage.src.sh Outdated
Comment thread scripts/post-triage.src.sh Outdated
Comment thread agents/triage.md
@rh-hemartin
rh-hemartin force-pushed the feat/2207-block-auto-promotion branch from 0362b88 to e48f683 Compare September 7, 2026 10:16
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 7, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 10:18 AM UTC · Completed 10:38 AM UTC

Commit: e48f683 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $5.94

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot removed the requires-manual-review Review requires human judgment label Sep 7, 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.

[HIGH] Required check functional-tests (triage) is currently failing (eval max_turns regression)

Live CI on the current HEAD (e48f683) shows functional-tests (triage) and the downstream functional-tests-complete gate both FAILING (run: https://github.com/fullsend-ai/agents/actions/runs/34110624836/job/101705837083), confirmed via gh pr checks 1078. The PR's test-plan checklist only lists the two passing shell unit-test scripts; the required live-agent eval suite is red with REGRESSIONS: 1 detected / [max_turns] pass_rate: >= 1.0 -> 0.875 — one triage eval case now needs more turns than the baseline allows, most plausibly from the added open-ended judgment call in the new block_auto_promotion prompt clause. No existing PR comment addresses this CI status; the PR is not mergeable in its current state.

Suggestion: Inspect the eval-results-triage artifact from that run to find the regressed case, and either tighten the prompt so the extra reasoning step isn't needed, or deliberately update the eval baseline (called out explicitly in the PR description) if the extra turn is expected. Do not merge while this required check is red.

Comment thread scripts/post-triage.src.sh
Replace the single-purpose requires_workflow_changes boolean with a
general-purpose block_auto_promotion object (blocked + reason). Workflow
file detection now sets blocked=true with a reason; later gates can use
the same field without a new post-script path.

When blocked is true, the post-script appends the reason to the triage
comment for every category. The label change (triaged instead of
ready-to-code) still applies only to categories that would otherwise
auto-promote.

The reason is flattened for the comment (newlines and ANSI only) so
:: and % stay intact for readers. Newlines cannot start a GHA workflow
command on stdout. A held-for-review footer is used when auto-promotion
is blocked.

Signed-off-by: Hector Martinez <hemartin@redhat.com>
@rh-hemartin
rh-hemartin force-pushed the feat/2207-block-auto-promotion branch from e48f683 to 5975344 Compare September 10, 2026 07:16
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 10, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 7:18 AM UTC · Completed 7:38 AM UTC

Commit: 5975344 · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5 · Effort: high · Cost: $4.03

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

"recommended_fix": { "type": "string", "minLength": 1 },
"proposed_test_case": { "type": "string", "minLength": 1 },
"requires_workflow_changes": { "type": "boolean" }
"block_auto_promotion": {

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] breaking-schema-change

The requires_workflow_changes boolean field remains removed from triage_summary and replaced with block_auto_promotion. Because triage_summary declares additionalProperties: false, any triage agent output still containing requires_workflow_changes is rejected by schema validation (covered by the requires-workflow-changes-rejected test). The producer (agents/triage.md), the schema, and the consumer (post-triage.sh/post-triage.src.sh) are all updated together in this one PR within this one repository, so a run binds all three from a single checkout - this is an intentional, coordinated flag-day rather than a staggered cross-repo break. Unchanged since the prior review of this PR (e48f683).

Suggested fix: Document in the PR description or release notes that this is an intentional flag-day change requiring atomic deployment. If staggered deployment is ever needed, add a transitional anyOf accepting either shape for one release cycle.

@@ -569,6 +600,10 @@ run_test_no_pattern() {
echo "PASS: ${test_name}"
}

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] scope-creep

The new test comment-fenced-code-block-preserved exercises the action: "insufficient" code path and asserts that a fenced code block in the general comment field survives unmangled. This does not trace to issue #2207 or to the block_auto_promotion work: the PR's only new sanitization helper, _comment_sanitize(), is applied exclusively to BLOCK_REASON under the action: "sufficient" branch and never touches COMMENT, so this test does not guard the new feature's sanitization behavior - it is an unrelated regression test added in the same commit. Unchanged since the prior review of this PR (e48f683).

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

Four inline findings (1 high, 3 medium) on the block_auto_promotion change: a missing breaking-change marker on the commit/PR title, a blank-reason gap in the post-script fallback, and two documentation issues.

"recommended_fix": { "type": "string", "minLength": 1 },
"proposed_test_case": { "type": "string", "minLength": 1 },
"requires_workflow_changes": { "type": "boolean" }
"block_auto_promotion": {

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] Field removal ships without the ! suffix and BREAKING CHANGE: trailer that COMMITS.md requires

This PR removes the requires_workflow_changes boolean from triage_summary, which declares additionalProperties: false, so any output still carrying that field is now rejected. COMMITS.md:75-83 lists "A field, flag, command, or API endpoint is removed or renamed" as breaking; COMMITS.md:66-73 requires BOTH an ! after the type/scope AND a BREAKING CHANGE: trailer in the commit body; and COMMITS.md:115 says a missing ! must be flagged as an important-severity finding.

Verified at head 5975344: the PR title and the PR's single commit are both feat(triage): generalize workflow-change block into block_auto_promotion with no !, and the commit body has no BREAKING CHANGE: trailer.

This is distinct from the two existing bot threads on this line: those ask only for a note in the PR description or release notes, and the PR body already carries that note (line 15: "requires_workflow_changes is removed; the schema and post-script only accept block_auto_promotion") — which satisfies the bot while leaving the COMMITS.md commit/PR-title requirement unmet. No existing comment mentions the ! suffix, the trailer, or COMMITS.md.

Suggestion: Retitle the PR and amend the commit to feat(triage)!: generalize workflow-change block into block_auto_promotion, and add a trailer to the commit body, e.g.:

BREAKING CHANGE: triage_summary.requires_workflow_changes is removed and rejected by additionalProperties:false. Migrate requires_workflow_changes: true to block_auto_promotion: {"blocked": true, "reason": "..."}.

AUTO_PROMOTION_BLOCKED=false
if [[ "${BLOCKED}" == "true" ]]; then
# Defense-in-depth: schema enforces minLength:1 but guard against unvalidated callers.
if [[ -z "${BLOCK_REASON}" ]]; 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.

[MEDIUM] Space- or tab-only block reason bypasses the "No reason provided" fallback and posts an empty blocked notice

_comment_sanitize (lines 77-79) replaces \n/\r with spaces, and the guard here tests [[ -z "${BLOCK_REASON}" ]], which does not match a non-empty but blank string.

Reproduced through the real pipeline (jq -r '...reason // empty' then _comment_sanitize) at head 5975344: reason "\n" IS caught (command substitution strips trailing newlines), but reason " ", "\t" and " \n" all bypass the fallback (len=1), and "\n\nx\n" yields " x". All of these satisfy the schema's minLength: 1 (schemas/triage-result.schema.json:206-211), so validation_loop passes them through, and the tracker comment renders **Auto-promotion blocked:** with nothing after it — the issue is held with no explanation, and the held-for-review footer still fires.

Not covered by an existing thread: the resolved thread at line 78 was about newline deletion vs replacement (fixed), and the bot thread on the bundled scripts/post-triage.sh:1403 argues the fallback is unreachable rather than that it fails to catch blank input.

Suggestion: Trim whitespace after sanitizing, e.g. BLOCK_REASON="$(_comment_sanitize "${BLOCK_REASON}" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')", or change the guard to [[ -z "${BLOCK_REASON//[[:space:]]/}" ]] so the "No reason provided" fallback still fires. Add a fixture with a space-only reason to scripts/post-triage-test.sh, and re-run make script-build so the bundled scripts/post-triage.sh mirror (around line 1403) stays in sync.

Comment thread docs/code.md
| Label | Meaning |
|-------|---------|
| `ready-to-code` | Triggers the code agent. Applied by the [triage](triage.md) agent for low-risk categories (bug, documentation, performance), or manually by a human for feature work after prioritization. Not applied when the triage result sets `requires_workflow_changes`, since the code agent cannot modify workflow files. |
| `ready-to-code` | Triggers the code agent. Applied by the [triage](triage.md) post-script for low-risk categories (bug, documentation, performance) when auto-promotion is not blocked, or manually by a human for feature work or workflow changes after 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] This row now tells humans to hand-apply ready-to-code for workflow changes, dispatching a coder that provably cannot push them

The rewritten ready-to-code row reads "...or manually by a human for feature work or workflow changes after review." The row it replaced said the opposite — "Not applied when the triage result sets requires_workflow_changes, since the code agent cannot modify workflow files" — so this PR deletes the warning and replaces it with the inverse instruction.

The permission gap itself is unchanged, and is asserted in two places, one of them edited by this same PR: agents/triage.md:133 still instructs the agent to block because "the code agent cannot modify workflow files under current permissions", and scripts/validate-code-output.sh:204-205,239 detects the push rejection and posts "the coder app intentionally lacks workflows write permission. Changes to .github/workflows/ must be made outside the agent".

Applying ready-to-code to a workflow-change issue therefore dispatches a run guaranteed to fail at push time — the exact outcome block_auto_promotion exists to prevent. No existing comment touches docs/code.md; this is about the docs inversion, not the footer wording discussed on the won't-fix thread in post-triage.src.sh.

Suggestion: Drop "or workflow changes" from the row and restore an explicit caveat, e.g. "Workflow/CI file changes cannot be completed by the code agent (it lacks workflows write permission) and require a human-authored PR — do not apply ready-to-code to unblock them."

Comment thread docs/triage.md
post-script applies the actions via `PUT /rest/api/3/issue/{key}` with
`fields.components`.

### Blocking auto-promotion

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] The /fs-code -> held-for-review footer swap is undocumented, and unlike the label change it fires for every category

When BLOCKED == "true", scripts/post-triage.src.sh:756-771 replaces the standard **Next steps:** /fs-code ... footer with "This issue was held for review. Run /fs-code only after confirming the concerns above." That footer branches on BLOCKED directly, so it applies to every category — including feature/security/other, which never auto-promoted and are otherwise unaffected by this change.

The new documentation stops one sentence short of it: this "Blocking auto-promotion" section (lines 164-177) documents only the reason-append and the label substitution, and agents/triage.md:389-395 does the same. Verified at head 5975344 — grep for "held for review", "Next steps" and "footer" across docs/triage.md, docs/code.md and agents/triage.md returns zero hits.

An operator reading the docs would conclude feature/security issues are untouched by blocked: true, when in fact their comment loses the /fs-code invitation.

Suggestion: Add a bullet to the "Blocking auto-promotion" section, e.g. "blocked: true also replaces the standard /fs-code next-steps footer with a held-for-review note. Unlike the label change, this applies to every category, including feature/security/other."

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants