fix(gateway): tolerate Unicode in stderr log handlers on Windows #27335
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Lint (ruff + ty) | |
| # Two things here: | |
| # 1. Advisory diff — ruff + ty diagnostics as a diff vs the target branch. | |
| # Posts a Markdown summary and a PR comment. Exit zero always. | |
| # 2. Blocking ``ruff check .`` — enforces the explicit rules in | |
| # ``[tool.ruff.lint.select]`` (currently PLW1514). Failure blocks merge. | |
| # Separate job so the advisory diff still runs and posts even when | |
| # enforcement fails. | |
| on: | |
| push: | |
| branches: [main] | |
| paths-ignore: | |
| - "**/*.md" | |
| - "docs/**" | |
| - "website/**" | |
| pull_request: | |
| branches: [main] | |
| paths-ignore: | |
| - "**/*.md" | |
| - "docs/**" | |
| - "website/**" | |
| permissions: | |
| contents: read | |
| pull-requests: write # needed to post/update PR comments | |
| concurrency: | |
| group: lint-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| lint-diff: | |
| name: ruff + ty diff | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 # need full history for merge-base + worktree | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5 | |
| - name: Install ruff + ty | |
| run: | | |
| uv tool install ruff | |
| uv tool install ty | |
| - name: Determine base ref | |
| id: base | |
| run: | | |
| # For PRs, diff against the merge base with the target branch. | |
| # For pushes to main, diff against the previous commit on main. | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| BASE_SHA=$(git merge-base "origin/${{ github.base_ref }}" HEAD) | |
| BASE_REF="origin/${{ github.base_ref }}" | |
| else | |
| BASE_SHA=$(git rev-parse HEAD~1 2>/dev/null || git rev-parse HEAD) | |
| BASE_REF="HEAD~1" | |
| fi | |
| echo "sha=${BASE_SHA}" >> "$GITHUB_OUTPUT" | |
| echo "ref=${BASE_REF}" >> "$GITHUB_OUTPUT" | |
| echo "Base SHA: ${BASE_SHA}" | |
| echo "Base ref: ${BASE_REF}" | |
| - name: Run ruff + ty on HEAD | |
| run: | | |
| mkdir -p .lint-reports/head | |
| ruff check --output-format json --exit-zero \ | |
| > .lint-reports/head/ruff.json || true | |
| ty check --output-format gitlab --exit-zero \ | |
| > .lint-reports/head/ty.json || true | |
| echo "HEAD ruff: $(wc -c < .lint-reports/head/ruff.json) bytes" | |
| echo "HEAD ty: $(wc -c < .lint-reports/head/ty.json) bytes" | |
| - name: Run ruff + ty on base (via git worktree) | |
| run: | | |
| mkdir -p .lint-reports/base | |
| # Use a worktree so we don't clobber the main checkout. If the basex | |
| # SHA is identical to HEAD (e.g. first commit), skip and leave the | |
| # base reports empty — the diff script handles missing files. | |
| HEAD_SHA=$(git rev-parse HEAD) | |
| BASE_SHA="${{ steps.base.outputs.sha }}" | |
| if [ "$BASE_SHA" = "$HEAD_SHA" ]; then | |
| echo "Base SHA == HEAD SHA, skipping base scan." | |
| echo '[]' > .lint-reports/base/ruff.json | |
| echo '[]' > .lint-reports/base/ty.json | |
| else | |
| git worktree add --detach /tmp/lint-base "$BASE_SHA" | |
| ( | |
| cd /tmp/lint-base | |
| ruff check --output-format json --exit-zero \ | |
| > "$GITHUB_WORKSPACE/.lint-reports/base/ruff.json" || true | |
| ty check --output-format gitlab --exit-zero \ | |
| > "$GITHUB_WORKSPACE/.lint-reports/base/ty.json" || true | |
| ) | |
| git worktree remove --force /tmp/lint-base | |
| fi | |
| echo "base ruff: $(wc -c < .lint-reports/base/ruff.json) bytes" | |
| echo "base ty: $(wc -c < .lint-reports/base/ty.json) bytes" | |
| - name: Generate diff summary | |
| run: | | |
| python scripts/lint_diff.py \ | |
| --base-ruff .lint-reports/base/ruff.json \ | |
| --head-ruff .lint-reports/head/ruff.json \ | |
| --base-ty .lint-reports/base/ty.json \ | |
| --head-ty .lint-reports/head/ty.json \ | |
| --base-ref "${{ steps.base.outputs.ref }}" \ | |
| --head-ref "${{ github.event_name == 'pull_request' && github.head_ref || github.ref_name }}" \ | |
| --output .lint-reports/summary.md | |
| cat .lint-reports/summary.md >> "$GITHUB_STEP_SUMMARY" | |
| - name: Upload reports as artifact | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: lint-reports | |
| path: .lint-reports/ | |
| retention-days: 14 | |
| - name: Post / update PR comment | |
| if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository | |
| continue-on-error: true | |
| uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const body = fs.readFileSync('.lint-reports/summary.md', 'utf8'); | |
| const marker = '<!-- lint-diff-summary -->'; | |
| const fullBody = marker + '\n' + body; | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| const existing = comments.find(c => c.body && c.body.includes(marker)); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existing.id, | |
| body: fullBody, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: fullBody, | |
| }); | |
| } | |
| ruff-blocking: | |
| # Enforce the rules in pyproject.toml [tool.ruff.lint.select]. Currently | |
| # PLW1514 (unspecified-encoding) — catches bare ``open()`` / | |
| # ``read_text()`` / ``write_text()`` calls that default to locale | |
| # encoding on Windows. Failure here blocks merge; the advisory | |
| # ``lint-diff`` job above runs independently so reviewers still get | |
| # the diff comment even when enforcement fails. | |
| name: ruff enforcement (blocking) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5 | |
| - name: Install ruff | |
| run: uv tool install ruff | |
| - name: ruff check . | |
| # No --exit-zero, no || true. Exit code propagates to the job, | |
| # which propagates to the required-check gate. | |
| run: | | |
| ruff check . | |
| windows-footguns: | |
| # Static guardrails on Windows-unsafe Python primitives — os.kill(pid, 0), | |
| # os.killpg, os.setsid, signal.SIGKILL without getattr fallback, | |
| # shebang scripts via subprocess, bare open() without encoding=, etc. | |
| # See scripts/check-windows-footguns.py for the full rule list. | |
| name: Windows footguns (blocking) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Set up Python | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Run footgun checker | |
| run: python scripts/check-windows-footguns.py --all |