From d021bca5e1b7dd5163dabbccbdc216002cf0fafb Mon Sep 17 00:00:00 2001 From: jesse-ux Date: Mon, 14 Sep 2026 09:49:25 +0800 Subject: [PATCH] fix(ci): clear leftover .venv before Python 3.14 venv create (BUG-677) Staging gate run 2594 died in Install dependencies with FileExistsError on a reused hostexecutor .venv. Remove the leftover directory and create the venv with --clear so Python 3.14 mkdir does not abort the job. --- .gitea/workflows/backend-quality-gate.yml | 5 ++++- docs/BUG_HISTORY.md | 16 ++++++++++++++++ frontend/tests/staging-backend-workflows.test.ts | 8 +++++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/backend-quality-gate.yml b/.gitea/workflows/backend-quality-gate.yml index c6d5ad87..86ce9701 100644 --- a/.gitea/workflows/backend-quality-gate.yml +++ b/.gitea/workflows/backend-quality-gate.yml @@ -235,7 +235,10 @@ jobs: NPM_CONFIG_REGISTRY: https://registry.npmmirror.com run: | set -euo pipefail - python3 -m venv .venv + # Hostexecutor may keep a gitignored .venv/; Python 3.14 mkdir then + # raises FileExistsError. Clear first instead of reusing leftovers. + rm -rf -- .venv + python3 -m venv --clear .venv export PATH="$PWD/.venv/bin:$PATH" python -m pip install --upgrade pip python -m pip install \ diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index 18df14f9..cfe26370 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -10532,3 +10532,19 @@ - 相关记录:BUG-442 - 复发自:无 - 修复版本:待发布 + +## BUG-677 | staging quality gate 在残留 .venv 上被 Python 3.14 mkdir 打断 + +- 状态:resolved +- 首次发现:2026-09-14 +- 最近更新:2026-09-14 +- 影响面:Gitea `Independent Staging Quality Gate` validate「Install dependencies」、publish(因 `needs: validate` 被 skip) +- 用户现象:推送 `69ffa07d` 后门禁 run `2594` 约 20 秒失败;测试、lint、build 未执行,publish/deploy skipped。 +- 触发条件:`runs-on: xiaoxin` 的 act_runner hostexecutor 复用 job 目录且已有 gitignored `.venv/`;runner 报告 `Python 3.14.4`。 +- 根因:`python3 -m venv .venv` 对已存在目录执行 `mkdir`,Python 3.14 抛出 `FileExistsError: [Errno 17] File exists: '.../hostexecutor/.venv'`。`git status --porcelain --untracked-files=all` 仍为空,因为忽略目录不出现在 porcelain 里。不是 pip、npm 或校正测试失败。 +- 修复:安装步骤先 `rm -rf -- .venv`,再 `python3 -m venv --clear .venv`。不改测试、lint、build、exact-SHA 与镜像发布语义。 +- 验证:`frontend/tests/staging-backend-workflows.test.ts` 锁定清除后再 `--clear`;Gitea gate 对修复 SHA 复跑。 +- 防复发:质量门创建 `.venv` 必须允许 hostexecutor 残留目录。不得把 Python 3.14 `FileExistsError` 当成业务测试失败。 +- 相关记录:BUG-149、BUG-364 +- 复发自:无 +- 修复版本:待发布 diff --git a/frontend/tests/staging-backend-workflows.test.ts b/frontend/tests/staging-backend-workflows.test.ts index 0f789832..d5539dde 100644 --- a/frontend/tests/staging-backend-workflows.test.ts +++ b/frontend/tests/staging-backend-workflows.test.ts @@ -227,7 +227,11 @@ test("Gitea quality gate validates before publishing an immutable ACR manifest", assert.equal((workflow.match(/exact staging gate checkout failed after \$attempt bounded attempts/g) ?? []).length, 2); assert.equal((workflow.match(/\[\[ "\$fetch_succeeded" == true \]\]/g) ?? []).length, 2); assert.doesNotMatch(workflow, /http\.lowSpeedLimit=1024|http\.lowSpeedTime=30/); - assert.match(workflow, /python3 -m venv \.venv/); + // 原值: python3 -m venv .venv + // 新值: rm -rf leftover then python3 -m venv --clear .venv + // 原因: BUG-677 Python 3.14 FileExistsError on reused hostexecutor .venv + assert.match(workflow, /rm -rf -- \.venv\n\s+python3 -m venv --clear \.venv/); + assert.doesNotMatch(workflow, /python3 -m venv \.venv(?:\s|$)/); assert.match(workflow, /PIP_INDEX_URL: https:\/\/mirrors\.aliyun\.com\/pypi\/simple\//); assert.match(workflow, /NPM_CONFIG_REGISTRY: https:\/\/registry\.npmmirror\.com/); for (const constraint of [ @@ -272,6 +276,8 @@ test("Gitea quality gate validates before publishing an immutable ACR manifest", const installStep = workflow.match( /- name: Install dependencies[\s\S]*?(?=\n\s+- name: Validate backend, package, frontend, and database contracts)/, )?.[0] ?? ""; + assert.match(installStep, /rm -rf -- \.venv/); + assert.match(installStep, /python3 -m venv --clear \.venv/); assert.match(installStep, /workdir="\$\(pwd -P\)"/); assert.match(installStep, /docker run --rm/); assert.doesNotMatch(installStep, /timeout --signal=TERM --kill-after=30s 900s docker run/);