Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
147 changes: 128 additions & 19 deletions .github/workflows/qwen-autofix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,13 @@ jobs:
needs.route.outputs.do_issue == 'true' &&
(github.event_name != 'schedule' || (needs.review-scan.result == 'success' && needs.review-scan.outputs.has_targets != 'true'))
}}
runs-on: 'ubuntu-latest'
# Secret-bearing and executes agent-driven code, but the agent runs inside
# the docker sandbox image (which the ECS pool ships) and only ever writes a
# new branch as the dev-bot - it never executes a foreign author's code.
# Forks of this repo (and MAINTAINER_ECS_RUNNER_DISABLED) fall back to
# hosted; PR-family events additionally need a same-repo head or a write+
# author, mirroring the fleet's ECS routing guard (ci.yml's classify_pr).
runs-on: '${{ (github.repository == ''QwenLM/qwen-code'' && vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true'' && (github.event_name != ''pull_request'' && github.event_name != ''pull_request_review'' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON(''["OWNER","MEMBER","COLLABORATOR"]''), github.event.pull_request.author_association))) && fromJSON(''["self-hosted", "linux", "x64", "ecs-qwen"]'') || fromJSON(''["ubuntu-latest"]'') }}'
timeout-minutes: 180
concurrency:
group: 'qwen-autofix-issue-${{ needs.route.outputs.issue_number || github.run_id }}'
Expand All @@ -667,12 +673,52 @@ jobs:
contents: 'read'
env:
REPO: '${{ github.repository }}'
WORKDIR: '/tmp/autofix'
# Per-run private dir: this pool carries many registrations sharing one
# OS /tmp, and issue-phase runs never serialize against each other, so
# a fixed path let concurrent runs clobber each other's decision files.
WORKDIR: '/tmp/autofix-${{ github.run_id }}'
EVENT_NAME: '${{ github.event_name }}'
READY_FOR_AGENT_LABEL: 'status/ready-for-agent'
AUTOFIX_APPROVED_LABEL: 'autofix/approved'
AUTOFIX_ISSUE_EXCLUDES: 'no:assignee -linked:pr -label:autofix/skip -label:autofix/in-progress -label:status/need-information -label:status/need-retesting sort:created-desc'
steps:
# Self-hosted runners reuse the workspace; a prior containerised job
# can leave root-owned, read-only files anywhere in it. Restore
# ownership and write permission unconditionally before checkout.
- name: 'Restore workspace ownership'
run: |-
set -uo pipefail
RUNNER_UID="$(id -u)"
RUNNER_GID="$(id -g)"
if [ "$RUNNER_UID" != "0" ]; then
chown -R "$RUNNER_UID:$RUNNER_GID" "$GITHUB_WORKSPACE" 2>/dev/null || sudo -n chown -R "$RUNNER_UID:$RUNNER_GID" "$GITHUB_WORKSPACE" || echo "::warning::could not restore workspace ownership; checkout may fail on leftover root-owned files"
fi
chmod -R u+rwX "$GITHUB_WORKSPACE" 2>/dev/null || sudo -n chmod -R u+rwX "$GITHUB_WORKSPACE" || echo "::warning::could not restore workspace write permissions; checkout may fail on leftover read-only files"

# Self-hosted runners keep the workspace between runs, and other pool
# jobs execute human-authored code as the runner user, so a prior job
# can plant git exec knobs (core.fsmonitor, filter.*.smudge,
# diff.external, includeIf, hooks) in the local config that would fire
# inside THIS job's PAT-bearing git steps. Keep a known-safe allowlist
# and unset everything else — the same pattern qwen-triage uses on
# this pool. No-op on a fresh hosted runner.
- name: 'Sanitize workspace git config'
run: |-
set -uo pipefail
# `.git` is a directory in a normal checkout but a gitlink file in
# a worktree; -e covers both, and a missing .git (first run) too.
if [ ! -e .git ]; then
echo "no prior workspace; nothing to sanitize"
exit 0
fi
git config --local --name-only --list 2>/dev/null \
| { grep -ivE '^(core\.(repositoryformatversion|bare|filemode|symlinks|ignorecase|precomposeunicode|logallrefupdates|worktree|hidedotfiles|protecthfs|protectntfs)|remote\.|branch\.|extensions\.|gc\.|pack\.|fetch\.|index\.|safe\.|submodule\.[^.]+\.(url|active|branch))' || true; } \
| while IFS= read -r key; do git config --local --unset-all "$key" 2>/dev/null || true; done
HOOKS_DIR="$(git rev-parse --git-path hooks 2>/dev/null || echo .git/hooks)"
# Match -type f OR -type l: a symlinked hook survives a bare
# `-type f` sweep and still fires on the next checkout.
find "$HOOKS_DIR" \( -type f -o -type l \) ! -name '*.sample' -delete 2>/dev/null || true

- name: 'Checkout'
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
with:
Expand All @@ -684,6 +730,17 @@ jobs:
rm -rf "${WORKDIR}"
mkdir -p "${WORKDIR}"

# Self-hosted runners keep the workspace's .git across runs, so a
# failed earlier attempt's local branch survives here: the agent's
# branch create then dies "branch already exists", or an adaptation
# checks out the stale line and pushes the failed attempt's commits
# into the new PR. Drop them deterministically (refs survive
# actions/checkout's untracked-file clean).
- name: 'Drop stale autofix branches'
run: |-
git for-each-ref --format='%(refname:short)' 'refs/heads/autofix/issue-*' \
| xargs -r -n 1 git branch -D 2>/dev/null || true

# Same staging as the review-address job: the verify gate always runs the
# trusted checkout's copy of the schema gate, never a working-tree copy.
- name: 'Stage trusted schema gate'
Expand Down Expand Up @@ -719,16 +776,14 @@ jobs:
RUNNER_ENVIRONMENT: '${{ runner.environment }}'
run: |-
case "${RUNNER_ENVIRONMENT}" in
github-hosted) ;;
github-hosted|self-hosted) ;;
Comment on lines 846 to +847

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed: the contract test now pins the accepted set — it asserts exactly two github-hosted|self-hosted) ;; case patterns (one per job that carries the step; build-cli has none). Reverting either to the hosted-only pattern now fails the suite.

*)
echo "::error::Unsupported runner environment: ${RUNNER_ENVIRONMENT:-unset}."
exit 1
;;
esac

- name: 'Set up Node.js (hosted)'
if: |-
${{ runner.environment == 'github-hosted' }}
- name: 'Set up Node.js'
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
with:
node-version: '22.x'
Expand Down Expand Up @@ -1260,7 +1315,7 @@ jobs:
uses: 'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' # v7.0.1
with:
name: 'autofix-issue-artifacts'
path: '/tmp/autofix/'
path: '/tmp/autofix-${{ github.run_id }}/'
if-no-files-found: 'ignore'

- name: 'Publish PR'
Expand Down Expand Up @@ -1297,9 +1352,12 @@ jobs:
fi
BRANCH="autofix/issue-${ISSUE}"
git config --local --unset-all http.https://github.com/.extraheader || true
git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${REPO}.git"
git config core.hooksPath /dev/null
git push --no-verify origin "${BRANCH}"
# Authenticate the push transiently, never writing the PAT into
# the origin remote: the pool keeps this workspace between runs,
# so a token persisted to .git/config would stay readable on the
# shared host until a later job wiped it.
git push --no-verify "https://x-access-token:${GITHUB_TOKEN}@github.com/${REPO}.git" "${BRANCH}"

PR_URL="$(gh pr create --repo "${REPO}" \
--base main --head "${BRANCH}" \
Expand Down Expand Up @@ -2604,23 +2662,34 @@ jobs:
needs: ['route', 'review-scan']
if: |-
${{ needs.review-scan.outputs.has_targets == 'true' }}
runs-on: 'ubuntu-latest'
runs-on: '${{ (github.repository == ''QwenLM/qwen-code'' && vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true'' && (github.event_name != ''pull_request'' && github.event_name != ''pull_request_review'' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON(''["OWNER","MEMBER","COLLABORATOR"]''), github.event.pull_request.author_association))) && fromJSON(''["self-hosted", "linux", "x64", "ecs-qwen"]'') || fromJSON(''["ubuntu-latest"]'') }}'
timeout-minutes: 30
permissions:
contents: 'read'
outputs:
base_sha: '${{ steps.meta.outputs.base_sha }}'
steps:
# Self-hosted runners reuse the workspace; a prior containerised job
# can leave root-owned, read-only files anywhere in it. Restore
# ownership and write permission unconditionally before checkout.
- name: 'Restore workspace ownership'
run: |-
set -uo pipefail
RUNNER_UID="$(id -u)"
RUNNER_GID="$(id -g)"
if [ "$RUNNER_UID" != "0" ]; then
chown -R "$RUNNER_UID:$RUNNER_GID" "$GITHUB_WORKSPACE" 2>/dev/null || sudo -n chown -R "$RUNNER_UID:$RUNNER_GID" "$GITHUB_WORKSPACE" || echo "::warning::could not restore workspace ownership; checkout may fail on leftover root-owned files"
fi
chmod -R u+rwX "$GITHUB_WORKSPACE" 2>/dev/null || sudo -n chmod -R u+rwX "$GITHUB_WORKSPACE" || echo "::warning::could not restore workspace write permissions; checkout may fail on leftover read-only files"

- name: 'Checkout trusted base'
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
with:
ref: '${{ github.event.repository.default_branch }}'
fetch-depth: 1
persist-credentials: false

- name: 'Set up Node.js (hosted)'
if: |-
${{ runner.environment == 'github-hosted' }}
- name: 'Set up Node.js'
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
with:
node-version: '22.x'
Expand Down Expand Up @@ -2692,7 +2761,13 @@ jobs:
needs: ['route', 'review-scan', 'build-cli']
if: |-
${{ needs.review-scan.outputs.has_targets == 'true' }}
runs-on: 'ubuntu-latest'
# Secret-bearing and executes PR code, but every target is live-gated to
# write+ (internal) authors at scan AND address time, and the ECS pool ships
# docker for the sandbox image. Forks of this repo (and
# MAINTAINER_ECS_RUNNER_DISABLED) fall back to hosted; PR-family events
# additionally need a same-repo head or a write+ author, mirroring the
# fleet's ECS routing guard (ci.yml's classify_pr).
runs-on: '${{ (github.repository == ''QwenLM/qwen-code'' && vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true'' && (github.event_name != ''pull_request'' && github.event_name != ''pull_request_review'' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON(''["OWNER","MEMBER","COLLABORATOR"]''), github.event.pull_request.author_association))) && fromJSON(''["self-hosted", "linux", "x64", "ecs-qwen"]'') || fromJSON(''["ubuntu-latest"]'') }}'
timeout-minutes: 300
permissions:
contents: 'read'
Expand Down Expand Up @@ -2778,6 +2853,43 @@ jobs:
exit 1
fi

# Self-hosted runners reuse the workspace; a prior containerised job
# can leave root-owned, read-only files anywhere in it. Restore
# ownership and write permission unconditionally before checkout.
- name: 'Restore workspace ownership'
run: |-
set -uo pipefail
RUNNER_UID="$(id -u)"
RUNNER_GID="$(id -g)"
if [ "$RUNNER_UID" != "0" ]; then
chown -R "$RUNNER_UID:$RUNNER_GID" "$GITHUB_WORKSPACE" 2>/dev/null || sudo -n chown -R "$RUNNER_UID:$RUNNER_GID" "$GITHUB_WORKSPACE" || echo "::warning::could not restore workspace ownership; checkout may fail on leftover root-owned files"
fi
chmod -R u+rwX "$GITHUB_WORKSPACE" 2>/dev/null || sudo -n chmod -R u+rwX "$GITHUB_WORKSPACE" || echo "::warning::could not restore workspace write permissions; checkout may fail on leftover read-only files"

# Self-hosted runners keep the workspace between runs, and other pool
# jobs execute human-authored code as the runner user, so a prior job
# can plant git exec knobs (core.fsmonitor, filter.*.smudge,
# diff.external, includeIf, hooks) in the local config that would fire
# inside THIS job's PAT-bearing git steps. Keep a known-safe allowlist
# and unset everything else — the same pattern qwen-triage uses on
# this pool. No-op on a fresh hosted runner.
- name: 'Sanitize workspace git config'
run: |-
set -uo pipefail
# `.git` is a directory in a normal checkout but a gitlink file in
# a worktree; -e covers both, and a missing .git (first run) too.
if [ ! -e .git ]; then
echo "no prior workspace; nothing to sanitize"
exit 0
fi
git config --local --name-only --list 2>/dev/null \
| { grep -ivE '^(core\.(repositoryformatversion|bare|filemode|symlinks|ignorecase|precomposeunicode|logallrefupdates|worktree|hidedotfiles|protecthfs|protectntfs)|remote\.|branch\.|extensions\.|gc\.|pack\.|fetch\.|index\.|safe\.|submodule\.[^.]+\.(url|active|branch))' || true; } \
| while IFS= read -r key; do git config --local --unset-all "$key" 2>/dev/null || true; done
HOOKS_DIR="$(git rev-parse --git-path hooks 2>/dev/null || echo .git/hooks)"
# Match -type f OR -type l: a symlinked hook survives a bare
# `-type f` sweep and still fires on the next checkout.
find "$HOOKS_DIR" \( -type f -o -type l \) ! -name '*.sample' -delete 2>/dev/null || true

- name: 'Checkout trusted base'
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
with:
Expand Down Expand Up @@ -2822,16 +2934,14 @@ jobs:
RUNNER_ENVIRONMENT: '${{ runner.environment }}'
run: |-
case "${RUNNER_ENVIRONMENT}" in
github-hosted) ;;
github-hosted|self-hosted) ;;
*)
echo "::error::Unsupported runner environment: ${RUNNER_ENVIRONMENT:-unset}."
exit 1
;;
esac

- name: 'Set up Node.js (hosted)'
if: |-
${{ runner.environment == 'github-hosted' }}
- name: 'Set up Node.js'
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
with:
node-version: '22.x'
Expand Down Expand Up @@ -3900,7 +4010,6 @@ jobs:
if [[ "${OUTCOME}" == "fixed" ]]; then
NEXT_ROUND="$(( ROUND + 1 ))"
git config --local --unset-all http.https://github.com/.extraheader || true
git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${REPO}.git"
# This step carries the PAT; the branch carries PR-controlled
# .husky hooks (hooksPath was pointed there so the AGENT's
# commits get checked). A pre-push hook would execute that code
Expand Down
Loading
Loading