Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ script-test:
$(call run-timed,bash scripts/pre-review-test.sh)
$(call run-timed,bash scripts/post-review-test.sh)
$(call run-timed,bash scripts/risk-tier1-test.sh)
$(call run-timed,bash scripts/pre-fix-test.sh)
$(call run-timed,bash scripts/post-fix-test.sh)
$(call run-timed,bash scripts/post-retro-test.sh)
$(call run-timed,bash scripts/pre-scribe-test.sh)
Expand Down
20 changes: 18 additions & 2 deletions agents/fix.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,24 @@ Bot-triggered runs (from the review agent) are capped at `ITERATION_CAP`
(default: 5). When the iteration count approaches this cap, the `needs-human`

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

Two existing lines in the agents/fix.md iteration-cap paragraph ("This ensures humans are / never locked out...") are rewrapped with no text change, which is unrelated churn under AGENTS.md §3. post-fix.src.sh also adds a HUMAN_CAP local only to avoid inlining ${ITERATION_CAP_HUMAN:-10} in an echo that otherwise did not change. See also: [code-organization] finding at this location (same reflow).

Suggested fix: Revert the whitespace-only rewrap in agents/fix.md and the unneeded HUMAN_CAP variable in post-fix.src.sh (then rebuild scripts/post-fix.sh with make script-build).

label is added and the autonomous loop stops on the next attempt. A human can
then direct the agent with `/fs-fix` commands up to `ITERATION_CAP_HUMAN`
(default: 10) total iterations (bot + human combined). This ensures humans
are never locked out of the agent after a bot loop exhausts its budget.
(default: 10) total iterations (bot + human combined). This ensures humans are
never locked out of the agent after a bot loop exhausts its budget.

A maintainer can tighten the *autonomous* loop for a single PR with a
`fullsend-fix-budget/N` label (N a positive integer). If present, the smallest
valid label lowers the bot cap to N, so the review→fix loop escalates to a human
sooner on a risky or expensive PR. It applies to the bot cap only: the human
`/fs-fix` cap is never tightened, so the label can never lock a human out. The
label can only *tighten* the bot cap, never raise it: a value at or above the
bot cap has no effect, and malformed values (non-integer, zero, negative, or
absurdly large) are ignored so a bad label cannot silently block or widen the
loop. pre-fix enforces the tightened cap and post-fix reports against the same
effective cap (summary and the `needs-human` warning).

> **Not yet active.** The label is recognized and enforced, but the review→fix
> workflow does not yet forward PR labels to the fix agent (`PR_LABELS` is unset
> in the delivery path), so applying the label currently has no effect. See
> `docs/fix.md` and the wiring follow-up before relying on it.

## Validation retry behavior

Expand Down
9 changes: 9 additions & 0 deletions docs/fix.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Remove the label or use `/fs-fix` to re-engage.
|-------|---------|
| `fullsend-no-fix` | Prevents automatic fix runs on this PR. Applied by `/fs-fix-stop`. Manual `/fs-fix` commands are unaffected. |
| `needs-human` | The fix agent is approaching its iteration cap and needs human direction. Applied automatically when an automatic fix iteration reaches the warning threshold. |
| `fullsend-fix-budget/N` | **Reserved — not yet active** (requires `PR_LABELS` wiring in the review→fix workflow; see [Iteration limits](#iteration-limits)). Once active: tightens the *autonomous* review→fix loop for this PR to `N` iterations (`N` a positive integer), so the bot escalates to a human sooner. Applied by a maintainer. Lowers the bot cap only (never the human `/fs-fix` cap) and can only tighten it, never raise it; malformed values are ignored. |

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] architectural-fit

The PR ships a feature that its own docs call "Reserved — not yet active". PR_LABELS is not set in the delivery path and the wiring is deferred to a fullsend follow-up, so the code, tests and docs describe and enforce a knob nobody can use yet; applying the label has no effect. "Not yet active" caveats are spread across agents/fix.md, docs/fix.md and harness/fix.yaml, and all three must be edited again when the feature is wired. The wiring plan lives in a harness comment rather than a tracked issue (AGENTS.md §2: no speculative features).

Suggested fix: Land this together with, or after, the workflow wiring. If it must go first, link a tracking issue for the follow-up and consider holding the user-facing docs until the feature is live.


## Configuration

Expand Down Expand Up @@ -156,6 +157,14 @@ The fix agent enforces iteration caps to prevent infinite review-fix loops:
`needs-human` label.
- Each `/fs-fix` comment cancels any in-flight fix run for the same PR and
starts a new one.
- **Per-PR override (reserved, not yet active):** a `fullsend-fix-budget/N`
label is intended to lower the *automatic* cap for a single PR (bot only; the
manual `/fs-fix` cap is never tightened, so a human is never locked out). The
parser and enforcement ship in pre-fix/post-fix, but the review→fix workflow
does not yet forward PR labels to the fix agent (`PR_LABELS` is unset in the
delivery path), so the label currently has no effect. Wiring it requires the
workflow to pass the PR's labels through `PR_LABELS`; until then the caps
behave exactly as above.

## Multi-forge support

Expand Down
9 changes: 9 additions & 0 deletions harness/fix.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ env:
TRIGGER_SOURCE: "${TRIGGER_SOURCE}"
HUMAN_INSTRUCTION: "${HUMAN_INSTRUCTION}"
FIX_ITERATION: "${FIX_ITERATION}"
# NOTE: a `fullsend-fix-budget/N` PR label can tighten the autonomous (bot)

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 PR adds a ~28-line NOTE comment to the shared env.runner block, mostly describing how to change another repo's workflows (reusable-fix.yml and reusable-dispatch.yml in fullsend). That is a design note for a follow-up, not a description of this repo's harness; it will go stale and bloats the harness file. The PR body does not mention it. It adds no env entry, so it is consistent with AGENTS.md §8.

Suggested fix: Cut the comment to 2–3 lines saying PR_LABELS is read from the step env, and move the wiring plan to the follow-up issue.

# fix cap, and pre-fix/post-fix already consume PR_LABELS to enforce it
# (the human /fs-fix cap is never tightened). It is omitted
# here on purpose: the reusable-fix workflow does not yet forward the PR
# labels, and referencing an unset host var fails harness env validation
# (fail-closed) on every run. To activate, re-add
# PR_LABELS: "${PR_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] Activation recipe prescribes an env.runner entry that pre-/post-fix do not need, and that would fail closed on GitLab

This comment tells the follow-up author to activate the label by re-adding PR_LABELS: "${PR_LABELS}" to the shared env.runner block together with a reusable-fix.yml change. Verified against fullsend-ai/fullsend main (7495cdc5):

  1. The entry is redundant for enforcement. childScriptEnv() in internal/cli/run.go:3586-3587 builds the pre-/post-script environment from os.Environ() first and only then overlays RunnerEnv. A step-level env: PR_LABELS on the "Run fix agent" step (reusable-fix.yml:361-385) therefore already reaches pre-fix and post-fix — the only two consumers — with no env.runner entry at all.
  2. Following the recipe breaks GitLab. harness/fix.yaml is forge-shared. The GitLab agent template (internal/scaffold/fullsend-repo-gitlab/.gitlab/ci/fullsend-agent.yml) sets every other key in this shared block (TARGET_BRANCH, HUMAN_INSTRUCTION, REVIEW_BODY_FILE, PRE_AGENT_HEAD, PUSH_TOKEN, ...) but has zero references to PR_LABELS. Adding the entry as written reproduces, on every GitLab fix run, the exact ValidateRunnerEnvWith fail-closed abort (harness.go:734-737, env.runner[PR_LABELS]: host variable PR_LABELS is not set) that the earlier thread on this file caught for GitHub.
  3. The forge-scoped blocks already exist — forge.github.env.runner (~line 119) and forge.gitlab.env.runner (~line 136) are the correct home for any forge-specific entry, and the recipe does not mention them.

This refines the suggestion in the earlier CRITICAL thread (which proposed re-adding the env.runner line in the wiring PR): that step is unnecessary, and unsafe in the shared block.

Suggestion. Rewrite the comment to state that the workflow step env alone is sufficient for pre-/post-fix (childScriptEnv inherits the process environment), so the GitHub wiring is just PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }} on the "Run fix agent" step. If an env.runner entry is ever wanted, say it must go in forge.github.env.runner, and in forge.gitlab.env.runner only after the GitLab agent template exports a set-possibly-empty PR_LABELS — never in the shared block.

# together with the reusable-fix.yml change that sets PR_LABELS (labels are
# available upstream as a comma-joined string).
REVIEW_BODY_FILE: "${REVIEW_BODY_FILE}"
PRE_AGENT_HEAD: "${PRE_AGENT_HEAD}"
PUSH_TOKEN: "${PUSH_TOKEN}"
Expand Down
52 changes: 52 additions & 0 deletions scripts/lib/fix-budget.lib.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env bash

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

6. Protected scripts require human approval 📜 Skill insight § Compliance

The PR modifies multiple files under the protected scripts/ path, so it must receive human review
and must not be auto-approved. The feature rationale provides context, but there is no linked issue
authorizing these governance/infrastructure changes.
Agent Prompt
## Issue description
This PR changes protected `scripts/` infrastructure and cannot be auto-approved.

## Issue Context
Route the PR for human approval and link the authorizing issue for the protected-path changes before merge.

## Fix Focus Areas
- scripts/lib/fix-budget.lib.sh[1-42]
- scripts/pre-fix.src.sh[20-29]
- scripts/pre-fix.src.sh[114-119]
- scripts/pre-fix-test.sh[1-59]

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

# shellcheck shell=bash
# fix-budget.lib.sh — parse a per-PR fix-loop budget from PR labels.
#
# A label of the form `fullsend-fix-budget/N` (N a positive integer) lets a
# maintainer cap the review->fix loop for a single PR below the global
# iteration cap. The label can only TIGHTEN the cap, never raise it:
# enforcement lives in pre-fix, which applies min(label_budget, cap).
#
# Bundled into pre-fix.sh via bundle-sh.sh.
#
# Expected env vars (optional):
# PR_LABELS — PR label names separated by commas and/or newlines. Absent/empty
# is fine: parse_fix_budget then returns nothing and the cap is
# unchanged. (The upstream dispatcher comma-joins labels; a
# newline-joined value is also accepted.)

[[ -n "${FIX_BUDGET_SH_LOADED:-}" ]] && return 0
FIX_BUDGET_SH_LOADED=1

FIX_BUDGET_LABEL_PREFIX="fullsend-fix-budget/"

# parse_fix_budget [labels]
# Reads label names (arg 1, or PR_LABELS env when omitted) separated by commas
# and/or newlines. Echoes the smallest valid budget found, or nothing when no
# valid label is present. A malformed value (non-integer, zero, negative) is
# ignored, not fatal — a bad label must not silently drop the existing cap.
parse_fix_budget() {
local labels="${1-${PR_LABELS:-}}"
local best="" label n
# Accept comma-joined labels (the upstream dispatcher format) as well as
# newline-joined: normalize commas to newlines before splitting.
labels="${labels//,/$'\n'}"
while IFS= read -r label; do
Comment on lines +28 to +34

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

5. Feature lacks linked authorization 📜 Skill insight § Compliance

This PR adds a new parser, runtime guard, generated bundle changes, and tests well beyond the rule's
20-line threshold, but the PR metadata contains no linked authorizing issue. The non-trivial feature
therefore lacks the required explicit authorization.
Agent Prompt
## Issue description
The non-trivial feature change has no linked issue authorizing its scope.

## Issue Context
Link an issue that explicitly authorizes the per-PR fix-budget feature and confirms the intended producer wiring and enforcement scope.

## Fix Focus Areas
- scripts/lib/fix-budget.lib.sh[1-42]
- scripts/pre-fix.src.sh[114-119]

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

# Trim surrounding whitespace so " fullsend-fix-budget/3 " still matches.
label="${label#"${label%%[![:space:]]*}"}"
label="${label%"${label##*[![:space:]]}"}"
[[ "${label}" == "${FIX_BUDGET_LABEL_PREFIX}"* ]] || continue
n="${label#"${FIX_BUDGET_LABEL_PREFIX}"}"
# Bound the digit count. An arbitrarily long value would overflow Bash's
# signed 64-bit arithmetic in the `-lt` comparison (e.g. 2^64 evaluates as
# 0), which would look "tighter" than any cap and block every fix run.
# A budget above 99999 is meaningless next to caps of 5/10, so treat an
# over-long value as malformed and ignore it.
[[ "${n}" =~ ^[1-9][0-9]{0,4}$ ]] || continue

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] fail-open

A malformed fullsend-fix-budget/ label (empty, zero, negative, non-numeric, or six or more digits) is silently skipped, and pre-fix/post-fix fall back to the global bot cap. Nothing tells the maintainer that the label they applied was ignored. The ignore-malformed policy is deliberate and documented (lib comment, agents/fix.md, docs/fix.md) and the fallback is the bounded pre-existing default, so no limit is bypassed.

Suggested fix: Optional: emit a gha_echo warning when a label carries the budget prefix but fails validation, so the maintainer knows the restriction did not apply.

if [[ -z "${best}" || "${n}" -lt "${best}" ]]; then
best="${n}"
fi
done <<< "${labels}"
[[ -n "${best}" ]] && printf '%s\n' "${best}"
return 0
}
40 changes: 40 additions & 0 deletions scripts/lib/review-labels.lib.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env bash

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 PR moves is_control_label and REVIEW_CONTROL_LABELS out of post-review.src.sh into a new lib, rewrites post-review-test.sh to source it, and makes fullsend-fix-budget/* a control label, so the review agent can no longer add or remove it via label_actions. The PR body does not mention this review-agent behaviour change or the extraction. The move supports the feature (the old test exercised a duplicated copy), so this is a disclosure gap rather than real scope creep.

Suggested fix: Disclose the review-agent behaviour change and the extraction in the PR body.

# shellcheck shell=bash
# review-labels.lib.sh — recognize pipeline-managed "control" labels.
#
# Control labels are set by the review pipeline (or a maintainer, for the
# fix-budget knob), not by the review agent. post-review refuses to add or
# remove them via agent-recommended label_actions. Kept in a sourceable lib so
# the same definition is exercised by both production and the unit test — a
# duplicated copy in the test would pass even if the production branch drifted.
#
# Bundled into post-review.sh via bundle-sh.sh.

[[ -n "${REVIEW_LABELS_SH_LOADED:-}" ]] && return 0
REVIEW_LABELS_SH_LOADED=1

REVIEW_CONTROL_LABELS=(
"ready-for-merge" "requires-manual-review" "rejected"
"ready-for-review" "fullsend-no-fix" "fullsend-fix"
)

# is_control_label LABEL — return 0 if LABEL is pipeline-managed, 1 otherwise.
is_control_label() {
local label="$1"
local cl
for cl in "${REVIEW_CONTROL_LABELS[@]}"; do
if [[ "${cl}" == "${label}" ]]; then
return 0
fi
done
# Pipeline-managed label prefixes.
if [[ "${label}" == risk/* ]]; then
return 0
fi
# Maintainer-set fix-loop budget (fullsend-fix-budget/N); pipeline-managed so
# the review agent preserves it rather than treating it as a contextual label.
if [[ "${label}" == fullsend-fix-budget/* ]]; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] auth-bypass

is_control_label matches fullsend-fix-budget/* case-sensitively. In post-review.src.sh an agent-recommended remove for a mixed-case name such as Fullsend-fix-budget/2 passes the character regex and the control-label guard, is added to VALIDATED_LABEL_REMOVES without an exact-spelling or existence check, and is sent to forge_remove_label. GitHub label names are case-insensitive, so this could remove a maintainer's budget label despite the new control-label policy. (The same gap already exists for the older control labels.) Impact is bounded: removing the label only restores the default bot cap, and the feature is inactive until PR_LABELS is wired.

Suggested fix: Compare control labels case-insensitively (e.g. lowercase with ${label,,} before matching), and add a regression test in post-review-test.sh showing a mixed-case control-label remove is refused.

return 0
fi
return 1
}
69 changes: 68 additions & 1 deletion scripts/post-fix.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1206,6 +1206,60 @@ classify_branch_vs_pr_head() {
fi
}
# END bundled: lib/branch-guard.lib.sh
# shellcheck source=lib/fix-budget.lib.sh
# BEGIN bundled: lib/fix-budget.lib.sh
# shellcheck shell=bash
# fix-budget.lib.sh — parse a per-PR fix-loop budget from PR labels.
#
# A label of the form `fullsend-fix-budget/N` (N a positive integer) lets a
# maintainer cap the review->fix loop for a single PR below the global
# iteration cap. The label can only TIGHTEN the cap, never raise it:
# enforcement lives in pre-fix, which applies min(label_budget, cap).
#
# Bundled into pre-fix.sh via bundle-sh.sh.
#
# Expected env vars (optional):
# PR_LABELS — PR label names separated by commas and/or newlines. Absent/empty
# is fine: parse_fix_budget then returns nothing and the cap is
# unchanged. (The upstream dispatcher comma-joins labels; a
# newline-joined value is also accepted.)

[[ -n "${FIX_BUDGET_SH_LOADED:-}" ]] && return 0
FIX_BUDGET_SH_LOADED=1

FIX_BUDGET_LABEL_PREFIX="fullsend-fix-budget/"

# parse_fix_budget [labels]
# Reads label names (arg 1, or PR_LABELS env when omitted) separated by commas
# and/or newlines. Echoes the smallest valid budget found, or nothing when no
# valid label is present. A malformed value (non-integer, zero, negative) is
# ignored, not fatal — a bad label must not silently drop the existing cap.
parse_fix_budget() {
local labels="${1-${PR_LABELS:-}}"
local best="" label n
# Accept comma-joined labels (the upstream dispatcher format) as well as
# newline-joined: normalize commas to newlines before splitting.
labels="${labels//,/$'\n'}"
while IFS= read -r label; do
# Trim surrounding whitespace so " fullsend-fix-budget/3 " still matches.
label="${label#"${label%%[![:space:]]*}"}"
label="${label%"${label##*[![:space:]]}"}"
[[ "${label}" == "${FIX_BUDGET_LABEL_PREFIX}"* ]] || continue
n="${label#"${FIX_BUDGET_LABEL_PREFIX}"}"
# Bound the digit count. An arbitrarily long value would overflow Bash's
# signed 64-bit arithmetic in the `-lt` comparison (e.g. 2^64 evaluates as
# 0), which would look "tighter" than any cap and block every fix run.
# A budget above 99999 is meaningless next to caps of 5/10, so treat an
# over-long value as malformed and ignore it.
[[ "${n}" =~ ^[1-9][0-9]{0,4}$ ]] || continue
if [[ -z "${best}" || "${n}" -lt "${best}" ]]; then
best="${n}"
fi
done <<< "${labels}"
[[ -n "${best}" ]] && printf '%s\n' "${best}"
return 0
}
# END bundled: lib/fix-budget.lib.sh


# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -1544,6 +1598,16 @@ fi
# ---------------------------------------------------------------------------
ITERATION="${FIX_ITERATION:-1}"
BOT_CAP="${ITERATION_CAP:-5}"

# A per-PR `fullsend-fix-budget/N` label may tighten the BOT cap only (never
# raise it, never touch the human cap) — matching pre-fix. Mirror it here so the
# needs-human warning and the iteration summary reflect the cap pre-fix actually
# enforces. Without this, a budget of 2 under a global cap of 5 would report
# "2 of 5" and never add needs-human, even though pre-fix rejects the next cycle.
FIX_BUDGET="$(parse_fix_budget "${PR_LABELS:-}")"
if [[ -n "${FIX_BUDGET}" && "${FIX_BUDGET}" -lt "${BOT_CAP}" ]]; then
BOT_CAP="${FIX_BUDGET}"
fi
WARN_THRESHOLD=$(( BOT_CAP - 1 ))

# The needs-human label is based on the bot cap — it signals that the
Expand All @@ -1568,5 +1632,8 @@ echo " Trigger: ${TRIGGER_SOURCE}"
if is_bot_user "${TRIGGER_SOURCE}"; then
echo " Iteration: ${ITERATION} of ${BOT_CAP} (bot cap)"
else
echo " Iteration: ${ITERATION} of ${ITERATION_CAP_HUMAN:-10} (human cap, total across bot+human)"
# The fix-budget label never tightens the human cap, so the human escape hatch
# always reports its full ITERATION_CAP_HUMAN budget.
HUMAN_CAP="${ITERATION_CAP_HUMAN:-10}"
echo " Iteration: ${ITERATION} of ${HUMAN_CAP} (human cap, total across bot+human)"
fi
17 changes: 16 additions & 1 deletion scripts/post-fix.src.sh
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ source "${SCRIPT_DIR_POST}/lib/gitleaks-install.lib.sh"
source "${SCRIPT_DIR_POST}/lib/precommit-gate.lib.sh"
# shellcheck source=lib/branch-guard.lib.sh
source "${SCRIPT_DIR_POST}/lib/branch-guard.lib.sh"
# shellcheck source=lib/fix-budget.lib.sh
source "${SCRIPT_DIR_POST}/lib/fix-budget.lib.sh"


# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -403,6 +405,16 @@ fi
# ---------------------------------------------------------------------------
ITERATION="${FIX_ITERATION:-1}"
BOT_CAP="${ITERATION_CAP:-5}"

# A per-PR `fullsend-fix-budget/N` label may tighten the BOT cap only (never
# raise it, never touch the human cap) — matching pre-fix. Mirror it here so the
# needs-human warning and the iteration summary reflect the cap pre-fix actually
# enforces. Without this, a budget of 2 under a global cap of 5 would report
# "2 of 5" and never add needs-human, even though pre-fix rejects the next cycle.
FIX_BUDGET="$(parse_fix_budget "${PR_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] Budget-aware needs-human mirror in post-fix has zero test coverage

Verified at head cc462ec. The added hunk at lines 409-418 computes FIX_BUDGET="$(parse_fix_budget "${PR_LABELS:-}")", tightens BOT_CAP when the budget is lower, and derives WARN_THRESHOLD=$(( BOT_CAP - 1 )). That threshold drives both the needs-human label application (lines 425-430) and the "Iteration: N of M (bot cap)" summary line — i.e. it is the entire mechanism preventing the "reports 2 of 5, never escalates" regression the surrounding comment describes.

scripts/post-fix-test.sh (1032 lines, and itself untouched by this PR) contains zero occurrences of PR_LABELS, "budget", or "needs-human" — confirmed by grep -c on the head blob (0/0/0). The new pre-fix-test.sh tests cover only the parser and pre-fix enforcement; nothing exercises the post-fix mirror. Deleting the three-line tightening block would leave every existing test green while silently restoring the exact bug the PR set out to prevent, and this is a boundary-sensitive computation (WARN_THRESHOLD=0 at budget 1).

Provenance, so this does not read as a re-raise: the coverage ask appears only inside the collapsed "Agent Prompt" of the bot comment anchored on scripts/pre-fix.src.sh:118, whose headline claim ("Post-fix ignores effective budget") has since been fixed by this very hunk — so that thread reads as addressed. An earlier review body noted a fifth candidate was "dropped as a duplicate", but that is an unanchored review-body remark, not a posted finding, and three pushes later the coverage still does not exist. No inline thread on scripts/post-fix.src.sh or scripts/post-fix-test.sh exists at all.

Suggestion. Add post-fix-test.sh cases mirroring the pre-fix ones:

  • (a) TRIGGER_SOURCE=<bot>, FIX_ITERATION=2, ITERATION_CAP=5, PR_LABELS=fullsend-fix-budget/2 asserts needs-human is applied and the summary reports "2 of 2", not "2 of 5";
  • (b) PR_LABELS=fullsend-fix-budget/9 with ITERATION_CAP=5 has no effect (label can only tighten);
  • (c) budget=1 pins the WARN_THRESHOLD=0 boundary so iteration 1 escalates;
  • (d) a human trigger with a budget label still reports the full ITERATION_CAP_HUMAN, matching the new comment at lines 441-444.

Malformed/absent labels should assert the unchanged global cap.

if [[ -n "${FIX_BUDGET}" && "${FIX_BUDGET}" -lt "${BOT_CAP}" ]]; then
BOT_CAP="${FIX_BUDGET}"
fi
WARN_THRESHOLD=$(( BOT_CAP - 1 ))

# The needs-human label is based on the bot cap — it signals that the
Expand All @@ -427,5 +439,8 @@ echo " Trigger: ${TRIGGER_SOURCE}"
if is_bot_user "${TRIGGER_SOURCE}"; then
echo " Iteration: ${ITERATION} of ${BOT_CAP} (bot cap)"
else
echo " Iteration: ${ITERATION} of ${ITERATION_CAP_HUMAN:-10} (human cap, total across bot+human)"
# The fix-budget label never tightens the human cap, so the human escape hatch
# always reports its full ITERATION_CAP_HUMAN budget.
HUMAN_CAP="${ITERATION_CAP_HUMAN:-10}"
echo " Iteration: ${ITERATION} of ${HUMAN_CAP} (human cap, total across bot+human)"
fi
29 changes: 10 additions & 19 deletions scripts/post-review-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -302,25 +302,11 @@ run_downgrade_test "approve-all-filtered-removes-findings" \
# ---------------------------------------------------------------------------
# Control-label guard tests
# ---------------------------------------------------------------------------

REVIEW_CONTROL_LABELS=(
"ready-for-merge" "requires-manual-review" "rejected"
"ready-for-review" "fullsend-no-fix" "fullsend-fix"
)

is_control_label() {
local label="$1"
for cl in "${REVIEW_CONTROL_LABELS[@]}"; do
if [[ "${cl}" == "${label}" ]]; then
return 0
fi
done
# Pipeline-managed label prefixes
if [[ "${label}" == risk/* ]]; then
return 0
fi
return 1
}
# Source the production is_control_label from its lib, so these tests fail if
# the production branch is removed or drifts — not a copy that always passes.
_LABELS_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib"
# shellcheck source=lib/review-labels.lib.sh
source "${_LABELS_LIB_DIR}/review-labels.lib.sh"

run_control_label_test() {
local test_name="$1"
Expand Down Expand Up @@ -360,8 +346,13 @@ run_control_label_test "risk-elevated-is-control" "risk/elevated" "true"
run_control_label_test "risk-high-is-control" "risk/high" "true"
run_control_label_test "risk-critical-is-control" "risk/critical" "true"

# Maintainer-set fix-budget labels should be control labels
run_control_label_test "fix-budget-3-is-control" "fullsend-fix-budget/3" "true"
run_control_label_test "fix-budget-99999-is-control" "fullsend-fix-budget/99999" "true"

# Non-control labels should NOT be recognized
run_control_label_test "area-api-not-control" "area/api" "false"
run_control_label_test "fix-budget-prefix-only-not-control" "fullsend-fix-budget" "false"
run_control_label_test "priority-high-not-control" "priority/high" "false"
run_control_label_test "bug-not-control" "bug" "false"
run_control_label_test "empty-not-control" "" "false"
Expand Down
Loading
Loading