Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
ff76452
fix(autofix): make the brake's BLOCKED handoff a first-class round ou…
wenshao Aug 17, 2026
3d2a046
Merge branch 'main' into fix/autofix-handoff-output-contract
qwen-code-dev-bot Aug 17, 2026
ed1ea96
fix(autofix): align the handoff outcome's consumers and pins with its…
qwen-code-dev-bot Aug 17, 2026
5c766ff
fix(autofix): classify a no-commit handoff before the gate's structur…
qwen-code-dev-bot Aug 17, 2026
e5a3106
fix(autofix): reject a no-commit handoff written over a dirty workspa…
qwen-code-dev-bot Aug 17, 2026
c0f1c75
fix(autofix): report a dirty-handoff rejection honestly, not as a fai…
qwen-code-dev-bot Aug 18, 2026
57664b9
Merge branch 'main' into fix/autofix-handoff-output-contract
qwen-code-dev-bot Aug 18, 2026
ef859bd
fix(autofix): reject a handoff written beside a round commit, non-ret…
qwen-code-dev-bot Aug 18, 2026
d8d4845
fix(ci): classify the committed handoff shape as its own non-retryabl…
wenshao Aug 18, 2026
5edd7f8
Merge remote-tracking branch 'wenshao/fix/autofix-handoff-output-cont…
wenshao Aug 18, 2026
9901ba8
fix(ci): count committed-handoff rounds in the milestone census rejec…
qwen-code-dev-bot Aug 18, 2026
8972874
fix(autofix): publish brake violations green and preserve handoffs ac…
qwen-code-dev-bot Aug 18, 2026
f1f2d17
test(ci): give four subprocess-heavy replays explicit budgets
wenshao Aug 19, 2026
a4fa22f
Merge remote-tracking branch 'origin/main' into fix/autofix-handoff-o…
wenshao Aug 19, 2026
cc3a05d
fix(ci): mirror the handoff outcome consumers into the recovery clone…
qwen-code-dev-bot Aug 20, 2026
955a32c
Merge remote-tracking branch 'origin/main' into fix/autofix-handoff-o…
wenshao Aug 20, 2026
8fb2ed0
Merge remote-tracking branch 'origin/main' into fix/autofix-handoff-o…
wenshao Aug 20, 2026
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
14 changes: 13 additions & 1 deletion .github/scripts/run-autofix-review-verification.sh
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,19 @@ run_check_no_ab 'cross-package contract verification failed' \
assert_verification_tree

if git diff --quiet "origin/${BRANCH}...${BRANCH}"; then
Comment thread
wenshao marked this conversation as resolved.
Resolved
# No new commit. That is only legitimate as a deliberate no-action.
# No new commit. A handoff with no failure.md is the deliberate kind: the
# agent stopped under instruction (the growth-brake BLOCKED stop) and
# deferred this item to a human. Name it — "produced nothing" is what a
# crash looks like, and the report must tell the two apart. (When
# failure.md coexists, the round came through a failure path — harness
# death or a pre-verdict crash — and keeps the failed classification.)
if [[ -s "${WORKDIR}/handoff.md" && ! -s "${WORKDIR}/failure.md" ]]; then
Comment thread
wenshao marked this conversation as resolved.
Resolved
Comment thread
wenshao marked this conversation as resolved.
Resolved
echo "🤝 Branch unchanged with a handoff — the agent stopped under instruction and deferred this item to a human:"
cat "${WORKDIR}/handoff.md"
Comment thread
wenshao marked this conversation as resolved.
Resolved
Comment thread
wenshao marked this conversation as resolved.
Resolved
echo "outcome=handoff" >> "${GITHUB_OUTPUT}"
exit 0
fi
# Otherwise no commit is only legitimate as a deliberate no-action.
if [[ -s "${WORKDIR}/no-action.md" ]]; then
echo "🟰 No action needed:"
cat "${WORKDIR}/no-action.md"
Expand Down
29 changes: 25 additions & 4 deletions .github/workflows/qwen-autofix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5768,7 +5768,14 @@ jobs:
echo "verified_head=${VERIFIED_HEAD}" >> "${GITHUB_OUTPUT}"
fi
case "${OUTCOME}" in
fixed|noop) ;;
# handoff is a deliberate verdict, not a failure: the agent
# stopped under instruction (the growth-brake BLOCKED stop) and
# deferred the item to a human. Failing the job here would leave
# a red review-address check, which the next scan counts as NEW
# feedback — turning one deliberate stop into a self-feeding
# loop. The 'Report dry-run / failure' step still posts the
# handoff comment and the eval marker for this outcome.
fixed|noop|handoff) ;;
Comment thread
wenshao marked this conversation as resolved.
Resolved
Comment thread
wenshao marked this conversation as resolved.
Resolved
Comment thread
wenshao marked this conversation as resolved.
Resolved
*) exit 1 ;;
esac

Expand Down Expand Up @@ -6472,7 +6479,7 @@ jobs:

- name: 'Report dry-run / failure'
if: |-
${{ always() && (needs.route.outputs.dry_run == 'true' || failure() || cancelled()) }}
${{ always() && (needs.route.outputs.dry_run == 'true' || failure() || cancelled() || steps.final_verify.outputs.outcome == 'handoff') }}
Comment thread
wenshao marked this conversation as resolved.
Resolved
Comment thread
wenshao marked this conversation as resolved.
Resolved
env:
OUTCOME: '${{ steps.final_verify.outputs.outcome }}'
COMMITTED: '${{ steps.final_verify.outputs.committed }}'
Expand Down Expand Up @@ -6561,7 +6568,11 @@ jobs:
# consumes a round.
POST_HANDOFF=false
if [[ "${DRY_RUN}" != "true" && "${STALE:-}" != "true" && -n "${GITHUB_TOKEN:-}" && "${OUTCOME:-unknown}" != "fixed" && "${OUTCOME:-unknown}" != "noop" ]]; then
if [[ "${OUTCOME:-unknown}" == "failed" || "${JOB_STATUS:-}" != "success" ]]; then
# handoff rounds end with a SUCCESS job status (a deliberate
# verdict), so they must trigger on the outcome itself — without
# this clause nothing would post and the loop would go silent on
# exactly the rounds that most need a visible human handoff.
if [[ "${OUTCOME:-unknown}" == "failed" || "${OUTCOME:-unknown}" == "handoff" || "${JOB_STATUS:-}" != "success" ]]; then
POST_HANDOFF=true
fi
fi
Expand Down Expand Up @@ -6735,6 +6746,15 @@ jobs:
MARK_ROUND="${MAX_ROUNDS}"
fi
else
if [[ "${OUTCOME:-}" == 'handoff' ]]; then
# A deliberate stop, not a failed fix: the agent stopped
# under instruction and deferred the item to a human. No
# stale-base retry (there is no fix to re-attempt) and the
# headline says what actually happened — the old
# "could not produce a passing fix" wording reported the
# brake's decision as a failure and buried the handoff.
HEADLINE="🤖 AutoFix stopped this item under instruction and deferred it to a human (round ${MARK_ROUND}/${MAX_ROUNDS}) — the agent's handoff note below names the decision and the options. The loop stays engaged and still picks up new feedback and base conflicts, but will not retry this item on its own."
Comment thread
wenshao marked this conversation as resolved.
Resolved
Comment thread
wenshao marked this conversation as resolved.
Resolved
Comment thread
wenshao marked this conversation as resolved.
Resolved
else
# The gate ran and rejected the agent's fix (a build/test
# failure). Before handing to a human, check whether the PR is
# merely BEHIND main: a build that fails on something main
Expand Down Expand Up @@ -6804,6 +6824,7 @@ jobs:
fi
HEADLINE="🤖 Could not produce a passing fix for this feedback (round ${MARK_ROUND}/${MAX_ROUNDS})${GATE_CLAUSE}. This item now needs a human; the loop stays engaged and still picks up new feedback and base conflicts, but will not retry this item on its own."
fi
fi
fi
elif [[ "${PREPARE_OUTCOME}" != 'success' && "${PREPARE_OUTCOME}" != 'failure' ]]; then
# NEWEST is empty because Prepare never RAN TO A VERDICT — an
Expand Down Expand Up @@ -6906,7 +6927,7 @@ jobs:
done <<< "${PRIOR_HEADS}"
if [[ "${CONSEC_FAIL}" -ge "${CONSECUTIVE_FAILURE_CAP}" ]]; then
MARK_ROUND="${MAX_ROUNDS}"
HEADLINE="🤖 AutoFix stopped after ${CONSEC_FAIL} consecutive rounds that failed to push anything (timeouts and/or gate rejections). Retrying at the same per-round budget is not converging — this usually means the PR is too large or conflicts with a fast-moving \`main\`. A human should rebase, split, or reduce it, then comment \`${RETRY_COMMAND}\` to re-arm. Until then future scans will skip this PR."
HEADLINE="🤖 AutoFix stopped after ${CONSEC_FAIL} consecutive rounds that pushed nothing (failed rounds, timeouts, gate rejections, or stops under instruction). Retrying at the same per-round budget is not converging — this usually means the PR is too large or conflicts with a fast-moving \`main\`. A human should rebase, split, or reduce it, then comment \`${RETRY_COMMAND}\` to re-arm. Until then future scans will skip this PR."
Comment thread
wenshao marked this conversation as resolved.
Resolved
Comment thread
wenshao marked this conversation as resolved.
Resolved
fi
# CUMULATIVE timeout breaker — the sibling of the consecutive
# one above, for the failure shape it cannot see: timeouts
Expand Down
12 changes: 10 additions & 2 deletions .qwen/skills/autofix/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -359,8 +359,14 @@ silently overriding or silently complying.
handoff that names the decision and lays out the options — split the PR (land
the core, track the remaining findings as follow-up issues), redesign, or
accept the current state with the tail deferred — plus your recommendation.
Continuing to patch, or deciding the split yourself, is exactly the wrong
move; the call is the maintainer's.
Write that handoff to `<workdir>/handoff.md` — English-only, no details
block — naming the decision, the options, your recommendation, and what was
tried; then stop without writing anything else: no commit, no
`address-summary.md`, no `no-action.md`, no `failure.md`. The harness
recognizes a handoff with no fix verdict as a deliberate deferral: the round
ends cleanly, the note is posted to the PR, and the item waits for the
maintainer instead of being re-run. Continuing to patch, or deciding the
split yourself, is exactly the wrong move; the call is the maintainer's.
- Needs a maintainer's decision: a finding that turns on a judgment that is
NOT yours to make — a product or scope tradeoff (is this acceptable for v1?
should the PR be split?), two reviewers asking for opposite things, or whether
Expand Down Expand Up @@ -506,5 +512,7 @@ Finish with exactly one outcome:
answered when you escalated. Each body is bilingual per GitHub Actions Rules.
Omit the file when every inline finding was resolved.
- No change: write `<workdir>/no-action.md` (bilingual per GitHub Actions Rules).
- Stopped by the growth brake: write `<workdir>/handoff.md` per the
not-converging rule (English-only, no details block) — and commit nothing.
- The GitHub Actions Rules' objective stop condition applies: write
`<workdir>/failure.md` and do not commit.
19 changes: 18 additions & 1 deletion .qwen/skills/autofix/scripts/run-agent.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,10 @@ const apiErrorWithoutVerdict =
result.status === 0 &&
result.apiError &&
!hasOutputVerdict &&
!existsSync(file(options.workdir, 'failure.md'));
!existsSync(file(options.workdir, 'failure.md')) &&
// A handoff is a verdict too: an agent that reached its BLOCKED stop must
// not be reclassified as a bare API blip by an error render in the tail.
!existsSync(file(options.workdir, 'handoff.md'));
if (
result.error ||
result.signal ||
Expand Down Expand Up @@ -586,6 +589,20 @@ if (existsSync(file(options.workdir, 'failure.md'))) {
process.exit(0);
}

// A handoff the AGENT itself wrote — the growth-brake BLOCKED stop: the
// feedback told it to defer to a human, so the round ends without a fix
// verdict. Same standing as failure.md: a real, human-facing outcome, NOT
// the missing-output failure class (which once reported a deliberate stop as
// "finished without required output file(s)" and buried the brake's decision
// under a generic failure.md). Honored only when no spec output exists: a
// round that also wrote address-summary.md or no-action.md published a
// result, and that verdict outranks the handoff.
if (!hasOutputVerdict && existsSync(file(options.workdir, 'handoff.md'))) {
Comment thread
wenshao marked this conversation as resolved.
Resolved
const content = readFileSync(file(options.workdir, 'handoff.md'), 'utf8');
console.error(`Autofix agent wrote handoff.md:\n${content}`);
Comment thread
wenshao marked this conversation as resolved.
Resolved
Comment thread
wenshao marked this conversation as resolved.
Resolved
process.exit(0);
}

if (spec.exclusiveOutput && presentOutputs.length > 1) {
const message = `Autofix agent wrote mutually exclusive output files: ${presentOutputs.join(', ')}.`;
writeFailure(options.workdir, message);
Expand Down
Loading