Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
46a71e6
fix(core): propagate filesystem cleanup failures in team_delete
yiliang114 Aug 26, 2026
6c2a26f
fix(core): wrap team_delete cleanup in try/catch and aggregate errors
Aug 27, 2026
60fc496
chore: merge main for runner cleanup fix
yiliang114 Aug 28, 2026
7cbd694
fix(ci): clean protected qwen leftovers before checkout
yiliang114 Aug 28, 2026
278bfe0
Merge branch 'main' into fix/issue-10210
yiliang114 Aug 28, 2026
1d639c7
fix(ci): repin the triage cleanup assertion to the stale_qwen loop
yiliang114 Aug 28, 2026
9204b17
test(ci): cover the sweep's dangling-symlink existence arm
yiliang114 Aug 28, 2026
4870dab
chore: merge main for PR closeout
yiliang114 Aug 28, 2026
5a65f64
fix(ci): recover review workspace before checkout
yiliang114 Aug 28, 2026
10cc3c7
test(ci): pin both symlink guard arms on every pre-checkout sweep copy
yiliang114 Aug 28, 2026
d136e28
docs(ci): name the external producer of the .qwen.root-orig residue
yiliang114 Aug 28, 2026
c2750e9
test(ci): execute both pre-checkout sweep copies in the behavioral fi…
yiliang114 Aug 28, 2026
c056c9d
test(ci): run quarantine fixtures over both sweep copies
yiliang114 Aug 28, 2026
86b0ddf
test(ci): correct stale mutation-coverage comment on the guard pins
yiliang114 Aug 28, 2026
6aba60f
test(ci): gate the pre-checkout sweep fixture on sweep tool capability
yiliang114 Aug 28, 2026
bf2c4ae
chore: merge main for runner cleanup closeout
yiliang114 Aug 29, 2026
5fc148e
fix(ci): quarantine poisoned workspaces before checkout
yiliang114 Aug 29, 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
4 changes: 2 additions & 2 deletions .github/scripts/qwen-triage-workflow.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -682,8 +682,8 @@ describe('ci.yml: self-hosted checkout jobs restore ownership unconditionally',
);
assert.match(
ciCleanStep.run,
/\[ ! -L "\$GITHUB_WORKSPACE\/\.qwen" \]/,
'cleanup must not follow a symlinked .qwen: chmod -R dereferences a symlinked argument and would widen an outside tree',
/\[ -d "\$stale_qwen" \] && \[ ! -L "\$stale_qwen" \]/,
'cleanup must not follow a symlinked qwen state: chmod -R dereferences a symlinked argument and would widen an outside tree',
);
assert.doesNotMatch(
ciCleanStep.run,
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/.size-baseline
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
9256 build-and-publish-image.yml
49610 cd-cua-driver.yml
2076 cd-mobile-mcp.yml
95501 ci.yml
98043 ci.yml
1482 codeql.yml
9389 comment-attachment-guard.yml
31677 desktop-release.yml
Expand All @@ -36,7 +36,7 @@
5942 qwen-autofix-fork-signal.yml
454547 qwen-autofix.yml
7061 qwen-ci-flaky-rerun.yml
168310 qwen-code-pr-review.yml
173139 qwen-code-pr-review.yml
79041 qwen-fleet-shepherd.yml
20525 qwen-issue-followup-bot.yml
5760 qwen-pr-safety-precheck.yml
Expand Down
209 changes: 129 additions & 80 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -235,37 +235,55 @@ jobs:
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"

# Remove .qwen/ left by a previous job so actions/checkout doesn't trip
# on restrictive permissions.
# Remove .qwen/ and its recovery backup left by a previous job so
Comment thread
yiliang114 marked this conversation as resolved.
# actions/checkout doesn't trip on restrictive permissions.
# `.qwen.root-orig` is emitted by recovery tooling OUTSIDE this repo —
# nothing here produces it (git grep matches only these sweep copies
# and their pins in scripts/tests/review-worktree-cleanup-workflow.
# test.js). It is the backup name a cancelled verify's recovery leaves
# after renaming an unreadable, root-owned `.qwen` aside (observed on
# the shared pool; first recorded around review run 33146730771). If
# that producer's naming changes or a third residue name appears,
# update the for-loop list in every sweep copy or the checkout
# poisoning this sweep exists for silently recurs.
- name: 'Clean stale .qwen before checkout'
if: "${{ needs.classify_pr.outputs.skip_ci != 'true' }}"
run: |-
set -uo pipefail
if [ -d "$GITHUB_WORKSPACE/.qwen" ] && [ ! -L "$GITHUB_WORKSPACE/.qwen" ]; then
chmod -R u+w "$GITHUB_WORKSPACE/.qwen" 2>/dev/null || true
# Last resort when nothing can DELETE the tree: move it out of the
# workspace. Unlinking an entry needs write permission on the
# directory holding it — which is exactly what a foreign-owned
# leftover denies — while renaming needs it only on the two
# parents, and the workspace root is always this runner's own. So
# a tree that defeats rm, chmod, and a sudo-less chown still
# renames aside, and the checkout below finds nothing to trip on.
# Leaving it in place instead poisons EVERY later job scheduled
# here, not just this one (measured, run 32621267802: `EACCES
# rmdir .qwen/tmp/review-pr-9748-scratch-verify-…/probe-ws/…`
# killed checkout for two unrelated PRs on the same runner).
rm -rf "$GITHUB_WORKSPACE/.qwen" 2>/dev/null ||
sudo -n rm -rf "$GITHUB_WORKSPACE/.qwen" 2>/dev/null ||
for stale_qwen in "$GITHUB_WORKSPACE/.qwen" "$GITHUB_WORKSPACE/.qwen.root-orig"; do
Comment thread
yiliang114 marked this conversation as resolved.
Comment thread
yiliang114 marked this conversation as resolved.
if [ ! -e "$stale_qwen" ] && [ ! -L "$stale_qwen" ]; then
continue
fi
if [ -d "$stale_qwen" ] && [ ! -L "$stale_qwen" ]; then
Comment thread
yiliang114 marked this conversation as resolved.
chmod -R u+w "$stale_qwen" 2>/dev/null || true
fi
# A foreign-owned directory cannot always be renamed to a
# different parent: updating its .. entry can fail even when the
# workspace parent is writable. If that individual move fails,
# quarantine the runner-owned workspace itself, then recreate the
# empty checkout root. Warm contents are lost only on this
# otherwise unrecoverable path.
rm -rf -- "$stale_qwen" 2>/dev/null ||
sudo -n rm -rf -- "$stale_qwen" 2>/dev/null ||
{
quarantine="$(dirname -- "$GITHUB_WORKSPACE")/_qwen-quarantine"
mkdir -p "$quarantine" 2>/dev/null || true
if mv -- "$GITHUB_WORKSPACE/.qwen" "$quarantine/qwen-$(date -u +%Y%m%dT%H%M%SZ)-$$" 2>/dev/null; then
echo "::warning::could not delete leaked .qwen; moved it to $quarantine so this checkout can proceed — that directory needs manual cleanup"
stale_name="$(basename -- "$stale_qwen")"
if mv -- "$stale_qwen" "$quarantine/${stale_name#\.}-$(date -u +%Y%m%dT%H%M%SZ)-$$" 2>/dev/null; then
echo "::warning::could not delete leaked $stale_name; moved it to $quarantine so this checkout can proceed — that directory needs manual cleanup"
else
echo "::warning::leaked .qwen; runner needs manual cleanup"
workspace_quarantine="$quarantine/workspace-$(date -u +%Y%m%dT%H%M%SZ)-$$"
if mv -- "$GITHUB_WORKSPACE" "$workspace_quarantine" 2>/dev/null &&
mkdir -p "$GITHUB_WORKSPACE" 2>/dev/null &&
cd "$GITHUB_WORKSPACE"; then
echo "::warning::could not delete leaked $stale_name; moved the whole workspace to $workspace_quarantine so this checkout can proceed — that directory needs manual cleanup"
break
else
echo "::warning::leaked $stale_name survived every recovery; runner needs manual cleanup"
fi
fi
}
fi
done
# Interrupted reviews leave worktree registrations under .qwen/tmp/
# and qwen-review/* branches behind. prune drops registrations whose
# directories the rm above removed; worktree remove --force then
Expand Down Expand Up @@ -715,35 +733,46 @@ jobs:

# Same pre-checkout recovery as the test job: this job lands on the
# same reused pool, so leftover review worktrees and branches from an
# interrupted review would break this checkout too.
# interrupted review would break this checkout too. The
# `.qwen.root-orig` name's provenance (an external recovery tool) is
# documented on the test job's copy.
- name: 'Clean stale .qwen before checkout'
run: |-
set -uo pipefail
if [ -d "$GITHUB_WORKSPACE/.qwen" ] && [ ! -L "$GITHUB_WORKSPACE/.qwen" ]; then
chmod -R u+w "$GITHUB_WORKSPACE/.qwen" 2>/dev/null || true
# Last resort when nothing can DELETE the tree: move it out of the
# workspace. Unlinking an entry needs write permission on the
# directory holding it — which is exactly what a foreign-owned
# leftover denies — while renaming needs it only on the two
# parents, and the workspace root is always this runner's own. So
# a tree that defeats rm, chmod, and a sudo-less chown still
# renames aside, and the checkout below finds nothing to trip on.
# Leaving it in place instead poisons EVERY later job scheduled
# here, not just this one (measured, run 32621267802: `EACCES
# rmdir .qwen/tmp/review-pr-9748-scratch-verify-…/probe-ws/…`
# killed checkout for two unrelated PRs on the same runner).
rm -rf "$GITHUB_WORKSPACE/.qwen" 2>/dev/null ||
sudo -n rm -rf "$GITHUB_WORKSPACE/.qwen" 2>/dev/null ||
for stale_qwen in "$GITHUB_WORKSPACE/.qwen" "$GITHUB_WORKSPACE/.qwen.root-orig"; do
if [ ! -e "$stale_qwen" ] && [ ! -L "$stale_qwen" ]; then
continue
fi
if [ -d "$stale_qwen" ] && [ ! -L "$stale_qwen" ]; then
chmod -R u+w "$stale_qwen" 2>/dev/null || true
fi
# A foreign-owned directory cannot always be renamed to a
# different parent: updating its .. entry can fail even when the
# workspace parent is writable. If that individual move fails,
# quarantine the runner-owned workspace itself, then recreate the
# empty checkout root. Warm contents are lost only on this
# otherwise unrecoverable path.
rm -rf -- "$stale_qwen" 2>/dev/null ||
sudo -n rm -rf -- "$stale_qwen" 2>/dev/null ||
{
quarantine="$(dirname -- "$GITHUB_WORKSPACE")/_qwen-quarantine"
mkdir -p "$quarantine" 2>/dev/null || true
if mv -- "$GITHUB_WORKSPACE/.qwen" "$quarantine/qwen-$(date -u +%Y%m%dT%H%M%SZ)-$$" 2>/dev/null; then
echo "::warning::could not delete leaked .qwen; moved it to $quarantine so this checkout can proceed — that directory needs manual cleanup"
stale_name="$(basename -- "$stale_qwen")"
if mv -- "$stale_qwen" "$quarantine/${stale_name#\.}-$(date -u +%Y%m%dT%H%M%SZ)-$$" 2>/dev/null; then
echo "::warning::could not delete leaked $stale_name; moved it to $quarantine so this checkout can proceed — that directory needs manual cleanup"
else
echo "::warning::leaked .qwen; runner needs manual cleanup"
workspace_quarantine="$quarantine/workspace-$(date -u +%Y%m%dT%H%M%SZ)-$$"
if mv -- "$GITHUB_WORKSPACE" "$workspace_quarantine" 2>/dev/null &&
mkdir -p "$GITHUB_WORKSPACE" 2>/dev/null &&
cd "$GITHUB_WORKSPACE"; then
echo "::warning::could not delete leaked $stale_name; moved the whole workspace to $workspace_quarantine so this checkout can proceed — that directory needs manual cleanup"
break
else
echo "::warning::leaked $stale_name survived every recovery; runner needs manual cleanup"
fi
fi
}
fi
done
# Interrupted reviews leave worktree registrations under .qwen/tmp/
# and qwen-review/* branches behind. prune drops registrations whose
# directories the rm above removed; worktree remove --force then
Expand Down Expand Up @@ -1262,31 +1291,40 @@ jobs:
- name: 'Clean stale .qwen before checkout'
run: |-
set -uo pipefail
if [ -d "$GITHUB_WORKSPACE/.qwen" ] && [ ! -L "$GITHUB_WORKSPACE/.qwen" ]; then
chmod -R u+w "$GITHUB_WORKSPACE/.qwen" 2>/dev/null || true
# Last resort when nothing can DELETE the tree: move it out of the
# workspace. Unlinking an entry needs write permission on the
# directory holding it — which is exactly what a foreign-owned
# leftover denies — while renaming needs it only on the two
# parents, and the workspace root is always this runner's own. So
# a tree that defeats rm, chmod, and a sudo-less chown still
# renames aside, and the checkout below finds nothing to trip on.
# Leaving it in place instead poisons EVERY later job scheduled
# here, not just this one (measured, run 32621267802: `EACCES
# rmdir .qwen/tmp/review-pr-9748-scratch-verify-…/probe-ws/…`
# killed checkout for two unrelated PRs on the same runner).
rm -rf "$GITHUB_WORKSPACE/.qwen" 2>/dev/null ||
sudo -n rm -rf "$GITHUB_WORKSPACE/.qwen" 2>/dev/null ||
for stale_qwen in "$GITHUB_WORKSPACE/.qwen" "$GITHUB_WORKSPACE/.qwen.root-orig"; do
if [ ! -e "$stale_qwen" ] && [ ! -L "$stale_qwen" ]; then
continue
fi
if [ -d "$stale_qwen" ] && [ ! -L "$stale_qwen" ]; then
chmod -R u+w "$stale_qwen" 2>/dev/null || true
fi
# A foreign-owned directory cannot always be renamed to a
# different parent: updating its .. entry can fail even when the
# workspace parent is writable. If that individual move fails,
# quarantine the runner-owned workspace itself, then recreate the
# empty checkout root. Warm contents are lost only on this
# otherwise unrecoverable path.
rm -rf -- "$stale_qwen" 2>/dev/null ||
sudo -n rm -rf -- "$stale_qwen" 2>/dev/null ||
{
quarantine="$(dirname -- "$GITHUB_WORKSPACE")/_qwen-quarantine"
mkdir -p "$quarantine" 2>/dev/null || true
if mv -- "$GITHUB_WORKSPACE/.qwen" "$quarantine/qwen-$(date -u +%Y%m%dT%H%M%SZ)-$$" 2>/dev/null; then
echo "::warning::could not delete leaked .qwen; moved it to $quarantine so this checkout can proceed — that directory needs manual cleanup"
stale_name="$(basename -- "$stale_qwen")"
if mv -- "$stale_qwen" "$quarantine/${stale_name#\.}-$(date -u +%Y%m%dT%H%M%SZ)-$$" 2>/dev/null; then
echo "::warning::could not delete leaked $stale_name; moved it to $quarantine so this checkout can proceed — that directory needs manual cleanup"
else
echo "::warning::leaked .qwen; runner needs manual cleanup"
workspace_quarantine="$quarantine/workspace-$(date -u +%Y%m%dT%H%M%SZ)-$$"
if mv -- "$GITHUB_WORKSPACE" "$workspace_quarantine" 2>/dev/null &&
mkdir -p "$GITHUB_WORKSPACE" 2>/dev/null &&
cd "$GITHUB_WORKSPACE"; then
echo "::warning::could not delete leaked $stale_name; moved the whole workspace to $workspace_quarantine so this checkout can proceed — that directory needs manual cleanup"
break
else
echo "::warning::leaked $stale_name survived every recovery; runner needs manual cleanup"
fi
fi
}
fi
done
# Interrupted reviews leave worktree registrations under .qwen/tmp/
# and qwen-review/* branches behind. prune drops registrations whose
# directories the rm above removed; worktree remove --force then
Expand Down Expand Up @@ -1480,35 +1518,46 @@ jobs:

# Same pre-checkout recovery as the test job: this job lands on the
# same reused pool, so leftover review worktrees and branches from an
# interrupted review would break this checkout too.
# interrupted review would break this checkout too. The
# `.qwen.root-orig` name's provenance (an external recovery tool) is
# documented on the test job's copy.
- name: 'Clean stale .qwen before checkout'
run: |-
set -uo pipefail
if [ -d "$GITHUB_WORKSPACE/.qwen" ] && [ ! -L "$GITHUB_WORKSPACE/.qwen" ]; then
chmod -R u+w "$GITHUB_WORKSPACE/.qwen" 2>/dev/null || true
# Last resort when nothing can DELETE the tree: move it out of the
# workspace. Unlinking an entry needs write permission on the
# directory holding it — which is exactly what a foreign-owned
# leftover denies — while renaming needs it only on the two
# parents, and the workspace root is always this runner's own. So
# a tree that defeats rm, chmod, and a sudo-less chown still
# renames aside, and the checkout below finds nothing to trip on.
# Leaving it in place instead poisons EVERY later job scheduled
# here, not just this one (measured, run 32621267802: `EACCES
# rmdir .qwen/tmp/review-pr-9748-scratch-verify-…/probe-ws/…`
# killed checkout for two unrelated PRs on the same runner).
rm -rf "$GITHUB_WORKSPACE/.qwen" 2>/dev/null ||
sudo -n rm -rf "$GITHUB_WORKSPACE/.qwen" 2>/dev/null ||
for stale_qwen in "$GITHUB_WORKSPACE/.qwen" "$GITHUB_WORKSPACE/.qwen.root-orig"; do
if [ ! -e "$stale_qwen" ] && [ ! -L "$stale_qwen" ]; then
continue
fi
if [ -d "$stale_qwen" ] && [ ! -L "$stale_qwen" ]; then
chmod -R u+w "$stale_qwen" 2>/dev/null || true
fi
# A foreign-owned directory cannot always be renamed to a
# different parent: updating its .. entry can fail even when the
# workspace parent is writable. If that individual move fails,
# quarantine the runner-owned workspace itself, then recreate the
# empty checkout root. Warm contents are lost only on this
# otherwise unrecoverable path.
rm -rf -- "$stale_qwen" 2>/dev/null ||
sudo -n rm -rf -- "$stale_qwen" 2>/dev/null ||
{
quarantine="$(dirname -- "$GITHUB_WORKSPACE")/_qwen-quarantine"
mkdir -p "$quarantine" 2>/dev/null || true
if mv -- "$GITHUB_WORKSPACE/.qwen" "$quarantine/qwen-$(date -u +%Y%m%dT%H%M%SZ)-$$" 2>/dev/null; then
echo "::warning::could not delete leaked .qwen; moved it to $quarantine so this checkout can proceed — that directory needs manual cleanup"
stale_name="$(basename -- "$stale_qwen")"
if mv -- "$stale_qwen" "$quarantine/${stale_name#\.}-$(date -u +%Y%m%dT%H%M%SZ)-$$" 2>/dev/null; then
echo "::warning::could not delete leaked $stale_name; moved it to $quarantine so this checkout can proceed — that directory needs manual cleanup"
else
echo "::warning::leaked .qwen; runner needs manual cleanup"
workspace_quarantine="$quarantine/workspace-$(date -u +%Y%m%dT%H%M%SZ)-$$"
if mv -- "$GITHUB_WORKSPACE" "$workspace_quarantine" 2>/dev/null &&
mkdir -p "$GITHUB_WORKSPACE" 2>/dev/null &&
cd "$GITHUB_WORKSPACE"; then
echo "::warning::could not delete leaked $stale_name; moved the whole workspace to $workspace_quarantine so this checkout can proceed — that directory needs manual cleanup"
break
else
echo "::warning::leaked $stale_name survived every recovery; runner needs manual cleanup"
fi
fi
}
fi
done
# Interrupted reviews leave worktree registrations under .qwen/tmp/
# and qwen-review/* branches behind. prune drops registrations whose
# directories the rm above removed; worktree remove --force then
Expand Down
Loading
Loading