fix(exec): abort PTY readers after forced termination (#5946) #365
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: Preview Build | |
| on: | |
| pull_request: | |
| types: [opened, reopened, ready_for_review, synchronize] | |
| workflow_dispatch: {} | |
| concurrency: | |
| group: preview-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| actions: read | |
| jobs: | |
| build: | |
| # Skip preview builds for the automated upstream-merge PRs. | |
| # These PRs are large, frequent, and not meant for end-user preview binaries. | |
| if: >- | |
| github.event_name == 'workflow_dispatch' || | |
| ( | |
| github.event_name == 'pull_request' && | |
| github.event.pull_request.draft == false && | |
| github.event.pull_request.head.ref != 'upstream-merge' | |
| ) | |
| name: Build ${{ matrix.target }} | |
| runs-on: ${{ matrix.os }} | |
| env: | |
| # Consistent Cargo/rustup homes for caching across all workflows | |
| CARGO_HOME: ${{ github.workspace }}/.cargo-home | |
| RUSTUP_HOME: ${{ github.workspace }}/.cargo-home/rustup | |
| RUST_WORKSPACE_DIR: code-rs | |
| CARGO_TARGET_DIR: ${{ github.workspace }}/code-rs/target | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # Linux MUSL (static-ish) builds | |
| - os: ubuntu-24.04 | |
| target: x86_64-unknown-linux-musl | |
| artifact: code-x86_64-unknown-linux-musl | |
| - os: ubuntu-24.04-arm | |
| target: aarch64-unknown-linux-musl | |
| artifact: code-aarch64-unknown-linux-musl | |
| # macOS builds (both architectures) | |
| - os: macos-14 | |
| target: x86_64-apple-darwin | |
| artifact: code-x86_64-apple-darwin | |
| - os: macos-14 | |
| target: aarch64-apple-darwin | |
| artifact: code-aarch64-apple-darwin | |
| # Windows build | |
| - os: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| artifact: code-x86_64-pc-windows-msvc.exe | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| shell: bash | |
| env: | |
| RUST_TOOLCHAIN: 1.90.0 | |
| run: | | |
| rustup set profile minimal | |
| rustup toolchain install "$RUST_TOOLCHAIN" --profile minimal | |
| rustup default "$RUST_TOOLCHAIN" | |
| rustup target add "${{ matrix.target }}" | |
| if [[ "${{ matrix.target }}" == *"unknown-linux-musl"* ]]; then | |
| rustup target add x86_64-unknown-linux-musl aarch64-unknown-linux-musl | |
| fi | |
| - name: Rust cache (target + registries) | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| prefix-key: v1-preview | |
| shared-key: preview-${{ matrix.target }}-rust-1.90 | |
| workspaces: | | |
| code-rs -> target | |
| codex-rs -> target | |
| cache-targets: true | |
| cache-workspace-crates: true | |
| cache-on-failure: true | |
| - name: Setup sccache (GHA backend) | |
| uses: mozilla-actions/sccache-action@v0.0.9 | |
| with: | |
| version: v0.10.0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Enable sccache | |
| shell: bash | |
| run: | | |
| echo "SCCACHE_GHA_ENABLED=true" >> "$GITHUB_ENV" | |
| echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV" | |
| echo "SCCACHE_IDLE_TIMEOUT=1800" >> "$GITHUB_ENV" | |
| echo "SCCACHE_CACHE_SIZE=10G" >> "$GITHUB_ENV" | |
| # Platform tuning (lightweight) | |
| - name: Linux musl tuning | |
| if: contains(matrix.os, 'ubuntu') && contains(matrix.target, 'musl') | |
| shell: bash | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y musl-tools pkg-config zstd | |
| echo 'CC=musl-gcc' >> "$GITHUB_ENV" | |
| case "${{ matrix.target }}" in | |
| x86_64-unknown-linux-musl) echo 'CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=musl-gcc' >> "$GITHUB_ENV" ;; | |
| aarch64-unknown-linux-musl) echo 'CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=musl-gcc' >> "$GITHUB_ENV" ;; | |
| esac | |
| echo 'PKG_CONFIG_ALLOW_CROSS=1' >> "$GITHUB_ENV" | |
| echo 'OPENSSL_STATIC=1' >> "$GITHUB_ENV" | |
| echo 'RUSTFLAGS=-Awarnings -C debuginfo=0 -C strip=symbols -C panic=abort' >> "$GITHUB_ENV" | |
| - name: macOS tuning | |
| if: startsWith(matrix.os, 'macos-') | |
| shell: bash | |
| run: | | |
| echo 'CC=sccache clang' >> "$GITHUB_ENV" | |
| echo 'CXX=sccache clang++' >> "$GITHUB_ENV" | |
| echo 'RUSTFLAGS=-Awarnings -C debuginfo=0 -C strip=symbols -C panic=abort' >> "$GITHUB_ENV" | |
| - name: Windows tuning | |
| if: matrix.os == 'windows-latest' | |
| shell: pwsh | |
| run: | | |
| "LIBGIT2_SYS_USE_SCHANNEL=1" >> $env:GITHUB_ENV | |
| "CURL_SSL_BACKEND=schannel" >> $env:GITHUB_ENV | |
| if (Get-Command lld-link -ErrorAction SilentlyContinue) { | |
| "RUSTFLAGS=-Awarnings -Clinker=lld-link -C debuginfo=0 -C strip=symbols -C panic=abort -C link-arg=/OPT:REF -C link-arg=/OPT:ICF -C link-arg=/DEBUG:NONE" >> $env:GITHUB_ENV | |
| } else { | |
| "RUSTFLAGS=-Awarnings -C debuginfo=0 -C strip=symbols -C panic=abort -C link-arg=/OPT:REF -C link-arg=/OPT:ICF -C link-arg=/DEBUG:NONE" >> $env:GITHUB_ENV | |
| } | |
| - name: Prefetch deps | |
| working-directory: ${{ env.RUST_WORKSPACE_DIR }} | |
| env: | |
| CARGO_NET_GIT_FETCH_WITH_CLI: "true" | |
| run: cargo fetch --locked | |
| - name: Build code (release) | |
| shell: bash | |
| run: | | |
| cd "${RUST_WORKSPACE_DIR}" | |
| cargo build --release --locked --target "${{ matrix.target }}" --bin code | |
| - name: Prepare artifacts | |
| shell: bash | |
| run: | | |
| mkdir -p artifacts | |
| if [[ "${{ matrix.os }}" == "windows-latest" ]]; then | |
| cp "${RUST_WORKSPACE_DIR}/target/${{ matrix.target }}/release/code.exe" "artifacts/${{ matrix.artifact }}" | |
| else | |
| cp "${RUST_WORKSPACE_DIR}/target/${{ matrix.target }}/release/code" "artifacts/${{ matrix.artifact }}" | |
| fi | |
| - name: Compress artifacts (Windows) | |
| if: matrix.os == 'windows-latest' | |
| shell: pwsh | |
| run: | | |
| Get-ChildItem artifacts -File | ForEach-Object { | |
| $src = $_.FullName | |
| $dst = "$src.zip" | |
| Compress-Archive -Path $src -DestinationPath $dst -Force | |
| Remove-Item $src -Force | |
| } | |
| - name: Compress artifacts (*nix dual-format) | |
| if: matrix.os != 'windows-latest' | |
| shell: bash | |
| run: | | |
| shopt -s nullglob | |
| for f in artifacts/*; do | |
| [ -f "$f" ] || continue | |
| base=$(basename "$f") | |
| # .zst (size-optimized) | |
| if command -v zstd >/dev/null 2>&1; then | |
| zstd -T0 -19 --force -o "artifacts/${base}.zst" "$f" || true | |
| fi | |
| # .tar.gz fallback | |
| tar -C artifacts -czf "artifacts/${base}.tar.gz" "$base" | |
| rm -f "$f" | |
| done | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: preview-${{ matrix.target }} | |
| path: artifacts/ | |
| compression-level: 0 | |
| release: | |
| name: Publish prerelease (all targets) | |
| needs: [build] | |
| outputs: | |
| slug: ${{ steps.slug.outputs.slug }} | |
| tag: ${{ steps.slug.outputs.tag }} | |
| title: ${{ steps.slug.outputs.title }} | |
| skip: ${{ steps.slug.outputs.skip }} | |
| # Only publish for PRs from the main repo (not forks) to avoid permission failures. | |
| # Fixes: https://github.com/just-every/code/issues/355 | |
| # Fixes: https://github.com/just-every/code/issues/356 | |
| if: >- | |
| github.event_name == 'pull_request' && | |
| github.event.pull_request.head.ref != 'upstream-merge' && | |
| github.event.pull_request.head.repo.full_name == github.repository | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Resolve slug and next tag | |
| id: slug | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const owner = context.repo.owner; const repo = context.repo.repo; const pr = context.payload.pull_request.number; | |
| const { data: pull } = await github.rest.pulls.get({ owner, repo, pull_number: pr }); | |
| const marker = /<!--\s*codex-id:\s*([a-z0-9-]{3,})\s*-->/i; | |
| function normalizeSlug(s) { | |
| if (!s) return ''; | |
| let x = String(s).toLowerCase().trim(); | |
| if (x.startsWith('code/')) x = x.slice(5); | |
| if (x.startsWith('id/')) x = x.slice(3); | |
| // keep only a–z, 0–9 and dashes; collapse repeats | |
| x = x.replace(/[^a-z0-9-]+/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, ''); | |
| if (!/^[a-z0-9-]{3,}$/.test(x)) return ''; | |
| return x; | |
| } | |
| let slug = ''; | |
| // 1) Prefer PR labels code/<slug> (fallback id/<slug>) | |
| try { | |
| const prLabels = await github.paginate(github.rest.issues.listLabelsOnIssue, { owner, repo, issue_number: pr, per_page: 100 }); | |
| const names = prLabels.map(l => (typeof l === 'string' ? l : l.name)).filter(Boolean); | |
| const codeLabel = names.find(n => n.startsWith('code/')); | |
| const idLabel = names.find(n => n.startsWith('id/')); | |
| slug = normalizeSlug(codeLabel || idLabel || ''); | |
| } catch {} | |
| // 2) PR body marker | |
| if (!slug) { | |
| const m = (pull.body || '').match(marker); | |
| if (m) slug = normalizeSlug(m[1]); | |
| } | |
| // 3) PR comments marker | |
| if (!slug) { | |
| try { | |
| const prComments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number: pr, per_page: 100 }); | |
| for (const c of prComments) { const mm = (c.body || '').match(marker); if (mm) { slug = normalizeSlug(mm[1]); if (slug) break; } } | |
| } catch {} | |
| } | |
| // 4) Linked issue via branch name issue-<n> | |
| let linkedIssue = 0; | |
| const b = pull.head.ref || ''; | |
| const im = b.match(/^issue-(\d+)$/); | |
| if (im) linkedIssue = Number(im[1]); | |
| if (!slug && linkedIssue) { | |
| try { | |
| const { data: iss } = await github.rest.issues.get({ owner, repo, issue_number: linkedIssue }); | |
| const labels = (iss.labels || []).map(l => (typeof l === 'string' ? l : l.name)).filter(Boolean); | |
| const codeLabel = labels.find(n => typeof n === 'string' && n.startsWith('code/')); | |
| const idLabel = labels.find(n => typeof n === 'string' && n.startsWith('id/')); | |
| slug = normalizeSlug(codeLabel || idLabel || ''); | |
| if (!slug) { | |
| const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number: linkedIssue, per_page: 100 }); | |
| for (const c of comments) { const mm = (c.body || '').match(marker); if (mm) { slug = normalizeSlug(mm[1]); if (slug) break; } } | |
| } | |
| } catch {} | |
| } | |
| if (!slug) { | |
| core.notice('Missing ID. Skipping prerelease publish; add code/<slug> or <!-- codex-id: ... --> to enable preview binaries.'); | |
| core.setOutput('slug', ''); | |
| core.setOutput('tag', ''); | |
| core.setOutput('title', ''); | |
| core.setOutput('skip', 'true'); | |
| return; | |
| } | |
| // Best-effort: ensure linked issue carries code/<slug> | |
| if (linkedIssue && slug) { | |
| try { | |
| const { data: iss } = await github.rest.issues.get({ owner, repo, issue_number: linkedIssue }); | |
| const labels = (iss.labels || []).map(l => (typeof l === 'string' ? l : l.name)).filter(Boolean); | |
| const need = `code/${slug}`; | |
| if (!labels.includes(need)) { | |
| try { await github.rest.issues.addLabels({ owner, repo, issue_number: linkedIssue, labels: [need] }); } catch {} | |
| } | |
| } catch {} | |
| } | |
| // Determine next tag: preview-<slug> or preview-<slug>-N | |
| const base = `preview-${slug}`; | |
| const rels = await github.paginate(github.rest.repos.listReleases, { owner, repo, per_page: 100 }); | |
| let maxN = 0; let hasBase = false; | |
| for (const r of rels) { | |
| const t = r.tag_name || ''; | |
| if (t === base) { hasBase = true; maxN = Math.max(maxN, 1); } | |
| const mm = t.match(new RegExp(`^${base}-(\\d+)$`)); | |
| if (mm) maxN = Math.max(maxN, parseInt(mm[1], 10)); | |
| } | |
| const next = hasBase || maxN > 0 ? `${base}-${maxN+1}` : base; | |
| core.setOutput('slug', slug); | |
| core.setOutput('tag', next); | |
| core.setOutput('title', `Preview for ${slug}`); | |
| core.setOutput('skip', 'false'); | |
| - name: Skip summary | |
| if: steps.slug.outputs.skip == 'true' | |
| shell: bash | |
| run: | | |
| printf 'Skipping prerelease upload: missing code/<slug> label or <!-- codex-id: ... --> marker.\n' >> "$GITHUB_STEP_SUMMARY" | |
| - name: Download all artifacts | |
| if: steps.slug.outputs.skip != 'true' | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts/ | |
| - name: Create or update prerelease with assets | |
| if: steps.slug.outputs.skip != 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| TAG: ${{ steps.slug.outputs.tag }} | |
| TITLE: ${{ steps.slug.outputs.title }} | |
| shell: bash | |
| run: | | |
| # Add state label triage/building to the linked issue (best-effort) | |
| owner=$(echo "$GITHUB_REPOSITORY" | cut -d/ -f1); repo=$(echo "$GITHUB_REPOSITORY" | cut -d/ -f2) | |
| pr=${{ github.event.pull_request.number }} | |
| issue_number="$(gh pr view "$pr" --json headRefName --jq '.headRefName' | sed -n 's/^issue-\([0-9]\+\)$/\1/p' || true)" | |
| if [ -n "$issue_number" ]; then | |
| gh api -X POST "/repos/$owner/$repo/issues/$issue_number/labels" -f labels[]='triage/building' >/dev/null 2>&1 || true | |
| fi | |
| set -euo pipefail | |
| REPO="${GITHUB_REPOSITORY}" | |
| # Show files for debugging | |
| echo "Artifacts directory:"; ls -R artifacts || true | |
| # Ensure release exists (create if missing) | |
| if ! gh release view "$TAG" -R "$REPO" >/dev/null 2>&1; then | |
| gh release create "$TAG" -R "$REPO" \ | |
| --title "$TITLE" \ | |
| --notes "Automated prerelease with preview binaries for PR #${{ github.event.pull_request.number }} (run ${{ github.run_id }})." \ | |
| --prerelease \ | |
| --latest=false \ | |
| --target "$GITHUB_SHA" | |
| fi | |
| # Upload/replace all artifacts | |
| mapfile -t files < <(find artifacts -type f -print) | |
| if [ ${#files[@]} -gt 0 ]; then | |
| gh release upload "$TAG" -R "$REPO" "${files[@]}" --clobber | |
| else | |
| echo "No artifacts found to upload." >&2 | |
| fi | |
| fallback: | |
| name: Run fallback build-fast | |
| needs: [build, release] | |
| if: >- | |
| always() && | |
| github.event_name == 'pull_request' && | |
| github.event.pull_request.head.ref != 'upstream-merge' && | |
| github.event.pull_request.draft == false && | |
| ( | |
| needs.release.result == 'skipped' || | |
| needs.release.outputs.skip == 'true' | |
| ) | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Run build-fast | |
| run: ./build-fast.sh | |
| comment: | |
| name: Post Artifact Links | |
| needs: [build, release] | |
| # Only run if release job ran (which is conditional on non-fork PRs) | |
| if: >- | |
| github.event_name == 'pull_request' && | |
| github.event.pull_request.head.ref != 'upstream-merge' && | |
| github.event.pull_request.head.repo.full_name == github.repository && | |
| needs.release.outputs.skip != 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Resolve slug and latest tag | |
| id: slug | |
| uses: actions/github-script@v7 | |
| env: | |
| RELEASE_SLUG: ${{ needs.release.outputs.slug }} | |
| with: | |
| script: | | |
| const owner = context.repo.owner; const repo = context.repo.repo; const pr = context.payload.pull_request.number; | |
| const { data: pull } = await github.rest.pulls.get({ owner, repo, pull_number: pr }); | |
| const marker = /<!--\s*codex-id:\s*([a-z0-9-]{3,})\s*-->/i; | |
| function normalizeSlug(s){ if(!s) return ''; let x=String(s).toLowerCase().trim(); if(x.startsWith('code/')) x=x.slice(5); if(x.startsWith('id/')) x=x.slice(3); x=x.replace(/[^a-z0-9-]+/g,'-').replace(/-+/g,'-').replace(/^-|-$/g,''); return /^[a-z0-9-]{3,}$/.test(x)?x:''; } | |
| let slug = normalizeSlug(process.env.RELEASE_SLUG || ''); | |
| // PR labels first | |
| try { const labs = await github.paginate(github.rest.issues.listLabelsOnIssue, { owner, repo, issue_number: pr, per_page: 100 }); | |
| const names = labs.map(l => (typeof l === 'string' ? l : l.name)).filter(Boolean); | |
| const codeLabel = names.find(n => n.startsWith('code/')); | |
| const idLabel = names.find(n => n.startsWith('id/')); | |
| slug = normalizeSlug(codeLabel || idLabel || ''); | |
| } catch {} | |
| // PR body | |
| if (!slug) { const m = (pull.body || '').match(marker); if (m) slug = normalizeSlug(m[1]); } | |
| // PR comments | |
| if (!slug) { | |
| try { const prc = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number: pr, per_page: 100 }); | |
| for (const c of prc) { const mm = (c.body || '').match(marker); if (mm) { slug = normalizeSlug(mm[1]); if (slug) break; } } | |
| } catch {} | |
| } | |
| // Linked issue via branch | |
| const b = pull.head.ref || ''; | |
| const im = b.match(/^issue-(\d+)$/); | |
| if (!slug && im) { | |
| const issue_number = Number(im[1]); | |
| try { | |
| const { data: iss } = await github.rest.issues.get({ owner, repo, issue_number }); | |
| const labels = (iss.labels || []).map(l => (typeof l === 'string' ? l : l.name)).filter(Boolean); | |
| const codeLabel = labels.find(n => typeof n === 'string' && n.startsWith('code/')); | |
| const idLabel = labels.find(n => typeof n === 'string' && n.startsWith('id/')); | |
| slug = normalizeSlug(codeLabel || idLabel || ''); | |
| if (!slug) { | |
| const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number, per_page: 100 }); | |
| for (const c of comments) { const mm = (c.body || '').match(marker); if (mm) { slug = normalizeSlug(mm[1]); if (slug) break; } } | |
| } | |
| } catch {} | |
| } | |
| if (!slug) throw new Error('Missing ID. Add code/<slug> label or <!-- codex-id: <slug> -->.'); | |
| const base = `preview-${slug}`; | |
| const rels = await github.paginate(github.rest.repos.listReleases, { owner, repo, per_page: 100 }); | |
| let latest = base; let maxN = 0; let hasBase = false; | |
| for (const r of rels) { | |
| const t = r.tag_name || ''; | |
| if (t === base) { hasBase = true; maxN = Math.max(maxN, 1); if (latest === base) latest = base; } | |
| const mm = t.match(new RegExp(`^${base}-(\\d+)$`)); | |
| if (mm) { const n = parseInt(mm[1], 10); if (n > maxN) { maxN = n; latest = t; } } | |
| } | |
| core.setOutput('slug', slug); | |
| core.setOutput('tag', latest); | |
| - name: Upsert PR comment with artifact info | |
| if: github.event_name == 'pull_request' && github.event.pull_request.head.ref != 'upstream-merge' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const pr_number = context.payload.pull_request.number; | |
| const runId = context.runId; | |
| const runUrl = `https://github.com/${owner}/${repo}/actions/runs/${runId}`; | |
| const slug = `${{ steps.slug.outputs.slug }}`; | |
| const tag = `${{ steps.slug.outputs.tag }}`; | |
| const releaseBase = `https://github.com/${owner}/${repo}/releases/download/${tag}`; | |
| const marker = '<!-- preview-build:artifacts -->'; | |
| const body = [ | |
| marker, | |
| '### Preview Build', | |
| '', | |
| 'You can run this right now! In your terminal just use:', | |
| '```bash', | |
| `code preview ${slug}`, | |
| '```', | |
| 'This will run the version of Code we\'ve created for this issue.', | |
| '', | |
| 'Direct downloads:', | |
| `- Linux x86_64 (.zst): ${releaseBase}/code-x86_64-unknown-linux-musl.zst`, | |
| `- Linux x86_64 (.tar.gz): ${releaseBase}/code-x86_64-unknown-linux-musl.tar.gz`, | |
| `- Linux aarch64 (.zst): ${releaseBase}/code-aarch64-unknown-linux-musl.zst`, | |
| `- Linux aarch64 (.tar.gz): ${releaseBase}/code-aarch64-unknown-linux-musl.tar.gz`, | |
| `- macOS x86_64 (.zst): ${releaseBase}/code-x86_64-apple-darwin.zst`, | |
| `- macOS x86_64 (.tar.gz): ${releaseBase}/code-x86_64-apple-darwin.tar.gz`, | |
| `- macOS arm64 (.zst): ${releaseBase}/code-aarch64-apple-darwin.zst`, | |
| `- macOS arm64 (.tar.gz): ${releaseBase}/code-aarch64-apple-darwin.tar.gz`, | |
| `- Windows x86_64 (.zip): ${releaseBase}/code-x86_64-pc-windows-msvc.exe.zip`, | |
| '', | |
| 'After you run it, please comment here and let me know if it does what you need.', | |
| marker | |
| ].join('\n'); | |
| // Find existing marker comment from this bot | |
| const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number: pr_number, per_page: 100 }); | |
| const mine = comments.find(c => c.user?.type?.toLowerCase().includes('bot') && c.body?.includes(marker)); | |
| if (mine) { | |
| await github.rest.issues.updateComment({ owner, repo, comment_id: mine.id, body }); | |
| } else { | |
| await github.rest.issues.createComment({ owner, repo, issue_number: pr_number, body }); | |
| } | |
| - name: Mark triage complete on linked issue | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const owner = context.repo.owner; const repo = context.repo.repo; | |
| const pr = context.payload.pull_request.number; | |
| const { data: pull } = await github.rest.pulls.get({ owner, repo, pull_number: pr }); | |
| const b = pull.head.ref || ''; | |
| const im = b.match(/^issue-(\d+)$/); | |
| if (im) { | |
| const issue_number = Number(im[1]); | |
| try { await github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['triage/complete'] }); } catch {} | |
| try { await github.rest.issues.removeLabel({ owner, repo, issue_number, name: 'triage/building' }); } catch {} | |
| } |