Repository navigation
feat: allow per-PR fix-loop budget via label #1042
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
0c6fdef
e8cc242
cc462ec
0d5fc9e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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. | | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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". 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 | ||
|
|
||
|
|
@@ -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 | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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}" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [MEDIUM] Activation recipe prescribes an This comment tells the follow-up author to activate the label by re-adding
This refines the suggestion in the earlier CRITICAL thread (which proposed re-adding the Suggestion. Rewrite the comment to state that the workflow step env alone is sufficient for pre-/post-fix ( |
||
| # 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}" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| #!/usr/bin/env bash | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 6. Protected scripts require human approval 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
|
||
| # 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 5. Feature lacks linked authorization 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
|
||
| # 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 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [low] fail-open A malformed Suggested fix: Optional: emit a |
||
| if [[ -z "${best}" || "${n}" -lt "${best}" ]]; then | ||
| best="${n}" | ||
| fi | ||
| done <<< "${labels}" | ||
| [[ -n "${best}" ]] && printf '%s\n' "${best}" | ||
| return 0 | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| #!/usr/bin/env bash | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [low] scope-creep The PR moves 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 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [medium] auth-bypass is_control_label matches Suggested fix: Compare control labels case-insensitively (e.g. lowercase with |
||
| return 0 | ||
| fi | ||
| return 1 | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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" | ||
|
|
||
|
|
||
| # --------------------------------------------------------------------------- | ||
|
|
@@ -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:-}")" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
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 Suggestion. Add
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 | ||
|
|
@@ -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 | ||
There was a problem hiding this comment.
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_CAPlocal 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).