zed-industries/zed

40 workflows · maturity 67% · 13 patterns · GitHub ↗

Security 25.83/100

Security dimensions

permissions
5
security scan
0
supply chain
13.3
secret handling
7.5
harden runner
0

Workflows (40)

add_commented_closed_issue_to_project perms .github/workflows/add_commented_closed_issue_to_project.yml
Triggers
issue_comment
Runs on
namespace-profile-2x4-ubuntu-2404
Jobs
add-to-project
Actions
actions/create-github-app-token, actions/add-to-project
Commands
  • echo "::notice::Skipping issue #$ISSUE_NUMBER - commenter is staff member"
  • echo "::notice::Adding issue #$ISSUE_NUMBER to project (comment by $COMMENT_USER_LOGIN)"
View raw YAML
name: "Surface closed issues someone's commented on"

on:
  issue_comment:
    types:
      - created

permissions:
  contents: read

jobs:
  add-to-project:
    if: >
      github.repository == 'zed-industries/zed' &&
      github.event.issue.state == 'closed' &&
      github.event.issue.pull_request == null &&
      github.event.issue.type != null &&
      github.event.issue.type.name == 'Bug' &&
      github.event.comment.user.type != 'Bot'
    runs-on: namespace-profile-2x4-ubuntu-2404
    timeout-minutes: 5
    steps:
      - id: is-post-close-comment
        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
        with:
          script: |
            const closedAt = new Date(context.payload.issue.closed_at);
            const commentedAt = new Date(context.payload.comment.created_at);
            const diffSeconds = Math.abs(commentedAt - closedAt) / 1000;
            if (diffSeconds <= 30) {
              core.notice(`Skipping — comment was likely part of "Close with comment" (${diffSeconds}s gap)`);
              return false;
            }
            return true;

      - if: steps.is-post-close-comment.outputs.result == 'true'
        id: get-app-token
        uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
        with:
          app-id: ${{ secrets.ZED_COMMUNITY_BOT_APP_ID }}
          private-key: ${{ secrets.ZED_COMMUNITY_BOT_PRIVATE_KEY }}
          owner: zed-industries

      - if: steps.is-post-close-comment.outputs.result == 'true'
        id: check-staff
        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
        with:
          github-token: ${{ steps.get-app-token.outputs.token }}
          script: |
            try {
              const response = await github.rest.teams.getMembershipForUserInOrg({
                org: 'zed-industries',
                team_slug: 'staff',
                username: context.payload.comment.user.login
              });
              return response.data.state === 'active';
            } catch (error) {
              // 404 means user is not a member
              if (error.status === 404) {
                return false;
              }
              throw error;
            }

      - if: steps.is-post-close-comment.outputs.result == 'true' && steps.check-staff.outputs.result == 'true'
        env:
          ISSUE_NUMBER: ${{ github.event.issue.number }}
        run: |
          echo "::notice::Skipping issue #$ISSUE_NUMBER - commenter is staff member"

      # github-script outputs are JSON strings, so we compare against 'false' (string)
      - if: steps.is-post-close-comment.outputs.result == 'true' && steps.check-staff.outputs.result == 'false'
        env:
          ISSUE_NUMBER: ${{ github.event.issue.number }}
          COMMENT_USER_LOGIN: ${{ github.event.comment.user.login }}
        run: |
          echo "::notice::Adding issue #$ISSUE_NUMBER to project (comment by $COMMENT_USER_LOGIN)"

      - if: steps.is-post-close-comment.outputs.result == 'true' && steps.check-staff.outputs.result == 'false'
        uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
        with:
          project-url: https://github.com/orgs/zed-industries/projects/73
          github-token: ${{ steps.get-app-token.outputs.token }}
after_release .github/workflows/after_release.yml
Triggers
release, workflow_dispatch
Runs on
namespace-profile-2x4-ubuntu-2404, namespace-profile-2x4-ubuntu-2404, self-32vcpu-windows-2022, namespace-profile-2x4-ubuntu-2404, namespace-profile-2x4-ubuntu-2404
Jobs
rebuild_releases_page, post_to_discord, publish_winget, create_sentry_release, notify_on_failure
Actions
2428392/gh-truncate-string-action, tsickert/discord-webhook, vedantmgoyal9/winget-releaser, getsentry/action-release
Commands
  • curl -fX POST https://cloud.zed.dev/releases/refresh?expect_tag=${{ github.event.release.tag_name || inputs.tag_name }}
  • ./script/redeploy-vercel
  • if [ "${{ github.event.release.prerelease || inputs.prerelease }}" == "true" ]; then URL="https://zed.dev/releases/preview" else URL="https://zed.dev/releases/stable" fi echo "URL=$URL" >> "$GITHUB_OUTPUT"
  • $headers = @{ "Authorization" = "Bearer $env:WINGET_TOKEN" "Accept" = "application/vnd.github+json" "X-GitHub-Api-Version" = "2022-11-28" } $body = @{ branch = "master" } | ConvertTo-Json $uri = "https://api.github.com/repos/$env:GITHUB_REPOSITORY_OWNER/winget-pkgs/merge-upstream" try { Invoke-RestMethod -Uri $uri -Method Post -Headers $headers -Body $body -ContentType "application/json" Write-Host "Successfully synced winget-pkgs fork" } catch { Write-Host "Fork sync response: $_" Write-Host "Continuing anyway - fork may already be up to date" }
  • if ("${{ github.event.release.prerelease || inputs.prerelease }}" -eq "true") { $PACKAGE_NAME = "ZedIndustries.Zed.Preview" } else { $PACKAGE_NAME = "ZedIndustries.Zed" } echo "PACKAGE_NAME=$PACKAGE_NAME" >> $env:GITHUB_OUTPUT
  • curl -X POST -H 'Content-type: application/json' --data "$(jq -n --arg text "$SLACK_MESSAGE" '{"text": $text}')" "$SLACK_WEBHOOK"
View raw YAML
# Generated from xtask::workflows::after_release
# Rebuild with `cargo xtask workflows`.
name: after_release
on:
  release:
    types:
    - published
  workflow_dispatch:
    inputs:
      tag_name:
        description: tag_name
        required: true
        type: string
      prerelease:
        description: prerelease
        required: true
        type: boolean
      body:
        description: body
        type: string
        default: ''
jobs:
  rebuild_releases_page:
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - name: after_release::rebuild_releases_page::refresh_cloud_releases
      run: curl -fX POST https://cloud.zed.dev/releases/refresh?expect_tag=${{ github.event.release.tag_name || inputs.tag_name }}
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: after_release::rebuild_releases_page::redeploy_zed_dev
      run: ./script/redeploy-vercel
      env:
        VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
  post_to_discord:
    needs:
    - rebuild_releases_page
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - id: get-release-url
      name: after_release::post_to_discord::get_release_url
      run: |
        if [ "${{ github.event.release.prerelease || inputs.prerelease }}" == "true" ]; then
            URL="https://zed.dev/releases/preview"
        else
            URL="https://zed.dev/releases/stable"
        fi

        echo "URL=$URL" >> "$GITHUB_OUTPUT"
    - id: get-content
      name: after_release::post_to_discord::get_content
      uses: 2428392/gh-truncate-string-action@b3ff790d21cf42af3ca7579146eedb93c8fb0757
      with:
        stringToTruncate: |
          📣 Zed [${{ github.event.release.tag_name || inputs.tag_name }}](<${{ steps.get-release-url.outputs.URL }}>)  was just released!

          ${{ github.event.release.body || inputs.body }}
        maxLength: 2000
        truncationSymbol: '...'
    - name: after_release::post_to_discord::discord_webhook_action
      uses: tsickert/discord-webhook@c840d45a03a323fbc3f7507ac7769dbd91bfb164
      with:
        webhook-url: ${{ secrets.DISCORD_WEBHOOK_RELEASE_NOTES }}
        content: ${{ steps.get-content.outputs.string }}
  publish_winget:
    runs-on: self-32vcpu-windows-2022
    steps:
    - name: after_release::publish_winget::sync_winget_pkgs_fork
      run: |
        $headers = @{
            "Authorization" = "Bearer $env:WINGET_TOKEN"
            "Accept" = "application/vnd.github+json"
            "X-GitHub-Api-Version" = "2022-11-28"
        }
        $body = @{ branch = "master" } | ConvertTo-Json
        $uri = "https://api.github.com/repos/$env:GITHUB_REPOSITORY_OWNER/winget-pkgs/merge-upstream"
        try {
            Invoke-RestMethod -Uri $uri -Method Post -Headers $headers -Body $body -ContentType "application/json"
            Write-Host "Successfully synced winget-pkgs fork"
        } catch {
            Write-Host "Fork sync response: $_"
            Write-Host "Continuing anyway - fork may already be up to date"
        }
      shell: pwsh
      env:
        WINGET_TOKEN: ${{ secrets.WINGET_TOKEN }}
    - id: set-package-name
      name: after_release::publish_winget::set_package_name
      run: |
        if ("${{ github.event.release.prerelease || inputs.prerelease }}" -eq "true") {
            $PACKAGE_NAME = "ZedIndustries.Zed.Preview"
        } else {
            $PACKAGE_NAME = "ZedIndustries.Zed"
        }

        echo "PACKAGE_NAME=$PACKAGE_NAME" >> $env:GITHUB_OUTPUT
      shell: pwsh
    - name: after_release::publish_winget::winget_releaser
      uses: vedantmgoyal9/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f
      with:
        identifier: ${{ steps.set-package-name.outputs.PACKAGE_NAME }}
        release-tag: ${{ github.event.release.tag_name || inputs.tag_name }}
        max-versions-to-keep: 5
        token: ${{ secrets.WINGET_TOKEN }}
  create_sentry_release:
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: release::create_sentry_release
      uses: getsentry/action-release@526942b68292201ac6bbb99b9a0747d4abee354c
      with:
        environment: production
      env:
        SENTRY_ORG: zed-dev
        SENTRY_PROJECT: zed
        SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
  notify_on_failure:
    needs:
    - rebuild_releases_page
    - post_to_discord
    - publish_winget
    - create_sentry_release
    if: failure()
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - name: release::send_slack_message
      run: 'curl -X POST -H ''Content-type: application/json'' --data "$(jq -n --arg text "$SLACK_MESSAGE" ''{"text": $text}'')" "$SLACK_WEBHOOK"'
      env:
        SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_WORKFLOW_FAILURES }}
        SLACK_MESSAGE: '❌ ${{ github.workflow }} failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
defaults:
  run:
    shell: bash -euxo pipefail {0}
assign-reviewers perms .github/workflows/assign-reviewers.yml
Triggers
pull_request_target
Runs on
ubuntu-latest
Jobs
assign-reviewers
Actions
actions/create-github-app-token
Commands
  • pip install --no-deps -q --only-binary ':all:' \ -r /dev/stdin <<< "pyyaml==6.0.3 --hash=sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"
  • cd codeowner-coordinator python .github/scripts/assign-reviewers.py \ --pr "$PR_URL" \ --apply \ --rules-file team-membership-rules.yml \ --repo "$TARGET_REPO" \ --org zed-industries \ 2>&1 | tee /tmp/assign-reviewers-output.txt
View raw YAML
# Assign Reviewers — Smart team assignment based on diff weight
#
# Triggers on PR open and ready_for_review events. Checks out the coordinator
# repo (zed-industries/codeowner-coordinator) to access the assignment script and rules,
# then assigns the 1-2 most relevant teams as reviewers.
#
# NOTE: This file is stored in the codeowner-coordinator repo but must be deployed to
# the zed repo at .github/workflows/assign-reviewers.yml. See INSTALL.md.
#
# AUTH NOTE: Uses a GitHub App (COORDINATOR_APP_ID + COORDINATOR_APP_PRIVATE_KEY)
# for all API operations: cloning the private coordinator repo, requesting team
# reviewers, and setting PR assignees. GITHUB_TOKEN is not used.
#
# SECURITY INVARIANTS (pull_request_target):
# This workflow runs with access to secrets for ALL PRs including forks.
# It is safe ONLY because:
#   1. The checkout is the coordinator repo at ref: main — NEVER the PR head/branch
#   2. No ${{ }} interpolation of event fields in run: blocks — all routed via env:
#   3. The script never executes, sources, or reads files from the PR branch
# Violating any of these enables remote code execution with secret access.

name: Assign Reviewers

on:
  # zizmor: ignore[dangerous-triggers] reviewed — no PR code checkout, only coordinator repo at ref: main
  pull_request_target:
    types: [opened, ready_for_review]

# GITHUB_TOKEN is not used — all operations use the GitHub App token.
# Declare minimal permissions so the default token has no write access.
permissions: {}

# Prevent duplicate runs for the same PR (e.g., rapid push + ready_for_review).
concurrency:
  group: assign-reviewers-${{ github.event.pull_request.number }}
  cancel-in-progress: true

# NOTE: For ready_for_review events, the webhook payload may still carry
# draft: true due to a GitHub race condition (payload serialized before DB
# update). We trust the event type instead — the script rechecks draft status
# via a live API call as defense-in-depth.
#
# No author_association filter — external and fork PRs also get reviewer
# assignments. Assigned reviewers are inherently scoped to org team members
# by the GitHub Teams API.
jobs:
  assign-reviewers:
    if: >-
      github.event.action == 'ready_for_review' || github.event.pull_request.draft == false
    runs-on: ubuntu-latest
    steps:
      - name: Generate app token
        id: app-token
        uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
        with:
          app-id: ${{ vars.COORDINATOR_APP_ID }}
          private-key: ${{ secrets.COORDINATOR_APP_PRIVATE_KEY }}
          repositories: codeowner-coordinator,zed

      # SECURITY: checks out the coordinator repo at ref: main, NOT the PR branch.
      # persist-credentials: false prevents the token from leaking into .git/config.
      - name: Checkout coordinator repo
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          repository: zed-industries/codeowner-coordinator
          ref: main
          path: codeowner-coordinator
          token: ${{ steps.app-token.outputs.token }}
          persist-credentials: false

      - name: Setup Python
        uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
        with:
          python-version: "3.11"

      - name: Install dependencies
        run: |
          pip install --no-deps -q --only-binary ':all:' \
            -r /dev/stdin <<< "pyyaml==6.0.3 --hash=sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"

      - name: Assign reviewers
        env:
          GH_TOKEN: ${{ steps.app-token.outputs.token }}
          PR_URL: ${{ github.event.pull_request.html_url }}
          TARGET_REPO: ${{ github.repository }}
          ASSIGN_INTERNAL: ${{ vars.ASSIGN_INTERNAL || 'false' }}
          ASSIGN_EXTERNAL: ${{ vars.ASSIGN_EXTERNAL || 'true' }}
        run: |
          cd codeowner-coordinator
          python .github/scripts/assign-reviewers.py \
            --pr "$PR_URL" \
            --apply \
            --rules-file team-membership-rules.yml \
            --repo "$TARGET_REPO" \
            --org zed-industries \
            2>&1 | tee /tmp/assign-reviewers-output.txt

      - name: Upload output
        if: always()
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: assign-reviewers-output
          path: /tmp/assign-reviewers-output.txt
          retention-days: 30
autofix_pr .github/workflows/autofix_pr.yml
Triggers
workflow_dispatch
Runs on
namespace-profile-16x32-ubuntu-2204, namespace-profile-2x4-ubuntu-2404
Jobs
run_autofix, commit_changes
Actions
namespacelabs/nscloud-cache-action, pnpm/action-setup, clechasseur/rs-cargo, actions/create-github-app-token
Commands
  • gh pr checkout "$PR_NUMBER"
  • mkdir -p ./../.cargo cp ./.cargo/ci-config.toml ./../.cargo/config.toml
  • ./script/linux
  • ./script/download-wasi-sdk
  • cargo fix --workspace --release --all-targets --all-features --allow-dirty --allow-staged
  • cargo machete --fix
  • cargo clippy --workspace --release --all-targets --all-features --fix --allow-dirty --allow-staged
  • ./script/prettier --write
View raw YAML
# Generated from xtask::workflows::autofix_pr
# Rebuild with `cargo xtask workflows`.
name: autofix_pr
run-name: 'autofix PR #${{ inputs.pr_number }}'
on:
  workflow_dispatch:
    inputs:
      pr_number:
        description: pr_number
        required: true
        type: string
      run_clippy:
        description: run_clippy
        type: boolean
        default: 'true'
jobs:
  run_autofix:
    runs-on: namespace-profile-16x32-ubuntu-2204
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: autofix_pr::run_autofix::checkout_pr
      run: gh pr checkout "$PR_NUMBER"
      env:
        PR_NUMBER: ${{ inputs.pr_number }}
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    - name: steps::setup_cargo_config
      run: |
        mkdir -p ./../.cargo
        cp ./.cargo/ci-config.toml ./../.cargo/config.toml
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: steps::setup_linux
      run: ./script/linux
    - name: steps::download_wasi_sdk
      run: ./script/download-wasi-sdk
    - name: steps::setup_pnpm
      uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2
      with:
        version: '9'
    - name: autofix_pr::run_autofix::install_cargo_machete
      if: ${{ inputs.run_clippy }}
      uses: clechasseur/rs-cargo@8435b10f6e71c2e3d4d3b7573003a8ce4bfc6386
      with:
        command: install
        args: cargo-machete@0.7.0
    - name: autofix_pr::run_autofix::run_cargo_fix
      if: ${{ inputs.run_clippy }}
      run: cargo fix --workspace --release --all-targets --all-features --allow-dirty --allow-staged
    - name: autofix_pr::run_autofix::run_cargo_machete_fix
      if: ${{ inputs.run_clippy }}
      run: cargo machete --fix
    - name: autofix_pr::run_autofix::run_clippy_fix
      if: ${{ inputs.run_clippy }}
      run: cargo clippy --workspace --release --all-targets --all-features --fix --allow-dirty --allow-staged
    - name: autofix_pr::run_autofix::run_prettier_fix
      run: ./script/prettier --write
    - name: autofix_pr::run_autofix::run_cargo_fmt
      run: cargo fmt --all
    - id: create-patch
      name: autofix_pr::run_autofix::create_patch
      run: |
        if git diff --quiet; then
            echo "No changes to commit"
            echo "has_changes=false" >> "$GITHUB_OUTPUT"
        else
            git diff > autofix.patch
            echo "has_changes=true" >> "$GITHUB_OUTPUT"
        fi
    - name: upload artifact autofix-patch
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: autofix-patch
        path: autofix.patch
        if-no-files-found: ignore
        retention-days: '1'
    - name: steps::cleanup_cargo_config
      if: always()
      run: |
        rm -rf ./../.cargo
    outputs:
      has_changes: ${{ steps.create-patch.outputs.has_changes }}
  commit_changes:
    needs:
    - run_autofix
    if: needs.run_autofix.outputs.has_changes == 'true'
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - id: generate-token
      name: steps::authenticate_as_zippy
      uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859
      with:
        app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
        private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
        token: ${{ steps.generate-token.outputs.token }}
    - name: autofix_pr::commit_changes::checkout_pr
      run: gh pr checkout "$PR_NUMBER"
      env:
        PR_NUMBER: ${{ inputs.pr_number }}
        GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
    - name: autofix_pr::download_patch_artifact
      uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
      with:
        name: autofix-patch
    - name: autofix_pr::commit_changes::apply_patch
      run: git apply autofix.patch
    - name: autofix_pr::commit_changes::commit_and_push
      run: |
        git commit -am "Autofix"
        git push
      env:
        GIT_COMMITTER_NAME: Zed Zippy
        GIT_COMMITTER_EMAIL: 234243425+zed-zippy[bot]@users.noreply.github.com
        GIT_AUTHOR_NAME: Zed Zippy
        GIT_AUTHOR_EMAIL: 234243425+zed-zippy[bot]@users.noreply.github.com
        GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
concurrency:
  group: ${{ github.workflow }}-${{ inputs.pr_number }}
  cancel-in-progress: true
defaults:
  run:
    shell: bash -euxo pipefail {0}
background_agent_mvp perms .github/workflows/background_agent_mvp.yml
Triggers
workflow_dispatch
Runs on
ubuntu-latest
Jobs
run-mvp
Commands
  • curl -fsSL https://app.factory.ai/cli | sh echo "${HOME}/.local/bin" >> "$GITHUB_PATH" echo "DROID_BIN=${HOME}/.local/bin/droid" >> "$GITHUB_ENV" "${HOME}/.local/bin/droid" --version
  • set -euo pipefail if [ -z "$DEFAULT_REVIEWERS" ]; then DEFAULT_REVIEWERS="eholk,morgankrey,osiewicz,bennetbo" fi REVIEWERS="${INPUT_REVIEWERS:-$DEFAULT_REVIEWERS}" REVIEWERS="$(echo "$REVIEWERS" | tr -d '[:space:]')" echo "reviewers=$REVIEWERS" >> "$GITHUB_OUTPUT"
  • set -euo pipefail PREFETCH_DIR="/tmp/crash-data" ARGS=(--select-only --prefetch-dir "$PREFETCH_DIR" --org "$SENTRY_ORG") if [ -n "$INPUT_CRASH_IDS" ]; then ARGS+=(--crash-ids "$INPUT_CRASH_IDS") else TARGET_DRAFT_PRS="${INPUT_TOP:-3}" if ! [[ "$TARGET_DRAFT_PRS" =~ ^[0-9]+$ ]] || [ "$TARGET_DRAFT_PRS" -lt 1 ]; then TARGET_DRAFT_PRS="3" fi CANDIDATE_TOP=$((TARGET_DRAFT_PRS * 5)) if [ "$CANDIDATE_TOP" -gt 100 ]; then CANDIDATE_TOP=100 fi ARGS+=(--top "$CANDIDATE_TOP" --sample-size 100) fi IDS="$(python3 script/run-background-agent-mvp-local "${ARGS[@]}")" if [ -z "$IDS" ]; then echo "No candidates selected" exit 1 fi echo "Using crash IDs: $IDS" echo "ids=$IDS" >> "$GITHUB_OUTPUT"
  • set -euo pipefail git config user.name "factory-droid[bot]" git config user.email "138933559+factory-droid[bot]@users.noreply.github.com" # Crash ID format validation regex CRASH_ID_PATTERN='^[A-Za-z0-9]+-[A-Za-z0-9]+$' TARGET_DRAFT_PRS="${TARGET_DRAFT_PRS_INPUT:-3}" if ! [[ "$TARGET_DRAFT_PRS" =~ ^[0-9]+$ ]] || [ "$TARGET_DRAFT_PRS" -lt 1 ]; then TARGET_DRAFT_PRS="3" fi CREATED_DRAFT_PRS=0 IFS=',' read -r -a CRASH_ID_ARRAY <<< "$CRASH_IDS" for CRASH_ID in "${CRASH_ID_ARRAY[@]}"; do if [ "$CREATED_DRAFT_PRS" -ge "$TARGET_DRAFT_PRS" ]; then echo "Reached target draft PR count ($TARGET_DRAFT_PRS), stopping candidate processing" break fi CRASH_ID="$(echo "$CRASH_ID" | xargs)" [ -z "$CRASH_ID" ] && continue # Validate crash ID format to prevent injection via branch names or prompts if ! [[ "$CRASH_ID" =~ $CRASH_ID_PATTERN ]]; then echo "ERROR: Invalid crash ID format: '$CRASH_ID' — skipping" continue fi BRANCH="background-agent/mvp-${CRASH_ID,,}-$(date +%Y%m%d)" echo "Running crash pipeline for $CRASH_ID on $BRANCH" # Deduplication: skip if a draft PR already exists for this crash EXISTING_BRANCH_PR="$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number' || echo "")" if [ -n "$EXISTING_BRANCH_PR" ]; then echo "Draft PR #$EXISTING_BRANCH_PR already exists for $CRASH_ID — skipping" continue fi if ! git fetch origin main; then echo "WARNING: Failed to fetch origin/main for $CRASH_ID — skipping" continue fi if ! git checkout -B "$BRANCH" origin/main; then echo "WARNING: Failed to create checkout branch $BRANCH for $CRASH_ID — skipping" continue fi CRASH_DATA_FILE="/tmp/crash-data/crash-${CRASH_ID}.md" if [ ! -f "$CRASH_DATA_FILE" ]; then echo "WARNING: No pre-fetched crash data for $CRASH_ID at $CRASH_DATA_FILE — skipping" continue fi python3 -c " import sys crash_id, data_file = sys.argv[1], sys.argv[2] prompt = f'''You are running the weekly background crash-fix MVP pipeline for crash {crash_id}. The crash report has been pre-fetched and is available at: {data_file} Read this file to get the crash data. Do not call script/sentry-fetch. Required workflow: 1. Read the crash report from {data_file} 2. Read and follow .rules. 3. Follow .factory/prompts/crash/investigate.md and write ANALYSIS.md 4. Follow .factory/prompts/crash/link-issues.md and write LINKED_ISSUES.md 5. Follow .factory/prompts/crash/fix.md to implement a minimal fix with tests 6. Run validators required by the fix prompt for the affected code paths 7. Write PR_BODY.md with sections: - Crash Summary - Root Cause - Fix - Validation - Potentially Related Issues (High/Medium/Low from LINKED_ISSUES.md) - Reviewer Checklist - Release Notes (final section; format as Release Notes:, then a blank line, then one bullet like - N/A) Constraints: - Do not merge or auto-approve. - Keep changes narrowly scoped to this crash. - Do not modify files in .github/, .factory/, or script/ directories. - When investigating git history, limit your search to the last 2 weeks of commits. Do not traverse older history. - If the crash is not solvable with available context, write a clear blocker summary to PR_BODY.md. ''' import textwrap with open('/tmp/background-agent-prompt.md', 'w') as f: f.write(textwrap.dedent(prompt)) " "$CRASH_ID" "$CRASH_DATA_FILE" if ! "$DROID_BIN" exec --auto medium -m "$DROID_MODEL" -f /tmp/background-agent-prompt.md; then echo "Droid execution failed for $CRASH_ID, continuing to next candidate" continue fi for REPORT_FILE in ANALYSIS.md LINKED_ISSUES.md PR_BODY.md; do if [ -f "$REPORT_FILE" ]; then echo "::group::${CRASH_ID} ${REPORT_FILE}" cat "$REPORT_FILE" echo "::endgroup::" fi done if git diff --quiet; then echo "No code changes produced for $CRASH_ID" continue fi # Stage only expected file types — not git add -A git add -- '*.rs' '*.toml' 'Cargo.lock' 'ANALYSIS.md' 'LINKED_ISSUES.md' 'PR_BODY.md' # Reject changes to protected paths PROTECTED_CHANGES="$(git diff --cached --name-only | grep -E '^(\.github/|\.factory/|script/)' || true)" if [ -n "$PROTECTED_CHANGES" ]; then echo "ERROR: Agent modified protected paths — aborting commit for $CRASH_ID:" echo "$PROTECTED_CHANGES" git reset HEAD -- . continue fi if ! git diff --cached --quiet; then git commit -m "Fix crash ${CRASH_ID}" fi git push -u origin "$BRANCH" CRATE_PREFIX="" CHANGED_CRATES="$(git diff --cached --name-only | awk -F/ '/^crates\/[^/]+\// {print $2}' | sort -u)" if [ -n "$CHANGED_CRATES" ] && [ "$(printf "%s\n" "$CHANGED_CRATES" | wc -l | tr -d ' ')" -eq 1 ]; then CRATE_PREFIX="${CHANGED_CRATES}: " fi TITLE="${CRATE_PREFIX}Fix crash ${CRASH_ID}" BODY_FILE="PR_BODY.md" if [ ! -f "$BODY_FILE" ]; then BODY_FILE="/tmp/pr-body-${CRASH_ID}.md" printf "Automated draft crash-fix pipeline output for %s.\n\nNo PR_BODY.md was generated by the agent; please review commit and linked artifacts manually.\n" "$CRASH_ID" > "$BODY_FILE" fi python3 -c ' import re import sys path = sys.argv[1] body = open(path, encoding="utf-8").read() pattern = re.compile(r"(^|\n)Release Notes:\r?\n(?:\r?\n)*(?P<bullets>(?:\s*-\s+.*(?:\r?\n|$))+)", re.MULTILINE) match = pattern.search(body) if match: bullets = [ re.sub(r"^\s*", "", bullet) for bullet in re.findall(r"^\s*-\s+.*$", match.group("bullets"), re.MULTILINE) ] if not bullets: bullets = ["- N/A"] section = "Release Notes:\n\n" + "\n".join(bullets) body_without_release_notes = (body[: match.start()] + body[match.end() :]).rstrip() if body_without_release_notes: normalized_body = f"{body_without_release_notes}\n\n{section}\n" else: normalized_body = f"{section}\n" else: normalized_body = body.rstrip() + "\n\nRelease Notes:\n\n- N/A\n" with open(path, "w", encoding="utf-8") as file: file.write(normalized_body) ' "$BODY_FILE" EXISTING_PR="$(gh pr list --head "$BRANCH" --json number --jq '.[0].number')" if [ -n "$EXISTING_PR" ]; then gh pr edit "$EXISTING_PR" --title "$TITLE" --body-file "$BODY_FILE" PR_NUMBER="$EXISTING_PR" else PR_URL="$(gh pr create --draft --base main --head "$BRANCH" --title "$TITLE" --body-file "$BODY_FILE")" PR_NUMBER="$(basename "$PR_URL")" fi if [ -n "$REVIEWERS" ]; then IFS=',' read -r -a REVIEWER_ARRAY <<< "$REVIEWERS" for REVIEWER in "${REVIEWER_ARRAY[@]}"; do [ -z "$REVIEWER" ] && continue gh pr edit "$PR_NUMBER" --add-reviewer "$REVIEWER" || true done fi CREATED_DRAFT_PRS=$((CREATED_DRAFT_PRS + 1)) echo "Created/updated draft PRs this run: $CREATED_DRAFT_PRS/$TARGET_DRAFT_PRS" done echo "created_draft_prs=$CREATED_DRAFT_PRS" >> "$GITHUB_OUTPUT" echo "target_draft_prs=$TARGET_DRAFT_PRS" >> "$GITHUB_OUTPUT"
  • rm -rf /tmp/crash-data
  • { echo "## Background Agent MVP" echo "" echo "- Crash IDs: ${SUMMARY_CRASH_IDS:-none}" echo "- Reviewer routing: ${SUMMARY_REVIEWERS:-NOT CONFIGURED}" echo "- Draft PRs created: ${SUMMARY_CREATED_DRAFT_PRS:-0}/${SUMMARY_TARGET_DRAFT_PRS:-3}" echo "- Pipeline: investigate -> link-issues -> fix -> draft PR" } >> "$GITHUB_STEP_SUMMARY"
View raw YAML
name: background_agent_mvp

# NOTE: Scheduled runs disabled as of 2026-02-24. The workflow can still be
# triggered manually via workflow_dispatch. See Notion doc "Background Agent
# for Zed" for current status and contact info to resume this work.
on:
  # schedule:
  #   - cron: "0 16 * * 1-5"
  workflow_dispatch:
    inputs:
      crash_ids:
        description: "Optional comma-separated Sentry issue IDs (e.g. ZED-4VS,ZED-123)"
        required: false
        type: string
      reviewers:
        description: "Optional comma-separated GitHub reviewer handles"
        required: false
        type: string
      top:
        description: "Top N candidates when crash_ids is empty"
        required: false
        type: string
        default: "3"

permissions:
  contents: write
  pull-requests: write

env:
  FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
  DROID_MODEL: claude-opus-4-5-20251101
  SENTRY_ORG: zed-dev

jobs:
  run-mvp:
    runs-on: ubuntu-latest
    timeout-minutes: 180

    steps:
      - name: Checkout repository
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          fetch-depth: 0

      - name: Install Droid CLI
        run: |
          curl -fsSL https://app.factory.ai/cli | sh
          echo "${HOME}/.local/bin" >> "$GITHUB_PATH"
          echo "DROID_BIN=${HOME}/.local/bin/droid" >> "$GITHUB_ENV"
          "${HOME}/.local/bin/droid" --version

      - name: Setup Python
        uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
        with:
          python-version: "3.12"

      - name: Resolve reviewers
        id: reviewers
        env:
          INPUT_REVIEWERS: ${{ inputs.reviewers }}
          DEFAULT_REVIEWERS: ${{ vars.BACKGROUND_AGENT_REVIEWERS }}
        run: |
          set -euo pipefail
          if [ -z "$DEFAULT_REVIEWERS" ]; then
            DEFAULT_REVIEWERS="eholk,morgankrey,osiewicz,bennetbo"
          fi
          REVIEWERS="${INPUT_REVIEWERS:-$DEFAULT_REVIEWERS}"
          REVIEWERS="$(echo "$REVIEWERS" | tr -d '[:space:]')"
          echo "reviewers=$REVIEWERS" >> "$GITHUB_OUTPUT"

      - name: Select crash candidates
        id: candidates
        env:
          INPUT_CRASH_IDS: ${{ inputs.crash_ids }}
          INPUT_TOP: ${{ inputs.top }}
          SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_BACKGROUND_AGENT_MVP_TOKEN }}
        run: |
          set -euo pipefail

          PREFETCH_DIR="/tmp/crash-data"
          ARGS=(--select-only --prefetch-dir "$PREFETCH_DIR" --org "$SENTRY_ORG")
          if [ -n "$INPUT_CRASH_IDS" ]; then
            ARGS+=(--crash-ids "$INPUT_CRASH_IDS")
          else
            TARGET_DRAFT_PRS="${INPUT_TOP:-3}"
            if ! [[ "$TARGET_DRAFT_PRS" =~ ^[0-9]+$ ]] || [ "$TARGET_DRAFT_PRS" -lt 1 ]; then
              TARGET_DRAFT_PRS="3"
            fi
            CANDIDATE_TOP=$((TARGET_DRAFT_PRS * 5))
            if [ "$CANDIDATE_TOP" -gt 100 ]; then
              CANDIDATE_TOP=100
            fi
            ARGS+=(--top "$CANDIDATE_TOP" --sample-size 100)
          fi

          IDS="$(python3 script/run-background-agent-mvp-local "${ARGS[@]}")"

          if [ -z "$IDS" ]; then
            echo "No candidates selected"
            exit 1
          fi

          echo "Using crash IDs: $IDS"
          echo "ids=$IDS" >> "$GITHUB_OUTPUT"

      - name: Run background agent pipeline per crash
        id: pipeline
        env:
          GH_TOKEN: ${{ github.token }}
          REVIEWERS: ${{ steps.reviewers.outputs.reviewers }}
          CRASH_IDS: ${{ steps.candidates.outputs.ids }}
          TARGET_DRAFT_PRS_INPUT: ${{ inputs.top }}
        run: |
          set -euo pipefail

          git config user.name "factory-droid[bot]"
          git config user.email "138933559+factory-droid[bot]@users.noreply.github.com"

          # Crash ID format validation regex
          CRASH_ID_PATTERN='^[A-Za-z0-9]+-[A-Za-z0-9]+$'
          TARGET_DRAFT_PRS="${TARGET_DRAFT_PRS_INPUT:-3}"
          if ! [[ "$TARGET_DRAFT_PRS" =~ ^[0-9]+$ ]] || [ "$TARGET_DRAFT_PRS" -lt 1 ]; then
            TARGET_DRAFT_PRS="3"
          fi
          CREATED_DRAFT_PRS=0

          IFS=',' read -r -a CRASH_ID_ARRAY <<< "$CRASH_IDS"

          for CRASH_ID in "${CRASH_ID_ARRAY[@]}"; do
            if [ "$CREATED_DRAFT_PRS" -ge "$TARGET_DRAFT_PRS" ]; then
              echo "Reached target draft PR count ($TARGET_DRAFT_PRS), stopping candidate processing"
              break
            fi

            CRASH_ID="$(echo "$CRASH_ID" | xargs)"
            [ -z "$CRASH_ID" ] && continue

            # Validate crash ID format to prevent injection via branch names or prompts
            if ! [[ "$CRASH_ID" =~ $CRASH_ID_PATTERN ]]; then
              echo "ERROR: Invalid crash ID format: '$CRASH_ID' — skipping"
              continue
            fi

            BRANCH="background-agent/mvp-${CRASH_ID,,}-$(date +%Y%m%d)"
            echo "Running crash pipeline for $CRASH_ID on $BRANCH"

            # Deduplication: skip if a draft PR already exists for this crash
            EXISTING_BRANCH_PR="$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number' || echo "")"
            if [ -n "$EXISTING_BRANCH_PR" ]; then
              echo "Draft PR #$EXISTING_BRANCH_PR already exists for $CRASH_ID — skipping"
              continue
            fi

            if ! git fetch origin main; then
              echo "WARNING: Failed to fetch origin/main for $CRASH_ID — skipping"
              continue
            fi

            if ! git checkout -B "$BRANCH" origin/main; then
              echo "WARNING: Failed to create checkout branch $BRANCH for $CRASH_ID — skipping"
              continue
            fi

            CRASH_DATA_FILE="/tmp/crash-data/crash-${CRASH_ID}.md"
            if [ ! -f "$CRASH_DATA_FILE" ]; then
              echo "WARNING: No pre-fetched crash data for $CRASH_ID at $CRASH_DATA_FILE — skipping"
              continue
            fi

            python3 -c "
          import sys
          crash_id, data_file = sys.argv[1], sys.argv[2]
          prompt = f'''You are running the weekly background crash-fix MVP pipeline for crash {crash_id}.

          The crash report has been pre-fetched and is available at: {data_file}
          Read this file to get the crash data. Do not call script/sentry-fetch.

          Required workflow:
          1. Read the crash report from {data_file}
          2. Read and follow .rules.
          3. Follow .factory/prompts/crash/investigate.md and write ANALYSIS.md
          4. Follow .factory/prompts/crash/link-issues.md and write LINKED_ISSUES.md
          5. Follow .factory/prompts/crash/fix.md to implement a minimal fix with tests
          6. Run validators required by the fix prompt for the affected code paths
          7. Write PR_BODY.md with sections:
             - Crash Summary
             - Root Cause
             - Fix
             - Validation
             - Potentially Related Issues (High/Medium/Low from LINKED_ISSUES.md)
             - Reviewer Checklist
             - Release Notes (final section; format as Release Notes:, then a blank line, then one bullet like - N/A)

          Constraints:
          - Do not merge or auto-approve.
          - Keep changes narrowly scoped to this crash.
          - Do not modify files in .github/, .factory/, or script/ directories.
          - When investigating git history, limit your search to the last 2 weeks of commits. Do not traverse older history.
          - If the crash is not solvable with available context, write a clear blocker summary to PR_BODY.md.
          '''
          import textwrap
          with open('/tmp/background-agent-prompt.md', 'w') as f:
              f.write(textwrap.dedent(prompt))
          " "$CRASH_ID" "$CRASH_DATA_FILE"

            if ! "$DROID_BIN" exec --auto medium -m "$DROID_MODEL" -f /tmp/background-agent-prompt.md; then
              echo "Droid execution failed for $CRASH_ID, continuing to next candidate"
              continue
            fi

            for REPORT_FILE in ANALYSIS.md LINKED_ISSUES.md PR_BODY.md; do
              if [ -f "$REPORT_FILE" ]; then
                echo "::group::${CRASH_ID} ${REPORT_FILE}"
                cat "$REPORT_FILE"
                echo "::endgroup::"
              fi
            done

            if git diff --quiet; then
              echo "No code changes produced for $CRASH_ID"
              continue
            fi

            # Stage only expected file types — not git add -A
            git add -- '*.rs' '*.toml' 'Cargo.lock' 'ANALYSIS.md' 'LINKED_ISSUES.md' 'PR_BODY.md'

            # Reject changes to protected paths
            PROTECTED_CHANGES="$(git diff --cached --name-only | grep -E '^(\.github/|\.factory/|script/)' || true)"
            if [ -n "$PROTECTED_CHANGES" ]; then
              echo "ERROR: Agent modified protected paths — aborting commit for $CRASH_ID:"
              echo "$PROTECTED_CHANGES"
              git reset HEAD -- .
              continue
            fi

            if ! git diff --cached --quiet; then
              git commit -m "Fix crash ${CRASH_ID}"
            fi

            git push -u origin "$BRANCH"

            CRATE_PREFIX=""
            CHANGED_CRATES="$(git diff --cached --name-only | awk -F/ '/^crates\/[^/]+\// {print $2}' | sort -u)"
            if [ -n "$CHANGED_CRATES" ] && [ "$(printf "%s\n" "$CHANGED_CRATES" | wc -l | tr -d ' ')" -eq 1 ]; then
              CRATE_PREFIX="${CHANGED_CRATES}: "
            fi

            TITLE="${CRATE_PREFIX}Fix crash ${CRASH_ID}"
            BODY_FILE="PR_BODY.md"
            if [ ! -f "$BODY_FILE" ]; then
              BODY_FILE="/tmp/pr-body-${CRASH_ID}.md"
              printf "Automated draft crash-fix pipeline output for %s.\n\nNo PR_BODY.md was generated by the agent; please review commit and linked artifacts manually.\n" "$CRASH_ID" > "$BODY_FILE"
            fi

            python3 -c '
            import re
            import sys

            path = sys.argv[1]
            body = open(path, encoding="utf-8").read()
            pattern = re.compile(r"(^|\n)Release Notes:\r?\n(?:\r?\n)*(?P<bullets>(?:\s*-\s+.*(?:\r?\n|$))+)", re.MULTILINE)
            match = pattern.search(body)

            if match:
                bullets = [
                    re.sub(r"^\s*", "", bullet)
                    for bullet in re.findall(r"^\s*-\s+.*$", match.group("bullets"), re.MULTILINE)
                ]
                if not bullets:
                    bullets = ["- N/A"]
                section = "Release Notes:\n\n" + "\n".join(bullets)
                body_without_release_notes = (body[: match.start()] + body[match.end() :]).rstrip()
                if body_without_release_notes:
                    normalized_body = f"{body_without_release_notes}\n\n{section}\n"
                else:
                    normalized_body = f"{section}\n"
            else:
                normalized_body = body.rstrip() + "\n\nRelease Notes:\n\n- N/A\n"

            with open(path, "w", encoding="utf-8") as file:
                file.write(normalized_body)
            ' "$BODY_FILE"

            EXISTING_PR="$(gh pr list --head "$BRANCH" --json number --jq '.[0].number')"
            if [ -n "$EXISTING_PR" ]; then
              gh pr edit "$EXISTING_PR" --title "$TITLE" --body-file "$BODY_FILE"
              PR_NUMBER="$EXISTING_PR"
            else
              PR_URL="$(gh pr create --draft --base main --head "$BRANCH" --title "$TITLE" --body-file "$BODY_FILE")"
              PR_NUMBER="$(basename "$PR_URL")"
            fi

            if [ -n "$REVIEWERS" ]; then
              IFS=',' read -r -a REVIEWER_ARRAY <<< "$REVIEWERS"
              for REVIEWER in "${REVIEWER_ARRAY[@]}"; do
                [ -z "$REVIEWER" ] && continue
                gh pr edit "$PR_NUMBER" --add-reviewer "$REVIEWER" || true
              done
            fi

            CREATED_DRAFT_PRS=$((CREATED_DRAFT_PRS + 1))
            echo "Created/updated draft PRs this run: $CREATED_DRAFT_PRS/$TARGET_DRAFT_PRS"
          done

          echo "created_draft_prs=$CREATED_DRAFT_PRS" >> "$GITHUB_OUTPUT"
          echo "target_draft_prs=$TARGET_DRAFT_PRS" >> "$GITHUB_OUTPUT"

      - name: Cleanup pre-fetched crash data
        if: always()
        run: rm -rf /tmp/crash-data

      - name: Workflow summary
        if: always()
        env:
          SUMMARY_CRASH_IDS: ${{ steps.candidates.outputs.ids }}
          SUMMARY_REVIEWERS: ${{ steps.reviewers.outputs.reviewers }}
          SUMMARY_CREATED_DRAFT_PRS: ${{ steps.pipeline.outputs.created_draft_prs }}
          SUMMARY_TARGET_DRAFT_PRS: ${{ steps.pipeline.outputs.target_draft_prs }}
        run: |
          {
            echo "## Background Agent MVP"
            echo ""
            echo "- Crash IDs: ${SUMMARY_CRASH_IDS:-none}"
            echo "- Reviewer routing: ${SUMMARY_REVIEWERS:-NOT CONFIGURED}"
            echo "- Draft PRs created: ${SUMMARY_CREATED_DRAFT_PRS:-0}/${SUMMARY_TARGET_DRAFT_PRS:-3}"
            echo "- Pipeline: investigate -> link-issues -> fix -> draft PR"
          } >> "$GITHUB_STEP_SUMMARY"

concurrency:
  group: background-agent-mvp
  cancel-in-progress: false
bump_collab_staging .github/workflows/bump_collab_staging.yml
Triggers
schedule
Runs on
namespace-profile-2x4-ubuntu-2404
Jobs
update-collab-staging-tag
Commands
  • git config user.name github-actions git config user.email github-actions@github.com git tag -f collab-staging git push origin collab-staging --force
View raw YAML
name: Bump collab-staging Tag

on:
  schedule:
    # Fire every day at 16:00 UTC (At the start of the US workday)
    - cron: "0 16 * * *"

jobs:
  update-collab-staging-tag:
    if: github.repository_owner == 'zed-industries'
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
      - name: Checkout repository
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          fetch-depth: 0

      - name: Update collab-staging tag
        run: |
          git config user.name github-actions
          git config user.email github-actions@github.com
          git tag -f collab-staging
          git push origin collab-staging --force
bump_patch_version .github/workflows/bump_patch_version.yml
Triggers
workflow_dispatch
Runs on
namespace-profile-16x32-ubuntu-2204
Jobs
run_bump_patch_version
Actions
actions/create-github-app-token
Commands
  • channel="$(cat crates/zed/RELEASE_CHANNEL)" tag_suffix="" case $channel in stable) ;; preview) tag_suffix="-pre" ;; *) echo "this must be run on either of stable|preview release branches" >&2 exit 1 ;; esac which cargo-set-version > /dev/null || cargo install cargo-edit -f --no-default-features --features "set-version" output="$(cargo set-version -p zed --bump patch 2>&1 | sed 's/.* //')" git commit -am "Bump to $output for @$GITHUB_ACTOR" git tag "v${output}${tag_suffix}" git push origin HEAD "v${output}${tag_suffix}"
View raw YAML
# Generated from xtask::workflows::bump_patch_version
# Rebuild with `cargo xtask workflows`.
name: bump_patch_version
on:
  workflow_dispatch:
    inputs:
      branch:
        description: Branch name to run on
        required: true
        type: string
jobs:
  run_bump_patch_version:
    if: github.repository_owner == 'zed-industries'
    runs-on: namespace-profile-16x32-ubuntu-2204
    steps:
    - id: generate-token
      name: steps::authenticate_as_zippy
      uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859
      with:
        app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
        private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
        ref: ${{ inputs.branch }}
        token: ${{ steps.generate-token.outputs.token }}
    - name: bump_patch_version::run_bump_patch_version::bump_patch_version
      run: |
        channel="$(cat crates/zed/RELEASE_CHANNEL)"

        tag_suffix=""
        case $channel in
          stable)
            ;;
          preview)
            tag_suffix="-pre"
            ;;
          *)
            echo "this must be run on either of stable|preview release branches" >&2
            exit 1
            ;;
        esac
        which cargo-set-version > /dev/null || cargo install cargo-edit -f --no-default-features --features "set-version"
        output="$(cargo set-version -p zed --bump patch 2>&1 | sed 's/.* //')"
        git commit -am "Bump to $output for @$GITHUB_ACTOR"
        git tag "v${output}${tag_suffix}"
        git push origin HEAD "v${output}${tag_suffix}"
      env:
        GIT_COMMITTER_NAME: Zed Zippy
        GIT_COMMITTER_EMAIL: 234243425+zed-zippy[bot]@users.noreply.github.com
        GIT_AUTHOR_NAME: Zed Zippy
        GIT_AUTHOR_EMAIL: 234243425+zed-zippy[bot]@users.noreply.github.com
        GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
concurrency:
  group: ${{ github.workflow }}-${{ inputs.branch }}
  cancel-in-progress: true
defaults:
  run:
    shell: bash -euxo pipefail {0}
catch_blank_issues perms .github/workflows/catch_blank_issues.yml
Triggers
issues
Runs on
namespace-profile-2x4-ubuntu-2404
Jobs
add-triage-label
Actions
actions/create-github-app-token
Commands
  • echo "::notice::Skipping issue #$ISSUE_NUMBER - actor is staff member"
View raw YAML
name: "Label new and reopened blank issues for triage"

on:
  issues:
    types:
      - opened
      - reopened

permissions:
  contents: read

jobs:
  add-triage-label:
    if: github.repository == 'zed-industries/zed'
    runs-on: namespace-profile-2x4-ubuntu-2404
    timeout-minutes: 5
    steps:
      - id: get-app-token
        uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
        with:
          app-id: ${{ secrets.ZED_COMMUNITY_BOT_APP_ID }}
          private-key: ${{ secrets.ZED_COMMUNITY_BOT_PRIVATE_KEY }}
          owner: zed-industries

      - id: check-staff
        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
        with:
          github-token: ${{ steps.get-app-token.outputs.token }}
          script: |
            try {
              const response = await github.rest.teams.getMembershipForUserInOrg({
                org: 'zed-industries',
                team_slug: 'staff',
                username: context.payload.sender.login
              });
              return response.data.state === 'active';
            } catch (error) {
              if (error.status === 404) {
                return false;
              }
              throw error;
            }

      - if: steps.check-staff.outputs.result == 'true'
        env:
          ISSUE_NUMBER: ${{ github.event.issue.number }}
        run: |
          echo "::notice::Skipping issue #$ISSUE_NUMBER - actor is staff member"

      - if: steps.check-staff.outputs.result == 'false'
        id: add-label
        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
        with:
          github-token: ${{ steps.get-app-token.outputs.token }}
          script: |
            const issue = context.payload.issue;
            const hasTriageLabel = issue.labels.some(
              label => label.name === 'state:needs triage'
            );

            if (hasTriageLabel) {
              console.log('Issue already has state:needs triage, skipping');
              return;
            }

            await github.rest.issues.addLabels({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: issue.number,
              labels: ['state:needs triage']
            });

            console.log(`Added state:needs triage to issue #${issue.number}`);
cherry_pick .github/workflows/cherry_pick.yml
Triggers
workflow_dispatch
Runs on
namespace-profile-2x4-ubuntu-2404
Jobs
run_cherry_pick
Actions
actions/create-github-app-token
Commands
  • ./script/cherry-pick "$BRANCH" "$COMMIT" "$CHANNEL"
View raw YAML
# Generated from xtask::workflows::cherry_pick
# Rebuild with `cargo xtask workflows`.
name: cherry_pick
run-name: 'cherry_pick to ${{ inputs.channel }} #${{ inputs.pr_number }}'
on:
  workflow_dispatch:
    inputs:
      commit:
        description: commit
        required: true
        type: string
      branch:
        description: branch
        required: true
        type: string
      channel:
        description: channel
        required: true
        type: string
      pr_number:
        description: pr_number
        required: true
        type: string
jobs:
  run_cherry_pick:
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - id: generate-token
      name: steps::authenticate_as_zippy
      uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859
      with:
        app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
        private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
    - name: cherry_pick::run_cherry_pick::cherry_pick
      run: ./script/cherry-pick "$BRANCH" "$COMMIT" "$CHANNEL"
      env:
        BRANCH: ${{ inputs.branch }}
        COMMIT: ${{ inputs.commit }}
        CHANNEL: ${{ inputs.channel }}
        GIT_COMMITTER_NAME: Zed Zippy
        GIT_COMMITTER_EMAIL: hi@zed.dev
        GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
defaults:
  run:
    shell: bash -euxo pipefail {0}
comment_on_potential_duplicate_issues .github/workflows/comment_on_potential_duplicate_issues.yml
Triggers
issues, workflow_dispatch
Runs on
ubuntu-latest
Jobs
identify-duplicates
Actions
actions/create-github-app-token
Commands
  • pip install requests
  • python script/github-check-new-issue-for-duplicates.py "$ISSUE_NUMBER" > result.json cat result.json
  • echo '```json' >> "$GITHUB_STEP_SUMMARY" if [[ -f result.json ]] && jq empty result.json 2>/dev/null; then jq . result.json >> "$GITHUB_STEP_SUMMARY" else echo '{"error": "No valid result.json generated. Check logs for details."}' >> "$GITHUB_STEP_SUMMARY" fi echo '```' >> "$GITHUB_STEP_SUMMARY"
View raw YAML
name: Comment on potential duplicate bug/crash reports

on:
  issues:
    types: [opened]
  workflow_dispatch:
    inputs:
      issue_number:
        description: "Issue number to analyze"
        required: true
        type: number

concurrency:
  group: potential-duplicate-check-${{ github.event.issue.number || inputs.issue_number }}
  cancel-in-progress: true

jobs:
  identify-duplicates:
    # For manual testing, allow running on any branch; for automatic runs, only on main repo
    if: github.event_name == 'workflow_dispatch' || github.repository == 'zed-industries/zed'
    runs-on: ubuntu-latest
    timeout-minutes: 5

    permissions:
      contents: read
      issues: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          sparse-checkout: script/github-check-new-issue-for-duplicates.py
          sparse-checkout-cone-mode: false

      - name: Get github app token
        id: get-app-token
        uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
        with:
          app-id: ${{ secrets.ZED_COMMUNITY_BOT_APP_ID }}
          private-key: ${{ secrets.ZED_COMMUNITY_BOT_PRIVATE_KEY }}
          owner: zed-industries

      - name: Set up Python
        uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
        with:
          python-version: "3.12"

      - name: Install dependencies
        run: pip install requests

      - name: Run duplicate detection
        id: detect
        env:
          GITHUB_TOKEN: ${{ steps.get-app-token.outputs.token }}
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY_ISSUE_DEDUP }}
          ISSUE_NUMBER: ${{ github.event.issue.number || inputs.issue_number }}
        run: |
          python script/github-check-new-issue-for-duplicates.py "$ISSUE_NUMBER" > result.json
          cat result.json

      - name: Write job summary
        if: always()
        run: |
          echo '```json' >> "$GITHUB_STEP_SUMMARY"
          if [[ -f result.json ]] && jq empty result.json 2>/dev/null; then
            jq . result.json >> "$GITHUB_STEP_SUMMARY"
          else
            echo '{"error": "No valid result.json generated. Check logs for details."}' >> "$GITHUB_STEP_SUMMARY"
          fi
          echo '```' >> "$GITHUB_STEP_SUMMARY"
community_champion_auto_labeler .github/workflows/community_champion_auto_labeler.yml
Triggers
issues, pull_request_target
Runs on
namespace-profile-2x4-ubuntu-2404
Jobs
label_community_champion
View raw YAML
name: Community Champion Auto Labeler

on:
  issues:
    types: [opened]
  pull_request_target:
    types: [opened]

jobs:
  label_community_champion:
    if: github.repository_owner == 'zed-industries'
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
      - name: Check if author is a community champion and apply label
        uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
        env:
          COMMUNITY_CHAMPIONS: |
            0x2CA
            5brian
            5herlocked
            abdelq
            afgomez
            AidanV
            akbxr
            AlvaroParker
            amtoaer
            artemevsevev
            bajrangCoder
            bcomnes
            Be-ing
            blopker
            bnjjj
            bobbymannino
            CharlesChen0823
            chbk
            davewa
            davidbarsky
            ddoemonn
            djsauble
            errmayank
            fantacell
            fdncred
            findrakecil
            FloppyDisco
            gko
            huacnlee
            imumesh18
            injust
            jacobtread
            jansol
            jeffreyguenther
            jenslys
            jongretar
            lemorage
            lingyaochu
            lnay
            marcocondrache
            marius851000
            mikebronner
            ognevny
            PKief
            playdohface
            RemcoSmitsDev
            rgbkrk
            romaninsh
            rxptr
            Simek
            someone13574
            sourcefrog
            suxiaoshao
            Takk8IS
            tartarughina
            thedadams
            tidely
            timvermeulen
            valentinegb
            versecafe
            vitallium
            WhySoBad
            ya7010
            Zertsov
        with:
          script: |
            const communityChampions = process.env.COMMUNITY_CHAMPIONS
              .split('\n')
              .map(handle => handle.trim().toLowerCase())
              .filter(handle => handle.length > 0);

            let author;
            if (context.eventName === 'issues') {
              author = context.payload.issue.user.login;
            } else if (context.eventName === 'pull_request_target') {
              author = context.payload.pull_request.user.login;
            }

            if (!author || !communityChampions.includes(author.toLowerCase())) {
              return;
            }

            const issueNumber = context.payload.issue?.number || context.payload.pull_request?.number;

            try {
              await github.rest.issues.addLabels({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: issueNumber,
                labels: ['community champion']
              });

              console.log(`Applied 'community champion' label to #${issueNumber} by ${author}`);
            } catch (error) {
              console.error(`Failed to apply label: ${error.message}`);
            }
community_close_stale_issues .github/workflows/community_close_stale_issues.yml
Triggers
schedule, workflow_dispatch
Runs on
namespace-profile-2x4-ubuntu-2404
Jobs
stale
Actions
actions/stale
View raw YAML
name: "Close Stale Issues"
on:
  schedule:
    - cron: "0 2 * * 5"
  workflow_dispatch:
    inputs:
      debug-only:
        description: "Run in dry-run mode (no changes made)"
        type: boolean
        default: false
      operations-per-run:
        description: "Max number of issues to process (default: 1000)"
        type: number
        default: 1000

jobs:
  stale:
    if: github.repository_owner == 'zed-industries'
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
      - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          stale-issue-message: >
            Zed development moves fast and a significant number of bugs become outdated.
            If you can reproduce this bug on the latest stable Zed, please let us know by leaving a comment with the Zed version,
            it helps us focus on the right issues.
            If the bug doesn't appear for you anymore, feel free to close the issue yourself; otherwise, the bot will close it in a couple of weeks.
            But even after it's closed by the bot, you can leave a comment with the version where the bug is reproducible and we'll reopen the issue.
            Thanks!
          close-issue-message: "This issue was closed due to inactivity. If you're still experiencing this problem, please leave a comment with your Zed version so that we can reopen the issue."
          days-before-stale: 90
          days-before-close: 21
          only-issue-types: "Bug,Crash"
          operations-per-run: ${{ inputs.operations-per-run || 2000 }}
          ascending: true
          enable-statistics: true
          debug-only: ${{ inputs.debug-only }}
          stale-issue-label: "stale"
          exempt-issue-labels: "never stale"
community_update_all_top_ranking_issues .github/workflows/community_update_all_top_ranking_issues.yml
Triggers
schedule, workflow_dispatch
Runs on
namespace-profile-2x4-ubuntu-2404
Jobs
update_top_ranking_issues
Actions
astral-sh/setup-uv
Commands
  • uv python install 3.13
  • uv sync --project script/update_top_ranking_issues -p 3.13
  • uv run --project script/update_top_ranking_issues script/update_top_ranking_issues/main.py --github-token "$GITHUB_TOKEN" --issue-reference-number 5393
View raw YAML
name: Update All Top Ranking Issues

on:
  schedule:
    - cron: "0 */12 * * *"
  workflow_dispatch:

jobs:
  update_top_ranking_issues:
    runs-on: namespace-profile-2x4-ubuntu-2404
    if: github.repository == 'zed-industries/zed'
    steps:
      - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
      - name: Set up uv
        uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
        with:
          version: "latest"
          enable-cache: true
          cache-dependency-glob: "script/update_top_ranking_issues/pyproject.toml"
      - name: Install Python 3.13
        run: uv python install 3.13
      - name: Install dependencies
        run: uv sync --project script/update_top_ranking_issues -p 3.13
      - name: Run script
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: uv run --project script/update_top_ranking_issues script/update_top_ranking_issues/main.py --github-token "$GITHUB_TOKEN" --issue-reference-number 5393
community_update_weekly_top_ranking_issues .github/workflows/community_update_weekly_top_ranking_issues.yml
Triggers
schedule, workflow_dispatch
Runs on
namespace-profile-2x4-ubuntu-2404
Jobs
update_top_ranking_issues
Actions
astral-sh/setup-uv
Commands
  • uv python install 3.13
  • uv sync --project script/update_top_ranking_issues -p 3.13
  • uv run --project script/update_top_ranking_issues script/update_top_ranking_issues/main.py --github-token "$GITHUB_TOKEN" --issue-reference-number 6952 --query-day-interval 7
View raw YAML
name: Update Weekly Top Ranking Issues

on:
  schedule:
    - cron: "0 15 * * *"
  workflow_dispatch:

jobs:
  update_top_ranking_issues:
    runs-on: namespace-profile-2x4-ubuntu-2404
    if: github.repository == 'zed-industries/zed'
    steps:
      - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
      - name: Set up uv
        uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
        with:
          version: "latest"
          enable-cache: true
          cache-dependency-glob: "script/update_top_ranking_issues/pyproject.toml"
      - name: Install Python 3.13
        run: uv python install 3.13
      - name: Install dependencies
        run: uv sync --project script/update_top_ranking_issues -p 3.13
      - name: Run script
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: uv run --project script/update_top_ranking_issues script/update_top_ranking_issues/main.py --github-token "$GITHUB_TOKEN" --issue-reference-number 6952 --query-day-interval 7
compare_perf .github/workflows/compare_perf.yml
Triggers
workflow_dispatch
Runs on
namespace-profile-16x32-ubuntu-2204
Jobs
run_perf
Actions
taiki-e/install-action
Commands
  • mkdir -p ./../.cargo cp ./.cargo/ci-config.toml ./../.cargo/config.toml
  • ./script/linux
  • ./script/download-wasi-sdk
  • git fetch origin "$REF_NAME" && git checkout "$REF_NAME"
  • if [ -n "$CRATE_NAME" ]; then cargo perf-test -p "$CRATE_NAME" -- --json="$REF_NAME"; else cargo perf-test -p vim -- --json="$REF_NAME"; fi
  • git fetch origin "$REF_NAME" && git checkout "$REF_NAME"
  • if [ -n "$CRATE_NAME" ]; then cargo perf-test -p "$CRATE_NAME" -- --json="$REF_NAME"; else cargo perf-test -p vim -- --json="$REF_NAME"; fi
  • cargo perf-compare --save=results.md "$BASE" "$HEAD"
View raw YAML
# Generated from xtask::workflows::compare_perf
# Rebuild with `cargo xtask workflows`.
name: compare_perf
on:
  workflow_dispatch:
    inputs:
      head:
        description: head
        required: true
        type: string
      base:
        description: base
        required: true
        type: string
      crate_name:
        description: crate_name
        type: string
        default: ''
jobs:
  run_perf:
    runs-on: namespace-profile-16x32-ubuntu-2204
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_cargo_config
      run: |
        mkdir -p ./../.cargo
        cp ./.cargo/ci-config.toml ./../.cargo/config.toml
    - name: steps::setup_linux
      run: ./script/linux
    - name: steps::download_wasi_sdk
      run: ./script/download-wasi-sdk
    - name: compare_perf::run_perf::install_hyperfine
      uses: taiki-e/install-action@b4f2d5cb8597b15997c8ede873eb6185efc5f0ad
    - name: steps::git_checkout
      run: git fetch origin "$REF_NAME" && git checkout "$REF_NAME"
      env:
        REF_NAME: ${{ inputs.base }}
    - name: compare_perf::run_perf::cargo_perf_test
      run: |2-

                    if [ -n "$CRATE_NAME" ]; then
                        cargo perf-test -p "$CRATE_NAME" -- --json="$REF_NAME";
                    else
                        cargo perf-test -p vim -- --json="$REF_NAME";
                    fi
      env:
        REF_NAME: ${{ inputs.base }}
        CRATE_NAME: ${{ inputs.crate_name }}
    - name: steps::git_checkout
      run: git fetch origin "$REF_NAME" && git checkout "$REF_NAME"
      env:
        REF_NAME: ${{ inputs.head }}
    - name: compare_perf::run_perf::cargo_perf_test
      run: |2-

                    if [ -n "$CRATE_NAME" ]; then
                        cargo perf-test -p "$CRATE_NAME" -- --json="$REF_NAME";
                    else
                        cargo perf-test -p vim -- --json="$REF_NAME";
                    fi
      env:
        REF_NAME: ${{ inputs.head }}
        CRATE_NAME: ${{ inputs.crate_name }}
    - name: compare_perf::run_perf::compare_runs
      run: cargo perf-compare --save=results.md "$BASE" "$HEAD"
      env:
        BASE: ${{ inputs.base }}
        HEAD: ${{ inputs.head }}
    - name: '@actions/upload-artifact results.md'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: results.md
        path: results.md
        if-no-files-found: error
    - name: steps::cleanup_cargo_config
      if: always()
      run: |
        rm -rf ./../.cargo
defaults:
  run:
    shell: bash -euxo pipefail {0}
congrats .github/workflows/congrats.yml
Triggers
push
Runs on
namespace-profile-2x4-ubuntu-2404
Jobs
check-author, congrats
View raw YAML
name: Congratsbot

on:
  push:
    branches: [main]

jobs:
  check-author:
    if: ${{ github.repository_owner == 'zed-industries' }}
    runs-on: namespace-profile-2x4-ubuntu-2404
    outputs:
      should_congratulate: ${{ steps.check.outputs.should_congratulate }}
    steps:
      - name: Get PR info and check if author is external
        id: check
        uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
        with:
          github-token: ${{ secrets.CONGRATSBOT_GITHUB_TOKEN }}
          script: |
            const { data: prs } = await github.rest.repos.listPullRequestsAssociatedWithCommit({
              owner: context.repo.owner,
              repo: context.repo.repo,
              commit_sha: context.sha
            });

            if (prs.length === 0) {
              core.setOutput('should_congratulate', 'false');
              return;
            }

            const mergedPR = prs.find(pr => pr.merged_at !== null) || prs[0];

            if (mergedPR.user.type === "Bot") {
              // They are a good bot, but not good enough to be congratulated
              core.setOutput('should_congratulate', 'false');
              return;
            }

            const prAuthor = mergedPR.user.login;

            try {
              await github.rest.teams.getMembershipForUserInOrg({
                org: 'zed-industries',
                team_slug: 'staff',
                username: prAuthor
              });
              core.setOutput('should_congratulate', 'false');
            } catch (error) {
              if (error.status === 404) {
                core.setOutput('should_congratulate', 'true');
              } else {
                console.error(`Error checking team membership: ${error.message}`);
                core.setOutput('should_congratulate', 'false');
              }
            }

  congrats:
    needs: check-author
    if: needs.check-author.outputs.should_congratulate == 'true'
    uses: withastro/automation/.github/workflows/congratsbot.yml@a5bd0c5748c4d56e687cdd558064f9ee8adfb1f2 # main
    with:
      EMOJIS: 🎉,🎊,🧑‍🚀,🥳,🙌,🚀,🦀,🔥,🚢
    secrets:
      DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_CONGRATS }}
danger .github/workflows/danger.yml
Triggers
pull_request
Runs on
namespace-profile-2x4-ubuntu-2404
Jobs
danger
Actions
pnpm/action-setup
Commands
  • pnpm install --dir script/danger
  • pnpm run --dir script/danger danger ci
View raw YAML
# Generated from xtask::workflows::danger
# Rebuild with `cargo xtask workflows`.
name: danger
on:
  pull_request:
    types:
    - opened
    - synchronize
    - reopened
    - edited
    branches:
    - main
jobs:
  danger:
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_pnpm
      uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2
      with:
        version: '9'
    - name: steps::setup_node
      uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
      with:
        node-version: '20'
        cache: pnpm
        cache-dependency-path: script/danger/pnpm-lock.yaml
    - name: danger::danger_job::install_deps
      run: pnpm install --dir script/danger
    - name: danger::danger_job::run
      run: pnpm run --dir script/danger danger ci
      env:
        GITHUB_TOKEN: not_a_real_token
        DANGER_GITHUB_API_BASE_URL: https://danger-proxy.zed.dev/github
defaults:
  run:
    shell: bash -euxo pipefail {0}
deploy_cloudflare .github/workflows/deploy_cloudflare.yml
Triggers
push
Runs on
namespace-profile-16x32-ubuntu-2204
Jobs
deploy-docs
Actions
cloudflare/wrangler-action, cloudflare/wrangler-action, cloudflare/wrangler-action, cloudflare/wrangler-action
Commands
  • cp ./.cargo/collab-config.toml ./.cargo/config.toml
View raw YAML
name: Deploy Docs

on:
  push:
    branches:
      - main

jobs:
  deploy-docs:
    name: Deploy Docs
    if: github.repository_owner == 'zed-industries'
    runs-on: namespace-profile-16x32-ubuntu-2204

    steps:
      - name: Checkout repo
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          clean: false

      - name: Set up default .cargo/config.toml
        run: cp ./.cargo/collab-config.toml ./.cargo/config.toml

      - name: Build docs
        uses: ./.github/actions/build_docs
        env:
          CC: clang
          CXX: clang++
          DOCS_AMPLITUDE_API_KEY: ${{ secrets.DOCS_AMPLITUDE_API_KEY }}
          DOCS_CONSENT_IO_INSTANCE: ${{ secrets.DOCS_CONSENT_IO_INSTANCE }}

      - name: Deploy Docs
        uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: pages deploy target/deploy --project-name=docs

      - name: Deploy Install
        uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: r2 object put -f script/install.sh zed-open-source-website-assets/install.sh

      - name: Deploy Docs Workers
        uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: deploy .cloudflare/docs-proxy/src/worker.js

      - name: Deploy Install Workers
        uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: deploy .cloudflare/docs-proxy/src/worker.js

      - name: Preserve Wrangler logs
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
        if: always()
        with:
          name: wrangler_logs
          path: /home/runner/.config/.wrangler/logs/
deploy_collab .github/workflows/deploy_collab.yml
Triggers
push
Runs on
namespace-profile-16x32-ubuntu-2204, namespace-profile-16x32-ubuntu-2204, namespace-profile-16x32-ubuntu-2204, namespace-profile-16x32-ubuntu-2204
Jobs
style, tests, publish, deploy
Actions
namespacelabs/nscloud-cache-action, namespacelabs/nscloud-cache-action, taiki-e/install-action, digitalocean/action-doctl, digitalocean/action-doctl
Commands
  • mkdir -p ./../.cargo cp ./.cargo/ci-config.toml ./../.cargo/config.toml
  • ./script/linux
  • ./script/download-wasi-sdk
  • cargo fmt --all -- --check
  • ./script/clippy
  • mkdir -p ./../.cargo cp ./.cargo/ci-config.toml ./../.cargo/config.toml
  • ./script/linux
  • ./script/download-wasi-sdk
View raw YAML
# Generated from xtask::workflows::deploy_collab
# Rebuild with `cargo xtask workflows`.
name: deploy_collab
env:
  DOCKER_BUILDKIT: '1'
on:
  push:
    tags:
    - collab-production
jobs:
  style:
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    name: Check formatting and Clippy lints
    runs-on: namespace-profile-16x32-ubuntu-2204
    env:
      CC: clang
      CXX: clang++
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
        fetch-depth: 0
    - name: steps::setup_cargo_config
      run: |
        mkdir -p ./../.cargo
        cp ./.cargo/ci-config.toml ./../.cargo/config.toml
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: steps::setup_linux
      run: ./script/linux
    - name: steps::download_wasi_sdk
      run: ./script/download-wasi-sdk
    - name: steps::cargo_fmt
      run: cargo fmt --all -- --check
    - name: steps::clippy
      run: ./script/clippy
  tests:
    needs:
    - style
    name: Run tests
    runs-on: namespace-profile-16x32-ubuntu-2204
    env:
      CC: clang
      CXX: clang++
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
        fetch-depth: 0
    - name: steps::setup_cargo_config
      run: |
        mkdir -p ./../.cargo
        cp ./.cargo/ci-config.toml ./../.cargo/config.toml
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: steps::setup_linux
      run: ./script/linux
    - name: steps::download_wasi_sdk
      run: ./script/download-wasi-sdk
    - name: steps::cargo_install_nextest
      uses: taiki-e/install-action@921e2c9f7148d7ba14cd819f417db338f63e733c
    - name: steps::clear_target_dir_if_large
      run: ./script/clear-target-dir-if-larger-than 250
    - name: deploy_collab::tests::run_collab_tests
      run: cargo nextest run --package collab --no-fail-fast
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_HOST_AUTH_METHOD: trust
        ports:
        - 5432:5432
        options: --health-cmd pg_isready --health-interval 500ms --health-timeout 5s --health-retries 10
  publish:
    needs:
    - style
    - tests
    name: Publish collab server image
    runs-on: namespace-profile-16x32-ubuntu-2204
    steps:
    - name: deploy_collab::publish::install_doctl
      uses: digitalocean/action-doctl@v2
      with:
        token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
    - name: deploy_collab::publish::sign_into_registry
      run: doctl registry login
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: deploy_collab::publish::build_docker_image
      run: |
        docker build -f Dockerfile-collab \
          --build-arg "GITHUB_SHA=$GITHUB_SHA" \
          --tag "registry.digitalocean.com/zed/collab:$GITHUB_SHA" \
          .
    - name: deploy_collab::publish::publish_docker_image
      run: docker push "registry.digitalocean.com/zed/collab:${GITHUB_SHA}"
    - name: deploy_collab::publish::prune_docker_system
      run: docker system prune --filter 'until=72h' -f
  deploy:
    needs:
    - publish
    name: Deploy new server image
    runs-on: namespace-profile-16x32-ubuntu-2204
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: deploy_collab::deploy::install_doctl
      uses: digitalocean/action-doctl@v2
      with:
        token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
    - name: deploy_collab::deploy::sign_into_kubernetes
      run: doctl kubernetes cluster kubeconfig save --expiry-seconds 600 "$CLUSTER_NAME"
      env:
        CLUSTER_NAME: ${{ secrets.CLUSTER_NAME }}
    - name: deploy_collab::deploy::start_rollout
      run: |
        set -eu
        if [[ $GITHUB_REF_NAME = "collab-production" ]]; then
          export ZED_KUBE_NAMESPACE=production
          export ZED_COLLAB_LOAD_BALANCER_SIZE_UNIT=10
          export ZED_API_LOAD_BALANCER_SIZE_UNIT=2
        elif [[ $GITHUB_REF_NAME = "collab-staging" ]]; then
          export ZED_KUBE_NAMESPACE=staging
          export ZED_COLLAB_LOAD_BALANCER_SIZE_UNIT=1
          export ZED_API_LOAD_BALANCER_SIZE_UNIT=1
        else
          echo "cowardly refusing to deploy from an unknown branch"
          exit 1
        fi

        echo "Deploying collab:$GITHUB_SHA to $ZED_KUBE_NAMESPACE"

        source script/lib/deploy-helpers.sh
        export_vars_for_environment "$ZED_KUBE_NAMESPACE"

        ZED_DO_CERTIFICATE_ID="$(doctl compute certificate list --format ID --no-header)"
        export ZED_DO_CERTIFICATE_ID
        export ZED_IMAGE_ID="registry.digitalocean.com/zed/collab:${GITHUB_SHA}"

        export ZED_SERVICE_NAME=collab
        export ZED_LOAD_BALANCER_SIZE_UNIT=$ZED_COLLAB_LOAD_BALANCER_SIZE_UNIT
        export DATABASE_MAX_CONNECTIONS=850
        envsubst < crates/collab/k8s/collab.template.yml | kubectl apply -f -
        kubectl -n "$ZED_KUBE_NAMESPACE" rollout status "deployment/$ZED_SERVICE_NAME" --watch
        echo "deployed ${ZED_SERVICE_NAME} to ${ZED_KUBE_NAMESPACE}"

        export ZED_SERVICE_NAME=api
        export ZED_LOAD_BALANCER_SIZE_UNIT=$ZED_API_LOAD_BALANCER_SIZE_UNIT
        export DATABASE_MAX_CONNECTIONS=60
        envsubst < crates/collab/k8s/collab.template.yml | kubectl apply -f -
        kubectl -n "$ZED_KUBE_NAMESPACE" rollout status "deployment/$ZED_SERVICE_NAME" --watch
        echo "deployed ${ZED_SERVICE_NAME} to ${ZED_KUBE_NAMESPACE}"
defaults:
  run:
    shell: bash -euxo pipefail {0}
docs_suggestions .github/workflows/docs_suggestions.yml
Triggers
pull_request, pull_request_target, workflow_dispatch
Runs on
ubuntu-latest, ubuntu-latest
Jobs
batch-suggestions, cherry-pick-suggestions
Commands
  • # Retry with exponential backoff for transient network/auth issues MAX_RETRIES=3 for i in $(seq 1 "$MAX_RETRIES"); do echo "Attempt $i of $MAX_RETRIES to install Droid CLI..." if curl -fsSL https://app.factory.ai/cli | sh; then echo "Droid CLI installed successfully" break fi if [ "$i" -eq "$MAX_RETRIES" ]; then echo "Failed to install Droid CLI after $MAX_RETRIES attempts" exit 1 fi sleep $((i * 5)) done echo "${HOME}/.local/bin" >> "$GITHUB_PATH"
  • if [ -n "$INPUT_PR_NUMBER" ]; then PR_NUM="$INPUT_PR_NUMBER" else PR_NUM="$EVENT_PR_NUMBER" fi if ! [[ "$PR_NUM" =~ ^[0-9]+$ ]]; then echo "::error::Invalid PR number: $PR_NUM" exit 1 fi echo "number=$PR_NUM" >> "$GITHUB_OUTPUT" PR_TITLE=$(gh pr view "$PR_NUM" --json title --jq '.title' | tr -d '\n\r' | head -c 200) EOF_MARKER="EOF_$(openssl rand -hex 8)" { echo "title<<$EOF_MARKER" echo "$PR_TITLE" echo "$EOF_MARKER" } >> "$GITHUB_OUTPUT"
  • # Ensure gh CLI is authenticated (GH_TOKEN may not be auto-detected) # Unset GH_TOKEN first to allow gh auth login to store credentials echo "$GH_TOKEN" | (unset GH_TOKEN && gh auth login --with-token) OUTPUT_FILE=$(mktemp) # Retry with exponential backoff for transient Factory API failures MAX_RETRIES=3 for i in $(seq 1 "$MAX_RETRIES"); do echo "Attempt $i of $MAX_RETRIES to analyze PR..." if ./script/docs-suggest \ --pr "$PR_NUMBER" \ --immediate \ --preview \ --output "$OUTPUT_FILE" \ --verbose; then echo "Analysis completed successfully" break fi if [ "$i" -eq "$MAX_RETRIES" ]; then echo "Analysis failed after $MAX_RETRIES attempts" exit 1 fi echo "Retrying in $((i * 5)) seconds..." sleep $((i * 5)) done # Check if we got actionable suggestions (not "no updates needed") if grep -q "Documentation Suggestions" "$OUTPUT_FILE" && \ ! grep -q "No Documentation Updates Needed" "$OUTPUT_FILE"; then echo "has_suggestions=true" >> "$GITHUB_OUTPUT" echo "output_file=$OUTPUT_FILE" >> "$GITHUB_OUTPUT" else echo "has_suggestions=false" >> "$GITHUB_OUTPUT" echo "No actionable documentation suggestions for this PR" cat "$OUTPUT_FILE" fi
  • set -euo pipefail # Configure git git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" # Retry loop for handling concurrent pushes MAX_RETRIES=3 for i in $(seq 1 "$MAX_RETRIES"); do echo "Attempt $i of $MAX_RETRIES" # Fetch and checkout suggestions branch (create if doesn't exist) if git ls-remote --exit-code --heads origin "$SUGGESTIONS_BRANCH" > /dev/null 2>&1; then git fetch origin "$SUGGESTIONS_BRANCH" git checkout -B "$SUGGESTIONS_BRANCH" "origin/$SUGGESTIONS_BRANCH" else # Create orphan branch for clean history git checkout --orphan "$SUGGESTIONS_BRANCH" git rm -rf . > /dev/null 2>&1 || true # Initialize with README cat > README.md << 'EOF' # Documentation Suggestions Queue This branch contains batched documentation suggestions for the next Preview release. Each file represents suggestions from a merged PR. At preview branch cut time, run `script/docs-suggest-publish` to create a documentation PR from these suggestions. ## Structure - `suggestions/PR-XXXXX.md` - Suggestions for PR #XXXXX - `manifest.json` - Index of all pending suggestions ## Workflow 1. PRs merged to main trigger documentation analysis 2. Suggestions are committed here as individual files 3. At preview release, suggestions are collected into a docs PR 4. After docs PR is created, this branch is reset EOF mkdir -p suggestions echo '{"suggestions":[]}' > manifest.json git add README.md suggestions manifest.json git commit -m "Initialize documentation suggestions queue" fi # Create suggestion file SUGGESTION_FILE="suggestions/PR-${PR_NUM}.md" { echo "# PR #${PR_NUM}: ${PR_TITLE}" echo "" echo "_Merged: $(date -u +%Y-%m-%dT%H:%M:%SZ)_" echo "_PR: https://github.com/${REPO}/pull/${PR_NUM}_" echo "" cat "$OUTPUT_FILE" } > "$SUGGESTION_FILE" # Update manifest MANIFEST=$(cat manifest.json) NEW_ENTRY="{\"pr\":${PR_NUM},\"title\":$(echo "$PR_TITLE" | jq -R .),\"file\":\"$SUGGESTION_FILE\",\"date\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" # Add to manifest if not already present if ! echo "$MANIFEST" | jq -e ".suggestions[] | select(.pr == $PR_NUM)" > /dev/null 2>&1; then echo "$MANIFEST" | jq ".suggestions += [$NEW_ENTRY]" > manifest.json fi # Commit git add "$SUGGESTION_FILE" manifest.json git commit -m "docs: Add suggestions for PR #${PR_NUM} ${PR_TITLE} Auto-generated documentation suggestions for review at next preview release." # Try to push if git push origin "$SUGGESTIONS_BRANCH"; then echo "Successfully pushed suggestions" break else echo "Push failed, retrying..." if [ "$i" -eq "$MAX_RETRIES" ]; then echo "Failed after $MAX_RETRIES attempts" exit 1 fi sleep $((i * 2)) fi done
  • { echo "## Documentation Suggestions" echo "" if [ "$HAS_SUGGESTIONS" == "true" ]; then echo "✅ Suggestions queued for PR #${PR_NUM}" echo "" echo "View pending suggestions: [docs/suggestions-pending branch](https://github.com/${REPO}/tree/${SUGGESTIONS_BRANCH})" else echo "No documentation updates needed for this PR." fi } >> "$GITHUB_STEP_SUMMARY"
  • # Retry with exponential backoff for transient network/auth issues MAX_RETRIES=3 for i in $(seq 1 "$MAX_RETRIES"); do echo "Attempt $i of $MAX_RETRIES to install Droid CLI..." if curl -fsSL https://app.factory.ai/cli | sh; then echo "Droid CLI installed successfully" break fi if [ "$i" -eq "$MAX_RETRIES" ]; then echo "Failed to install Droid CLI after $MAX_RETRIES attempts" exit 1 fi sleep $((i * 5)) done echo "${HOME}/.local/bin" >> "$GITHUB_PATH"
  • if [ -n "$INPUT_PR_NUMBER" ]; then PR_NUM="$INPUT_PR_NUMBER" else PR_NUM="$EVENT_PR_NUMBER" fi if ! [[ "$PR_NUM" =~ ^[0-9]+$ ]]; then echo "::error::Invalid PR number: $PR_NUM" exit 1 fi echo "number=$PR_NUM" >> "$GITHUB_OUTPUT"
  • # Ensure gh CLI is authenticated (GH_TOKEN may not be auto-detected) # Unset GH_TOKEN first to allow gh auth login to store credentials echo "$GH_TOKEN" | (unset GH_TOKEN && gh auth login --with-token) OUTPUT_FILE="${RUNNER_TEMP}/suggestions.md" # Cherry-picks don't get preview callout # Retry with exponential backoff for transient Factory API failures MAX_RETRIES=3 for i in $(seq 1 "$MAX_RETRIES"); do echo "Attempt $i of $MAX_RETRIES to analyze PR..." if ./script/docs-suggest \ --pr "$PR_NUMBER" \ --immediate \ --no-preview \ --output "$OUTPUT_FILE" \ --verbose; then echo "Analysis completed successfully" break fi if [ "$i" -eq "$MAX_RETRIES" ]; then echo "Analysis failed after $MAX_RETRIES attempts" exit 1 fi echo "Retrying in $((i * 5)) seconds..." sleep $((i * 5)) done # Check if we got actionable suggestions if [ -s "$OUTPUT_FILE" ] && \ grep -q "Documentation Suggestions" "$OUTPUT_FILE" && \ ! grep -q "No Documentation Updates Needed" "$OUTPUT_FILE"; then echo "has_suggestions=true" >> "$GITHUB_OUTPUT" echo "suggestions_file=$OUTPUT_FILE" >> "$GITHUB_OUTPUT" else echo "has_suggestions=false" >> "$GITHUB_OUTPUT" fi
View raw YAML
name: Documentation Suggestions

# Stable release callout stripping plan (not wired yet):
# 1. Add a separate stable-only workflow trigger on `release.published`
#    with `github.event.release.prerelease == false`.
# 2. In that workflow, run `script/docs-strip-preview-callouts` on `main`.
# 3. Open a PR with stripped preview callouts for human review.
# 4. Fail loudly on script errors or when no callout changes are produced.
# 5. Keep this workflow focused on suggestions only until that stable workflow is added.

on:
  # Run when PRs are merged to main
  pull_request:
    types: [closed]
    branches: [main]
    paths:
      - 'crates/**/*.rs'
      - '!crates/**/*_test.rs'
      - '!crates/**/tests/**'

  # Run on cherry-picks to release branches
  pull_request_target:
    types: [opened, synchronize]
    branches:
      - 'v0.*'
    paths:
      - 'crates/**/*.rs'

  # Manual trigger for testing
  workflow_dispatch:
    inputs:
      pr_number:
        description: 'PR number to analyze'
        required: true
        type: string
      mode:
        description: 'Output mode'
        required: true
        type: choice
        options:
          - batch
          - immediate
        default: batch

env:
  DROID_MODEL: claude-sonnet-4-5-20250929
  SUGGESTIONS_BRANCH: docs/suggestions-pending

jobs:
  # Job for PRs merged to main - batch suggestions to branch
  # Only runs for PRs from the same repo (not forks) since secrets aren't available for fork PRs
  batch-suggestions:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    permissions:
      contents: write
      pull-requests: read
    if: |
      (github.event_name == 'pull_request' &&
       github.event.pull_request.merged == true &&
       github.event.pull_request.base.ref == 'main' &&
       github.event.pull_request.head.repo.full_name == github.repository) ||
      (github.event_name == 'workflow_dispatch' && inputs.mode == 'batch')

    steps:
      - name: Checkout repository
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          fetch-depth: 0
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Install Droid CLI
        run: |
          # Retry with exponential backoff for transient network/auth issues
          MAX_RETRIES=3
          for i in $(seq 1 "$MAX_RETRIES"); do
            echo "Attempt $i of $MAX_RETRIES to install Droid CLI..."
            if curl -fsSL https://app.factory.ai/cli | sh; then
              echo "Droid CLI installed successfully"
              break
            fi
            if [ "$i" -eq "$MAX_RETRIES" ]; then
              echo "Failed to install Droid CLI after $MAX_RETRIES attempts"
              exit 1
            fi
            sleep $((i * 5))
          done
          echo "${HOME}/.local/bin" >> "$GITHUB_PATH"
        env:
          FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}

      - name: Get PR info
        id: pr
        env:
          INPUT_PR_NUMBER: ${{ inputs.pr_number }}
          EVENT_PR_NUMBER: ${{ github.event.pull_request.number }}
          GH_TOKEN: ${{ github.token }}
        run: |
          if [ -n "$INPUT_PR_NUMBER" ]; then
            PR_NUM="$INPUT_PR_NUMBER"
          else
            PR_NUM="$EVENT_PR_NUMBER"
          fi
          if ! [[ "$PR_NUM" =~ ^[0-9]+$ ]]; then
            echo "::error::Invalid PR number: $PR_NUM"
            exit 1
          fi
          echo "number=$PR_NUM" >> "$GITHUB_OUTPUT"
          PR_TITLE=$(gh pr view "$PR_NUM" --json title --jq '.title' | tr -d '\n\r' | head -c 200)
          EOF_MARKER="EOF_$(openssl rand -hex 8)"
          {
            echo "title<<$EOF_MARKER"
            echo "$PR_TITLE"
            echo "$EOF_MARKER"
          } >> "$GITHUB_OUTPUT"

      - name: Analyze PR for documentation needs
        id: analyze
        env:
          GH_TOKEN: ${{ github.token }}
          FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
          PR_NUMBER: ${{ steps.pr.outputs.number }}
        run: |
          # Ensure gh CLI is authenticated (GH_TOKEN may not be auto-detected)
          # Unset GH_TOKEN first to allow gh auth login to store credentials
          echo "$GH_TOKEN" | (unset GH_TOKEN && gh auth login --with-token)

          OUTPUT_FILE=$(mktemp)

          # Retry with exponential backoff for transient Factory API failures
          MAX_RETRIES=3
          for i in $(seq 1 "$MAX_RETRIES"); do
            echo "Attempt $i of $MAX_RETRIES to analyze PR..."
            if ./script/docs-suggest \
              --pr "$PR_NUMBER" \
              --immediate \
              --preview \
              --output "$OUTPUT_FILE" \
              --verbose; then
              echo "Analysis completed successfully"
              break
            fi
            if [ "$i" -eq "$MAX_RETRIES" ]; then
              echo "Analysis failed after $MAX_RETRIES attempts"
              exit 1
            fi
            echo "Retrying in $((i * 5)) seconds..."
            sleep $((i * 5))
          done

          # Check if we got actionable suggestions (not "no updates needed")
          if grep -q "Documentation Suggestions" "$OUTPUT_FILE" && \
             ! grep -q "No Documentation Updates Needed" "$OUTPUT_FILE"; then
            echo "has_suggestions=true" >> "$GITHUB_OUTPUT"
            echo "output_file=$OUTPUT_FILE" >> "$GITHUB_OUTPUT"
          else
            echo "has_suggestions=false" >> "$GITHUB_OUTPUT"
            echo "No actionable documentation suggestions for this PR"
            cat "$OUTPUT_FILE"
          fi

      - name: Commit suggestions to queue branch
        if: steps.analyze.outputs.has_suggestions == 'true'
        env:
          PR_NUM: ${{ steps.pr.outputs.number }}
          PR_TITLE: ${{ steps.pr.outputs.title }}
          OUTPUT_FILE: ${{ steps.analyze.outputs.output_file }}
          REPO: ${{ github.repository }}
        run: |
          set -euo pipefail

          # Configure git
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"

          # Retry loop for handling concurrent pushes
          MAX_RETRIES=3
          for i in $(seq 1 "$MAX_RETRIES"); do
            echo "Attempt $i of $MAX_RETRIES"

            # Fetch and checkout suggestions branch (create if doesn't exist)
            if git ls-remote --exit-code --heads origin "$SUGGESTIONS_BRANCH" > /dev/null 2>&1; then
              git fetch origin "$SUGGESTIONS_BRANCH"
              git checkout -B "$SUGGESTIONS_BRANCH" "origin/$SUGGESTIONS_BRANCH"
            else
              # Create orphan branch for clean history
              git checkout --orphan "$SUGGESTIONS_BRANCH"
              git rm -rf . > /dev/null 2>&1 || true

              # Initialize with README
              cat > README.md << 'EOF'
          # Documentation Suggestions Queue

          This branch contains batched documentation suggestions for the next Preview release.

          Each file represents suggestions from a merged PR. At preview branch cut time,
          run `script/docs-suggest-publish` to create a documentation PR from these suggestions.

          ## Structure

          - `suggestions/PR-XXXXX.md` - Suggestions for PR #XXXXX
          - `manifest.json` - Index of all pending suggestions

          ## Workflow

          1. PRs merged to main trigger documentation analysis
          2. Suggestions are committed here as individual files
          3. At preview release, suggestions are collected into a docs PR
          4. After docs PR is created, this branch is reset
          EOF

              mkdir -p suggestions
              echo '{"suggestions":[]}' > manifest.json
              git add README.md suggestions manifest.json
              git commit -m "Initialize documentation suggestions queue"
            fi

            # Create suggestion file
            SUGGESTION_FILE="suggestions/PR-${PR_NUM}.md"

            {
              echo "# PR #${PR_NUM}: ${PR_TITLE}"
              echo ""
              echo "_Merged: $(date -u +%Y-%m-%dT%H:%M:%SZ)_"
              echo "_PR: https://github.com/${REPO}/pull/${PR_NUM}_"
              echo ""
              cat "$OUTPUT_FILE"
            } > "$SUGGESTION_FILE"

            # Update manifest
            MANIFEST=$(cat manifest.json)
            NEW_ENTRY="{\"pr\":${PR_NUM},\"title\":$(echo "$PR_TITLE" | jq -R .),\"file\":\"$SUGGESTION_FILE\",\"date\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}"

            # Add to manifest if not already present
            if ! echo "$MANIFEST" | jq -e ".suggestions[] | select(.pr == $PR_NUM)" > /dev/null 2>&1; then
              echo "$MANIFEST" | jq ".suggestions += [$NEW_ENTRY]" > manifest.json
            fi

            # Commit
            git add "$SUGGESTION_FILE" manifest.json
            git commit -m "docs: Add suggestions for PR #${PR_NUM}

          ${PR_TITLE}

          Auto-generated documentation suggestions for review at next preview release."

            # Try to push
            if git push origin "$SUGGESTIONS_BRANCH"; then
              echo "Successfully pushed suggestions"
              break
            else
              echo "Push failed, retrying..."
              if [ "$i" -eq "$MAX_RETRIES" ]; then
                echo "Failed after $MAX_RETRIES attempts"
                exit 1
              fi
              sleep $((i * 2))
            fi
          done

      - name: Summary
        if: always()
        env:
          HAS_SUGGESTIONS: ${{ steps.analyze.outputs.has_suggestions }}
          PR_NUM: ${{ steps.pr.outputs.number }}
          REPO: ${{ github.repository }}
        run: |
          {
            echo "## Documentation Suggestions"
            echo ""
            if [ "$HAS_SUGGESTIONS" == "true" ]; then
              echo "✅ Suggestions queued for PR #${PR_NUM}"
              echo ""
              echo "View pending suggestions: [docs/suggestions-pending branch](https://github.com/${REPO}/tree/${SUGGESTIONS_BRANCH})"
            else
              echo "No documentation updates needed for this PR."
            fi
          } >> "$GITHUB_STEP_SUMMARY"

  # Job for cherry-picks to release branches - immediate output as PR comment
  cherry-pick-suggestions:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    permissions:
      contents: read
      pull-requests: write
    concurrency:
      group: docs-suggestions-${{ github.event.pull_request.number || inputs.pr_number || 'manual' }}
      cancel-in-progress: true
    if: |
      (github.event_name == 'pull_request_target' &&
       startsWith(github.event.pull_request.base.ref, 'v0.') &&
       contains(fromJSON('["MEMBER","OWNER"]'),
                github.event.pull_request.author_association)) ||
      (github.event_name == 'workflow_dispatch' && inputs.mode == 'immediate')

    steps:
      - name: Checkout repository
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          fetch-depth: 0
          ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.base.ref || '' }}
          persist-credentials: false

      - name: Install Droid CLI
        run: |
          # Retry with exponential backoff for transient network/auth issues
          MAX_RETRIES=3
          for i in $(seq 1 "$MAX_RETRIES"); do
            echo "Attempt $i of $MAX_RETRIES to install Droid CLI..."
            if curl -fsSL https://app.factory.ai/cli | sh; then
              echo "Droid CLI installed successfully"
              break
            fi
            if [ "$i" -eq "$MAX_RETRIES" ]; then
              echo "Failed to install Droid CLI after $MAX_RETRIES attempts"
              exit 1
            fi
            sleep $((i * 5))
          done
          echo "${HOME}/.local/bin" >> "$GITHUB_PATH"
        env:
          FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}

      - name: Get PR number
        id: pr
        env:
          INPUT_PR_NUMBER: ${{ inputs.pr_number }}
          EVENT_PR_NUMBER: ${{ github.event.pull_request.number }}
        run: |
          if [ -n "$INPUT_PR_NUMBER" ]; then
            PR_NUM="$INPUT_PR_NUMBER"
          else
            PR_NUM="$EVENT_PR_NUMBER"
          fi
          if ! [[ "$PR_NUM" =~ ^[0-9]+$ ]]; then
            echo "::error::Invalid PR number: $PR_NUM"
            exit 1
          fi
          echo "number=$PR_NUM" >> "$GITHUB_OUTPUT"

      - name: Analyze PR for documentation needs
        id: analyze
        env:
          GH_TOKEN: ${{ github.token }}
          FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
          PR_NUMBER: ${{ steps.pr.outputs.number }}
        run: |
          # Ensure gh CLI is authenticated (GH_TOKEN may not be auto-detected)
          # Unset GH_TOKEN first to allow gh auth login to store credentials
          echo "$GH_TOKEN" | (unset GH_TOKEN && gh auth login --with-token)

          OUTPUT_FILE="${RUNNER_TEMP}/suggestions.md"

          # Cherry-picks don't get preview callout
          # Retry with exponential backoff for transient Factory API failures
          MAX_RETRIES=3
          for i in $(seq 1 "$MAX_RETRIES"); do
            echo "Attempt $i of $MAX_RETRIES to analyze PR..."
            if ./script/docs-suggest \
              --pr "$PR_NUMBER" \
              --immediate \
              --no-preview \
              --output "$OUTPUT_FILE" \
              --verbose; then
              echo "Analysis completed successfully"
              break
            fi
            if [ "$i" -eq "$MAX_RETRIES" ]; then
              echo "Analysis failed after $MAX_RETRIES attempts"
              exit 1
            fi
            echo "Retrying in $((i * 5)) seconds..."
            sleep $((i * 5))
          done

          # Check if we got actionable suggestions
          if [ -s "$OUTPUT_FILE" ] && \
             grep -q "Documentation Suggestions" "$OUTPUT_FILE" && \
             ! grep -q "No Documentation Updates Needed" "$OUTPUT_FILE"; then
            echo "has_suggestions=true" >> "$GITHUB_OUTPUT"
            echo "suggestions_file=$OUTPUT_FILE" >> "$GITHUB_OUTPUT"
          else
            echo "has_suggestions=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Post suggestions as PR comment
        if: steps.analyze.outputs.has_suggestions == 'true'
        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
        env:
          SUGGESTIONS_FILE: ${{ steps.analyze.outputs.suggestions_file }}
          PR_NUMBER: ${{ steps.pr.outputs.number }}
        with:
          script: |
            const fs = require('fs');

            // Read suggestions from file
            const suggestionsRaw = fs.readFileSync(process.env.SUGGESTIONS_FILE, 'utf8');

            // Sanitize AI-generated content
            let sanitized = suggestionsRaw
              // Strip HTML tags
              .replace(/<[^>]*>/g, '')
              // Strip markdown links but keep display text
              .replace(/\[([^\]]*)\]\([^)]*\)/g, '$1')
              // Strip raw URLs
              .replace(/https?:\/\/[^\s)>\]]+/g, '[link removed]')
              // Strip protocol-relative URLs
              .replace(/\/\/[^\s)>\]]+\.[^\s)>\]]+/g, '[link removed]')
              // Neutralize @-mentions (preserve JSDoc-style annotations)
              .replace(/@(?!param\b|returns?\b|throws?\b|typedef\b|type\b|see\b|example\b|since\b|deprecated\b|default\b)(\w+)/g, '`@$1`')
              // Strip cross-repo references that could be confused with real links
              .replace(/[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+#\d+/g, '[ref removed]');

            // Truncate to 20,000 characters
            if (sanitized.length > 20000) {
              sanitized = sanitized.substring(0, 20000) + '\n\n…(truncated)';
            }

            // Parse and validate PR number
            const prNumber = parseInt(process.env.PR_NUMBER, 10);
            if (isNaN(prNumber) || prNumber <= 0) {
              core.setFailed(`Invalid PR number: ${process.env.PR_NUMBER}`);
              return;
            }

            const body = `## 📚 Documentation Suggestions

            This cherry-pick contains changes that may need documentation updates.

            ${sanitized}

            ---
            > **Note:** This comment was generated automatically by an AI model analyzing
            > code changes. Suggestions may contain inaccuracies — please verify before acting.

            <details>
            <summary>About this comment</summary>

            This comment was generated automatically by analyzing code changes in this cherry-pick.
            Cherry-picks typically don't need new documentation since the feature was already
            documented when merged to main, but please verify.

            </details>`;

            // Find existing comment to update (avoid spam)
            const { data: comments } = await github.rest.issues.listComments({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: prNumber
            });

            const botComment = comments.find(c =>
              c.user.type === 'Bot' &&
              c.body.includes('Documentation Suggestions')
            );

            if (botComment) {
              await github.rest.issues.updateComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                comment_id: botComment.id,
                body: body
              });
            } else {
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: prNumber,
                body: body
              });
            }

      - name: Summary
        if: always()
        env:
          HAS_SUGGESTIONS: ${{ steps.analyze.outputs.has_suggestions }}
          PR_NUM: ${{ steps.pr.outputs.number }}
        run: |
          {
            echo "## 📚 Documentation Suggestions (Cherry-pick)"
            echo ""
            if [ "$HAS_SUGGESTIONS" == "true" ]; then
              echo "Suggestions posted as PR comment on #${PR_NUM}."
            else
              echo "No documentation suggestions for this cherry-pick."
            fi
          } >> "$GITHUB_STEP_SUMMARY"
extension_auto_bump matrix .github/workflows/extension_auto_bump.yml
Triggers
push
Runs on
namespace-profile-2x4-ubuntu-2404
Jobs
detect_changed_extensions, bump_extension_versions
Matrix
extension→ ${{ fromJson(needs.detect_changed_extensions.outputs.changed_extensions) }}
Commands
  • COMPARE_REV="$(git rev-parse HEAD~1)" CHANGED_FILES="$(git diff --name-only "$COMPARE_REV" "$GITHUB_SHA")" # Detect changed extension directories (excluding extensions/workflows) CHANGED_EXTENSIONS=$(echo "$CHANGED_FILES" | grep -oP '^extensions/[^/]+(?=/)' | sort -u | grep -v '^extensions/workflows$' || true) if [ -n "$CHANGED_EXTENSIONS" ]; then EXTENSIONS_JSON=$(echo "$CHANGED_EXTENSIONS" | jq -R -s -c 'split("\n") | map(select(length > 0))') else EXTENSIONS_JSON="[]" fi # Filter out newly added or entirely removed extensions FILTERED="[]" for ext in $(echo "$EXTENSIONS_JSON" | jq -r '.[]'); do if git show HEAD~1:"$ext/extension.toml" >/dev/null 2>&1 && \ [ -f "$ext/extension.toml" ]; then FILTERED=$(echo "$FILTERED" | jq -c --arg e "$ext" '. + [$e]') fi done echo "changed_extensions=$FILTERED" >> "$GITHUB_OUTPUT"
View raw YAML
# Generated from xtask::workflows::extension_auto_bump
# Rebuild with `cargo xtask workflows`.
name: extension_auto_bump
on:
  push:
    branches:
    - main
    paths:
    - extensions/**
    - '!extensions/slash-commands-example/**'
    - '!extensions/test-extension/**'
    - '!extensions/workflows/**'
    - '!extensions/*.md'
jobs:
  detect_changed_extensions:
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
        fetch-depth: 2
    - id: detect
      name: extension_auto_bump::detect_changed_extensions
      run: |
        COMPARE_REV="$(git rev-parse HEAD~1)"
        CHANGED_FILES="$(git diff --name-only "$COMPARE_REV" "$GITHUB_SHA")"
        # Detect changed extension directories (excluding extensions/workflows)
        CHANGED_EXTENSIONS=$(echo "$CHANGED_FILES" | grep -oP '^extensions/[^/]+(?=/)' | sort -u | grep -v '^extensions/workflows$' || true)
        if [ -n "$CHANGED_EXTENSIONS" ]; then
            EXTENSIONS_JSON=$(echo "$CHANGED_EXTENSIONS" | jq -R -s -c 'split("\n") | map(select(length > 0))')
        else
            EXTENSIONS_JSON="[]"
        fi
        # Filter out newly added or entirely removed extensions
        FILTERED="[]"
        for ext in $(echo "$EXTENSIONS_JSON" | jq -r '.[]'); do
            if git show HEAD~1:"$ext/extension.toml" >/dev/null 2>&1 && \
               [ -f "$ext/extension.toml" ]; then
                FILTERED=$(echo "$FILTERED" | jq -c --arg e "$ext" '. + [$e]')
            fi
        done
        echo "changed_extensions=$FILTERED" >> "$GITHUB_OUTPUT"
    outputs:
      changed_extensions: ${{ steps.detect.outputs.changed_extensions }}
    timeout-minutes: 5
  bump_extension_versions:
    needs:
    - detect_changed_extensions
    if: needs.detect_changed_extensions.outputs.changed_extensions != '[]'
    permissions:
      actions: write
      contents: write
      issues: write
      pull-requests: write
    strategy:
      matrix:
        extension: ${{ fromJson(needs.detect_changed_extensions.outputs.changed_extensions) }}
      fail-fast: false
      max-parallel: 1
    uses: ./.github/workflows/extension_bump.yml
    secrets:
      app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
      app-secret: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
    with:
      working-directory: ${{ matrix.extension }}
      force-bump: false
concurrency:
  group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
  cancel-in-progress: true
defaults:
  run:
    shell: bash -euxo pipefail {0}
extension_bump .github/workflows/extension_bump.yml
Triggers
workflow_call
Runs on
namespace-profile-2x4-ubuntu-2404, namespace-profile-2x4-ubuntu-2404, namespace-profile-2x4-ubuntu-2404, namespace-profile-2x4-ubuntu-2404
Jobs
check_version_changed, bump_extension_version, create_version_label, trigger_release
Actions
actions/create-github-app-token, namespacelabs/nscloud-cache-action, peter-evans/create-pull-request, actions/create-github-app-token, actions/create-github-app-token, huacnlee/zed-extension-action
Commands
  • CURRENT_VERSION="$(sed -n 's/^version = \"\(.*\)\"/\1/p' < extension.toml | tr -d '[:space:]')" if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then PR_FORK_POINT="$(git merge-base origin/main HEAD)" git checkout "$PR_FORK_POINT" else git checkout "$(git log -1 --format=%H)"~1 fi PARENT_COMMIT_VERSION="$(sed -n 's/^version = \"\(.*\)\"/\1/p' < extension.toml | tr -d '[:space:]')" [[ "$CURRENT_VERSION" == "$PARENT_COMMIT_VERSION" ]] && \ echo "version_changed=false" >> "$GITHUB_OUTPUT" || \ echo "version_changed=true" >> "$GITHUB_OUTPUT" echo "current_version=${CURRENT_VERSION}" >> "$GITHUB_OUTPUT"
  • pip install bump2version --break-system-packages
  • BUMP_FILES=("extension.toml") if [[ -f "Cargo.toml" ]]; then BUMP_FILES+=("Cargo.toml") fi bump2version \ --search "version = \"{current_version}"\" \ --replace "version = \"{new_version}"\" \ --current-version "$OLD_VERSION" \ --no-configured-files "$BUMP_TYPE" "${BUMP_FILES[@]}" if [[ -f "Cargo.toml" ]]; then cargo +stable update --workspace fi NEW_VERSION="$(sed -n 's/^version = \"\(.*\)\"/\1/p' < extension.toml | tr -d '[:space:]')" EXTENSION_ID="$(sed -n 's/^id = "\(.*\)"/\1/p' < extension.toml | head -1 | tr -d '[:space:]')" EXTENSION_NAME="$(sed -n 's/^name = "\(.*\)"/\1/p' < extension.toml | head -1 | tr -d '[:space:]')" if [[ "$WORKING_DIR" == "." || -z "$WORKING_DIR" ]]; then { echo "title=Bump version to ${NEW_VERSION}"; echo "body=This PR bumps the version of this extension to v${NEW_VERSION}"; echo "branch_name=zed-zippy-autobump"; } >> "$GITHUB_OUTPUT" else { echo "title=${EXTENSION_ID}: Bump to v${NEW_VERSION}"; echo "body<<EOF"; echo "This PR bumps the version of the ${EXTENSION_NAME} extension to v${NEW_VERSION}."; echo ""; echo "Release Notes:"; echo ""; echo "- N/A"; echo "EOF"; echo "branch_name=zed-zippy-${EXTENSION_ID}-autobump"; } >> "$GITHUB_OUTPUT" fi echo "new_version=${NEW_VERSION}" >> "$GITHUB_OUTPUT"
  • EXTENSION_ID="$(sed -n 's/^id = "\(.*\)"/\1/p' < extension.toml | head -1 | tr -d '[:space:]')" if [[ "$WORKING_DIR" == "." || -z "$WORKING_DIR" ]]; then TAG="v${CURRENT_VERSION}" else TAG="${EXTENSION_ID}-v${CURRENT_VERSION}" fi echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
  • EXTENSION_ID="$(sed -n 's/id = \"\(.*\)\"/\1/p' < extension.toml)" echo "extension_id=${EXTENSION_ID}" >> "$GITHUB_OUTPUT"
View raw YAML
# Generated from xtask::workflows::extension_bump
# Rebuild with `cargo xtask workflows`.
name: extension_bump
env:
  CARGO_TERM_COLOR: always
  RUST_BACKTRACE: '1'
  CARGO_INCREMENTAL: '0'
  ZED_EXTENSION_CLI_SHA: 1fa7f1a3ec28ea1eae6db2e937d7a538fb10c0c7
on:
  workflow_call:
    inputs:
      bump-type:
        description: bump-type
        type: string
        default: patch
      force-bump:
        description: force-bump
        required: true
        type: boolean
      working-directory:
        description: working-directory
        type: string
        default: .
    secrets:
      app-id:
        description: The app ID used to create the PR
        required: true
      app-secret:
        description: The app secret for the corresponding app ID
        required: true
jobs:
  check_version_changed:
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
        fetch-depth: 0
    - id: compare-versions-check
      name: extension_bump::compare_versions
      run: |
        CURRENT_VERSION="$(sed -n 's/^version = \"\(.*\)\"/\1/p' < extension.toml | tr -d '[:space:]')"

        if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then
            PR_FORK_POINT="$(git merge-base origin/main HEAD)"
            git checkout "$PR_FORK_POINT"
        else
            git checkout "$(git log -1 --format=%H)"~1
        fi

        PARENT_COMMIT_VERSION="$(sed -n 's/^version = \"\(.*\)\"/\1/p' < extension.toml | tr -d '[:space:]')"

        [[ "$CURRENT_VERSION" == "$PARENT_COMMIT_VERSION" ]] && \
            echo "version_changed=false" >> "$GITHUB_OUTPUT" || \
            echo "version_changed=true" >> "$GITHUB_OUTPUT"

        echo "current_version=${CURRENT_VERSION}" >> "$GITHUB_OUTPUT"
    outputs:
      version_changed: ${{ steps.compare-versions-check.outputs.version_changed }}
      current_version: ${{ steps.compare-versions-check.outputs.current_version }}
    timeout-minutes: 1
    defaults:
      run:
        shell: bash -euxo pipefail {0}
        working-directory: ${{ inputs.working-directory }}
  bump_extension_version:
    needs:
    - check_version_changed
    if: |-
      (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions') &&
      (inputs.force-bump == true || needs.check_version_changed.outputs.version_changed == 'false')
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - id: generate-token
      name: steps::generate_token
      uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859
      with:
        app-id: ${{ secrets.app-id }}
        private-key: ${{ secrets.app-secret }}
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: extension_bump::install_bump_2_version
      run: pip install bump2version --break-system-packages
    - id: bump-version
      name: extension_bump::bump_version
      run: |
        BUMP_FILES=("extension.toml")
        if [[ -f "Cargo.toml" ]]; then
            BUMP_FILES+=("Cargo.toml")
        fi

        bump2version \
            --search "version = \"{current_version}"\" \
            --replace "version = \"{new_version}"\" \
            --current-version "$OLD_VERSION" \
            --no-configured-files "$BUMP_TYPE" "${BUMP_FILES[@]}"

        if [[ -f "Cargo.toml" ]]; then
            cargo +stable update --workspace
        fi

        NEW_VERSION="$(sed -n 's/^version = \"\(.*\)\"/\1/p' < extension.toml | tr -d '[:space:]')"
        EXTENSION_ID="$(sed -n 's/^id = "\(.*\)"/\1/p' < extension.toml | head -1 | tr -d '[:space:]')"
        EXTENSION_NAME="$(sed -n 's/^name = "\(.*\)"/\1/p' < extension.toml | head -1 | tr -d '[:space:]')"

        if [[ "$WORKING_DIR" == "." || -z "$WORKING_DIR" ]]; then
            {
                echo "title=Bump version to ${NEW_VERSION}";
                echo "body=This PR bumps the version of this extension to v${NEW_VERSION}";
                echo "branch_name=zed-zippy-autobump";
            } >> "$GITHUB_OUTPUT"
        else
            {
                echo "title=${EXTENSION_ID}: Bump to v${NEW_VERSION}";
                echo "body<<EOF";
                echo "This PR bumps the version of the ${EXTENSION_NAME} extension to v${NEW_VERSION}.";
                echo "";
                echo "Release Notes:";
                echo "";
                echo "- N/A";
                echo "EOF";
                echo "branch_name=zed-zippy-${EXTENSION_ID}-autobump";
            } >> "$GITHUB_OUTPUT"
        fi

        echo "new_version=${NEW_VERSION}" >> "$GITHUB_OUTPUT"
      env:
        OLD_VERSION: ${{ needs.check_version_changed.outputs.current_version }}
        BUMP_TYPE: ${{ inputs.bump-type }}
        WORKING_DIR: ${{ inputs.working-directory }}
    - name: extension_bump::create_pull_request
      uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725
      with:
        title: ${{ steps.bump-version.outputs.title }}
        body: ${{ steps.bump-version.outputs.body }}
        commit-message: ${{ steps.bump-version.outputs.title }}
        branch: ${{ steps.bump-version.outputs.branch_name }}
        committer: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>
        base: main
        delete-branch: true
        token: ${{ steps.generate-token.outputs.token }}
        sign-commits: true
        assignees: ${{ github.actor }}
    timeout-minutes: 5
    defaults:
      run:
        shell: bash -euxo pipefail {0}
        working-directory: ${{ inputs.working-directory }}
  create_version_label:
    needs:
    - check_version_changed
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions') && github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.check_version_changed.outputs.version_changed == 'true'
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - id: generate-token
      name: steps::generate_token
      uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859
      with:
        app-id: ${{ secrets.app-id }}
        private-key: ${{ secrets.app-secret }}
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - id: determine-tag
      name: extension_bump::determine_tag
      run: |
        EXTENSION_ID="$(sed -n 's/^id = "\(.*\)"/\1/p' < extension.toml | head -1 | tr -d '[:space:]')"

        if [[ "$WORKING_DIR" == "." || -z "$WORKING_DIR" ]]; then
            TAG="v${CURRENT_VERSION}"
        else
            TAG="${EXTENSION_ID}-v${CURRENT_VERSION}"
        fi

        echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
      env:
        CURRENT_VERSION: ${{ needs.check_version_changed.outputs.current_version }}
        WORKING_DIR: ${{ inputs.working-directory }}
    - name: extension_bump::create_version_tag
      uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b
      with:
        script: |-
          github.rest.git.createRef({
              owner: context.repo.owner,
              repo: context.repo.repo,
              ref: 'refs/tags/${{ steps.determine-tag.outputs.tag }}',
              sha: context.sha
          })
        github-token: ${{ steps.generate-token.outputs.token }}
    outputs:
      tag: ${{ steps.determine-tag.outputs.tag }}
    timeout-minutes: 1
    defaults:
      run:
        shell: bash -euxo pipefail {0}
        working-directory: ${{ inputs.working-directory }}
  trigger_release:
    needs:
    - check_version_changed
    - create_version_label
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - id: generate-token
      name: steps::generate_token
      uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859
      with:
        app-id: ${{ secrets.app-id }}
        private-key: ${{ secrets.app-secret }}
        owner: zed-industries
        repositories: extensions
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - id: get-extension-id
      name: extension_bump::get_extension_id
      run: |
        EXTENSION_ID="$(sed -n 's/id = \"\(.*\)\"/\1/p' < extension.toml)"

        echo "extension_id=${EXTENSION_ID}" >> "$GITHUB_OUTPUT"
    - id: extension-update
      name: extension_bump::release_action
      uses: huacnlee/zed-extension-action@82920ff0876879f65ffbcfa3403589114a8919c6
      with:
        extension-name: ${{ steps.get-extension-id.outputs.extension_id }}
        push-to: zed-industries/extensions
        tag: ${{ needs.create_version_label.outputs.tag }}
      env:
        COMMITTER_TOKEN: ${{ steps.generate-token.outputs.token }}
    - name: extension_bump::enable_automerge_if_staff
      uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b
      with:
        github-token: ${{ steps.generate-token.outputs.token }}
        script: |
          const prNumber = process.env.PR_NUMBER;
          if (!prNumber) {
              console.log('No pull request number set, skipping automerge.');
              return;
          }

          const author = process.env.GITHUB_ACTOR;
          let isStaff = false;
          try {
              const response = await github.rest.teams.getMembershipForUserInOrg({
                  org: 'zed-industries',
                  team_slug: 'staff',
                  username: author
              });
              isStaff = response.data.state === 'active';
          } catch (error) {
              if (error.status !== 404) {
                  throw error;
              }
          }

          if (!isStaff) {
              console.log(`Actor ${author} is not a staff member, skipping automerge.`);
              return;
          }

          // Assign staff member responsible for the bump
          const pullNumber = parseInt(prNumber);

          await github.rest.issues.addAssignees({
              owner: 'zed-industries',
              repo: 'extensions',
              issue_number: pullNumber,
              assignees: [author]
          });
          console.log(`Assigned ${author} to PR #${prNumber} in zed-industries/extensions`);

          // Get the GraphQL node ID
          const { data: pr } = await github.rest.pulls.get({
              owner: 'zed-industries',
              repo: 'extensions',
              pull_number: pullNumber
          });

          await github.graphql(`
              mutation($pullRequestId: ID!) {
                  enablePullRequestAutoMerge(input: { pullRequestId: $pullRequestId, mergeMethod: SQUASH }) {
                      pullRequest {
                          autoMergeRequest {
                              enabledAt
                          }
                      }
                  }
              }
          `, { pullRequestId: pr.node_id });

          console.log(`Automerge enabled for PR #${prNumber} in zed-industries/extensions`);
      env:
        PR_NUMBER: ${{ steps.extension-update.outputs.pull-request-number }}
    defaults:
      run:
        shell: bash -euxo pipefail {0}
        working-directory: ${{ inputs.working-directory }}
concurrency:
  group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}extension-bump
  cancel-in-progress: true
defaults:
  run:
    shell: bash -euxo pipefail {0}
extension_tests .github/workflows/extension_tests.yml
Triggers
workflow_call
Runs on
namespace-profile-2x4-ubuntu-2404, namespace-profile-8x32-ubuntu-2404, namespace-profile-8x32-ubuntu-2404, namespace-profile-2x4-ubuntu-2404
Jobs
orchestrate, check_rust, check_extension, tests_pass
Actions
namespacelabs/nscloud-cache-action, taiki-e/install-action, namespacelabs/nscloud-cache-action, dsaltares/fetch-gh-release-asset
Commands
  • set -euo pipefail if [ -z "$GITHUB_BASE_REF" ]; then echo "Not in a PR context (i.e., push to main/stable/preview)" COMPARE_REV="$(git rev-parse HEAD~1)" else echo "In a PR context comparing to pull_request.base.ref" git fetch origin "$GITHUB_BASE_REF" --depth=350 COMPARE_REV="$(git merge-base "origin/${GITHUB_BASE_REF}" HEAD)" fi CHANGED_FILES="$(git diff --name-only "$COMPARE_REV" "$GITHUB_SHA")" # When running from a subdirectory, git diff returns repo-root-relative paths. # Filter to only files within the current working directory and strip the prefix. REPO_SUBDIR="$(git rev-parse --show-prefix)" REPO_SUBDIR="${REPO_SUBDIR%/}" if [ -n "$REPO_SUBDIR" ]; then CHANGED_FILES="$(echo "$CHANGED_FILES" | grep "^${REPO_SUBDIR}/" | sed "s|^${REPO_SUBDIR}/||" || true)" fi check_pattern() { local output_name="$1" local pattern="$2" local grep_arg="$3" echo "$CHANGED_FILES" | grep "$grep_arg" "$pattern" && \ echo "${output_name}=true" >> "$GITHUB_OUTPUT" || \ echo "${output_name}=false" >> "$GITHUB_OUTPUT" } check_pattern "check_rust" '^(Cargo.lock|Cargo.toml|.*\.rs)$' -qP check_pattern "check_extension" '^(extension\.toml|.*\.scm)$' -qP
  • rustup target add wasm32-wasip2
  • PACKAGE_NAME="$(sed -n 's/^name = "\(.*\)"/\1/p' < Cargo.toml | head -1 | tr -d '[:space:]')" echo "package_name=${PACKAGE_NAME}" >> "$GITHUB_OUTPUT"
  • cargo fmt -p "$PACKAGE_NAME" -- --check
  • cargo clippy -p "$PACKAGE_NAME" --release --all-features -- --deny warnings
  • cargo nextest run -p "$PACKAGE_NAME" --no-fail-fast --no-tests=warn --target "$(rustc -vV | sed -n 's|host: ||p')"
  • wget --quiet "https://zed-extension-cli.nyc3.digitaloceanspaces.com/$ZED_EXTENSION_CLI_SHA/x86_64-unknown-linux-gnu/zed-extension" -O "$GITHUB_WORKSPACE/zed-extension" chmod +x "$GITHUB_WORKSPACE/zed-extension"
  • mkdir -p /tmp/ext-scratch mkdir -p /tmp/ext-output "$GITHUB_WORKSPACE/zed-extension" --source-dir . --scratch-dir /tmp/ext-scratch --output-dir /tmp/ext-output
View raw YAML
# Generated from xtask::workflows::extension_tests
# Rebuild with `cargo xtask workflows`.
name: extension_tests
env:
  CARGO_TERM_COLOR: always
  RUST_BACKTRACE: '1'
  CARGO_INCREMENTAL: '0'
  ZED_EXTENSION_CLI_SHA: 1fa7f1a3ec28ea1eae6db2e937d7a538fb10c0c7
  RUSTUP_TOOLCHAIN: stable
  CARGO_BUILD_TARGET: wasm32-wasip2
on:
  workflow_call:
    inputs:
      working-directory:
        description: working-directory
        type: string
        default: .
jobs:
  orchestrate:
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
        fetch-depth: ${{ github.ref == 'refs/heads/main' && 2 || 350 }}
    - id: filter
      name: filter
      run: |
        set -euo pipefail
        if [ -z "$GITHUB_BASE_REF" ]; then
          echo "Not in a PR context (i.e., push to main/stable/preview)"
          COMPARE_REV="$(git rev-parse HEAD~1)"
        else
          echo "In a PR context comparing to pull_request.base.ref"
          git fetch origin "$GITHUB_BASE_REF" --depth=350
          COMPARE_REV="$(git merge-base "origin/${GITHUB_BASE_REF}" HEAD)"
        fi
        CHANGED_FILES="$(git diff --name-only "$COMPARE_REV" "$GITHUB_SHA")"

        # When running from a subdirectory, git diff returns repo-root-relative paths.
        # Filter to only files within the current working directory and strip the prefix.
        REPO_SUBDIR="$(git rev-parse --show-prefix)"
        REPO_SUBDIR="${REPO_SUBDIR%/}"
        if [ -n "$REPO_SUBDIR" ]; then
            CHANGED_FILES="$(echo "$CHANGED_FILES" | grep "^${REPO_SUBDIR}/" | sed "s|^${REPO_SUBDIR}/||" || true)"
        fi

        check_pattern() {
          local output_name="$1"
          local pattern="$2"
          local grep_arg="$3"

          echo "$CHANGED_FILES" | grep "$grep_arg" "$pattern" && \
            echo "${output_name}=true" >> "$GITHUB_OUTPUT" || \
            echo "${output_name}=false" >> "$GITHUB_OUTPUT"
        }

        check_pattern "check_rust" '^(Cargo.lock|Cargo.toml|.*\.rs)$' -qP
        check_pattern "check_extension" '^(extension\.toml|.*\.scm)$' -qP
    outputs:
      check_rust: ${{ steps.filter.outputs.check_rust }}
      check_extension: ${{ steps.filter.outputs.check_extension }}
    defaults:
      run:
        shell: bash -euxo pipefail {0}
        working-directory: ${{ inputs.working-directory }}
  check_rust:
    needs:
    - orchestrate
    if: needs.orchestrate.outputs.check_rust == 'true'
    runs-on: namespace-profile-8x32-ubuntu-2404
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: extension_tests::install_rust_target
      run: rustup target add wasm32-wasip2
    - id: get-package-name
      name: extension_tests::get_package_name
      run: |
        PACKAGE_NAME="$(sed -n 's/^name = "\(.*\)"/\1/p' < Cargo.toml | head -1 | tr -d '[:space:]')"
        echo "package_name=${PACKAGE_NAME}" >> "$GITHUB_OUTPUT"
    - name: extension_tests::cargo_fmt_package
      run: cargo fmt -p "$PACKAGE_NAME" -- --check
      env:
        PACKAGE_NAME: ${{ steps.get-package-name.outputs.package_name }}
    - name: extension_tests::run_clippy
      run: cargo clippy -p "$PACKAGE_NAME" --release --all-features -- --deny warnings
      env:
        PACKAGE_NAME: ${{ steps.get-package-name.outputs.package_name }}
    - name: steps::cargo_install_nextest
      uses: taiki-e/install-action@921e2c9f7148d7ba14cd819f417db338f63e733c
    - name: extension_tests::run_nextest
      run: 'cargo nextest run -p "$PACKAGE_NAME" --no-fail-fast --no-tests=warn --target "$(rustc -vV | sed -n ''s|host: ||p'')"'
      env:
        PACKAGE_NAME: ${{ steps.get-package-name.outputs.package_name }}
        NEXTEST_NO_TESTS: warn
    timeout-minutes: 6
    defaults:
      run:
        shell: bash -euxo pipefail {0}
        working-directory: ${{ inputs.working-directory }}
  check_extension:
    needs:
    - orchestrate
    if: needs.orchestrate.outputs.check_extension == 'true'
    runs-on: namespace-profile-8x32-ubuntu-2404
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
        fetch-depth: 0
    - id: cache-zed-extension-cli
      name: extension_tests::cache_zed_extension_cli
      uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
      with:
        path: zed-extension
        key: zed-extension-${{ env.ZED_EXTENSION_CLI_SHA }}
    - name: extension_tests::download_zed_extension_cli
      if: steps.cache-zed-extension-cli.outputs.cache-hit != 'true'
      run: |
        wget --quiet "https://zed-extension-cli.nyc3.digitaloceanspaces.com/$ZED_EXTENSION_CLI_SHA/x86_64-unknown-linux-gnu/zed-extension" -O "$GITHUB_WORKSPACE/zed-extension"
        chmod +x "$GITHUB_WORKSPACE/zed-extension"
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: extension_tests::check
      run: |
        mkdir -p /tmp/ext-scratch
        mkdir -p /tmp/ext-output
        "$GITHUB_WORKSPACE/zed-extension" --source-dir . --scratch-dir /tmp/ext-scratch --output-dir /tmp/ext-output
    - name: run_tests::fetch_ts_query_ls
      uses: dsaltares/fetch-gh-release-asset@aa37ae5c44d3c9820bc12fe675e8670ecd93bd1c
      with:
        repo: ribru17/ts_query_ls
        version: tags/v3.15.1
        file: ts_query_ls-x86_64-unknown-linux-gnu.tar.gz
    - name: run_tests::run_ts_query_ls
      run: |-
        tar -xf "$GITHUB_WORKSPACE/ts_query_ls-x86_64-unknown-linux-gnu.tar.gz" -C "$GITHUB_WORKSPACE"
        "$GITHUB_WORKSPACE/ts_query_ls" format --check . || {
            echo "Found unformatted queries, please format them with ts_query_ls."
            echo "For easy use, install the Tree-sitter query extension:"
            echo "zed://extension/tree-sitter-query"
            false
        }
    - id: compare-versions-check
      name: extension_bump::compare_versions
      run: |
        CURRENT_VERSION="$(sed -n 's/^version = \"\(.*\)\"/\1/p' < extension.toml | tr -d '[:space:]')"

        if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then
            PR_FORK_POINT="$(git merge-base origin/main HEAD)"
            git checkout "$PR_FORK_POINT"
        else
            git checkout "$(git log -1 --format=%H)"~1
        fi

        PARENT_COMMIT_VERSION="$(sed -n 's/^version = \"\(.*\)\"/\1/p' < extension.toml | tr -d '[:space:]')"

        [[ "$CURRENT_VERSION" == "$PARENT_COMMIT_VERSION" ]] && \
            echo "version_changed=false" >> "$GITHUB_OUTPUT" || \
            echo "version_changed=true" >> "$GITHUB_OUTPUT"

        echo "current_version=${CURRENT_VERSION}" >> "$GITHUB_OUTPUT"
    - name: extension_tests::verify_version_did_not_change
      run: |
        if [[ "$VERSION_CHANGED" == "true" && "$GITHUB_EVENT_NAME" == "pull_request" && "$PR_USER_LOGIN" != "zed-zippy[bot]" ]] ; then
            echo "Version change detected in your change!"
            echo "Version changes happen in separate PRs and will be performed by the zed-zippy bot"
            exit 42
        fi
      env:
        VERSION_CHANGED: ${{ steps.compare-versions-check.outputs.version_changed }}
        PR_USER_LOGIN: ${{ github.event.pull_request.user.login }}
    timeout-minutes: 6
    defaults:
      run:
        shell: bash -euxo pipefail {0}
        working-directory: ${{ inputs.working-directory }}
  tests_pass:
    needs:
    - orchestrate
    - check_rust
    - check_extension
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions') && always()
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - name: run_tests::tests_pass
      run: |
        set +x
        EXIT_CODE=0

        check_result() {
          echo "* $1: $2"
          if [[ "$2" != "skipped" && "$2" != "success" ]]; then EXIT_CODE=1; fi
        }

        check_result "orchestrate" "$RESULT_ORCHESTRATE"
        check_result "check_rust" "$RESULT_CHECK_RUST"
        check_result "check_extension" "$RESULT_CHECK_EXTENSION"

        exit $EXIT_CODE
      env:
        RESULT_ORCHESTRATE: ${{ needs.orchestrate.result }}
        RESULT_CHECK_RUST: ${{ needs.check_rust.result }}
        RESULT_CHECK_EXTENSION: ${{ needs.check_extension.result }}
concurrency:
  group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}extension-tests
  cancel-in-progress: true
defaults:
  run:
    shell: bash -euxo pipefail {0}
extension_workflow_rollout matrix .github/workflows/extension_workflow_rollout.yml
Triggers
workflow_dispatch
Runs on
namespace-profile-2x4-ubuntu-2404, namespace-profile-2x4-ubuntu-2404, namespace-profile-2x4-ubuntu-2404
Jobs
fetch_extension_repos, rollout_workflows_to_extension, create_rollout_tag
Matrix
repo→ ${{ fromJson(needs.fetch_extension_repos.outputs.repos) }}
Actions
namespacelabs/nscloud-cache-action, actions/create-github-app-token, peter-evans/create-pull-request, actions/create-github-app-token
Commands
  • PREV_COMMIT=$(git rev-parse "extension-workflows^{commit}" 2>/dev/null || echo "") if [ -z "$PREV_COMMIT" ]; then echo "::error::No previous rollout tag 'extension-workflows' found. Cannot determine file changes." exit 1 fi echo "Found previous rollout at commit: $PREV_COMMIT" echo "prev_commit=$PREV_COMMIT" >> "$GITHUB_OUTPUT"
  • for workflow_type in "ci" "shared"; do if [ "$workflow_type" = "ci" ]; then WORKFLOW_DIR="extensions/workflows" else WORKFLOW_DIR="extensions/workflows/shared" fi REMOVED=$(git diff --name-status -M "$PREV_COMMIT" HEAD -- "$WORKFLOW_DIR" | \ awk '/^D/ { print $2 } /^R/ { print $2 }' | \ xargs -I{} basename {} 2>/dev/null | \ tr '\n' ' ' || echo "") REMOVED=$(echo "$REMOVED" | xargs) echo "Removed files for $workflow_type: $REMOVED" echo "removed_${workflow_type}=$REMOVED" >> "$GITHUB_OUTPUT" done
  • cargo xtask workflows "$COMMIT_SHA"
  • mkdir -p extension/.github/workflows if [ "$MATRIX_REPO" = "workflows" ]; then REMOVED_FILES="$REMOVED_CI" else REMOVED_FILES="$REMOVED_SHARED" fi cd extension/.github/workflows if [ -n "$REMOVED_FILES" ]; then for file in $REMOVED_FILES; do if [ -f "$file" ]; then rm -f "$file" fi done fi cd - > /dev/null if [ "$MATRIX_REPO" = "workflows" ]; then cp workflow-files/*.yml extension/.github/workflows/ else cp workflow-files/shared/*.yml extension/.github/workflows/ fi
  • echo "sha_short=$(echo "$GITHUB_SHA" | cut -c1-7)" >> "$GITHUB_OUTPUT"
  • if [ -n "$PR_NUMBER" ]; then gh pr merge "$PR_NUMBER" --auto --squash fi
  • git config user.name "zed-zippy[bot]" git config user.email "234243425+zed-zippy[bot]@users.noreply.github.com"
  • if git rev-parse "extension-workflows" >/dev/null 2>&1; then git tag -d "extension-workflows" git push origin ":refs/tags/extension-workflows" || true fi echo "Creating new tag 'extension-workflows' at $(git rev-parse --short HEAD)" git tag "extension-workflows" git push origin "extension-workflows"
View raw YAML
# Generated from xtask::workflows::extension_workflow_rollout
# Rebuild with `cargo xtask workflows`.
name: extension_workflow_rollout
env:
  CARGO_TERM_COLOR: always
on:
  workflow_dispatch:
    inputs:
      filter-repos:
        description: Comma-separated list of repository names to rollout to. Leave empty for all repos.
        type: string
        default: ''
      change-description:
        description: Description for the changes to be expected with this rollout
        type: string
        default: ''
jobs:
  fetch_extension_repos:
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions') && github.ref == 'refs/heads/main'
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - name: checkout_zed_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
        fetch-depth: 0
    - id: prev-tag
      name: extension_workflow_rollout::fetch_extension_repos::get_previous_tag_commit
      run: |
        PREV_COMMIT=$(git rev-parse "extension-workflows^{commit}" 2>/dev/null || echo "")
        if [ -z "$PREV_COMMIT" ]; then
            echo "::error::No previous rollout tag 'extension-workflows' found. Cannot determine file changes."
            exit 1
        fi
        echo "Found previous rollout at commit: $PREV_COMMIT"
        echo "prev_commit=$PREV_COMMIT" >> "$GITHUB_OUTPUT"
    - id: calc-changes
      name: extension_workflow_rollout::fetch_extension_repos::get_removed_files
      run: |
        for workflow_type in "ci" "shared"; do
            if [ "$workflow_type" = "ci" ]; then
                WORKFLOW_DIR="extensions/workflows"
            else
                WORKFLOW_DIR="extensions/workflows/shared"
            fi

            REMOVED=$(git diff --name-status -M "$PREV_COMMIT" HEAD -- "$WORKFLOW_DIR" | \
                awk '/^D/ { print $2 } /^R/ { print $2 }' | \
                xargs -I{} basename {} 2>/dev/null | \
                tr '\n' ' ' || echo "")
            REMOVED=$(echo "$REMOVED" | xargs)

            echo "Removed files for $workflow_type: $REMOVED"
            echo "removed_${workflow_type}=$REMOVED" >> "$GITHUB_OUTPUT"
        done
      env:
        PREV_COMMIT: ${{ steps.prev-tag.outputs.prev_commit }}
    - id: list-repos
      name: extension_workflow_rollout::fetch_extension_repos::get_repositories
      uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b
      with:
        script: |
          const repos = await github.paginate(github.rest.repos.listForOrg, {
              org: 'zed-extensions',
              type: 'public',
              per_page: 100,
          });

          let filteredRepos = repos
              .filter(repo => !repo.archived)
              .map(repo => repo.name);

          const filterInput = `${{ inputs.filter-repos }}`.trim();
          if (filterInput.length > 0) {
              const allowedNames = filterInput.split(',').map(s => s.trim()).filter(s => s.length > 0);
              filteredRepos = filteredRepos.filter(name => allowedNames.includes(name));
              console.log(`Filter applied. Matched ${filteredRepos.length} repos from ${allowedNames.length} requested.`);
          }

          console.log(`Found ${filteredRepos.length} extension repos`);
          return filteredRepos;
        result-encoding: json
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: extension_workflow_rollout::fetch_extension_repos::generate_workflow_files
      run: |
        cargo xtask workflows "$COMMIT_SHA"
      env:
        COMMIT_SHA: ${{ github.sha }}
    - name: extension_workflow_rollout::fetch_extension_repos::upload_workflow_files
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: extension-workflow-files
        path: extensions/workflows/**/*.yml
        if-no-files-found: error
    outputs:
      repos: ${{ steps.list-repos.outputs.result }}
      prev_commit: ${{ steps.prev-tag.outputs.prev_commit }}
      removed_ci: ${{ steps.calc-changes.outputs.removed_ci }}
      removed_shared: ${{ steps.calc-changes.outputs.removed_shared }}
    timeout-minutes: 10
  rollout_workflows_to_extension:
    needs:
    - fetch_extension_repos
    if: needs.fetch_extension_repos.outputs.repos != '[]'
    runs-on: namespace-profile-2x4-ubuntu-2404
    strategy:
      matrix:
        repo: ${{ fromJson(needs.fetch_extension_repos.outputs.repos) }}
      fail-fast: false
      max-parallel: 10
    steps:
    - id: generate-token
      name: steps::generate_token
      uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859
      with:
        app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
        private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
        owner: zed-extensions
        repositories: ${{ matrix.repo }}
        permission-pull-requests: write
        permission-contents: write
        permission-workflows: write
    - name: checkout_extension_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
        path: extension
        repository: zed-extensions/${{ matrix.repo }}
        token: ${{ steps.generate-token.outputs.token }}
    - name: extension_workflow_rollout::rollout_workflows_to_extension::download_workflow_files
      uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
      with:
        name: extension-workflow-files
        path: workflow-files
    - name: extension_workflow_rollout::rollout_workflows_to_extension::sync_workflow_files
      run: |
        mkdir -p extension/.github/workflows

        if [ "$MATRIX_REPO" = "workflows" ]; then
            REMOVED_FILES="$REMOVED_CI"
        else
            REMOVED_FILES="$REMOVED_SHARED"
        fi

        cd extension/.github/workflows

        if [ -n "$REMOVED_FILES" ]; then
            for file in $REMOVED_FILES; do
                if [ -f "$file" ]; then
                    rm -f "$file"
                fi
            done
        fi

        cd - > /dev/null

        if [ "$MATRIX_REPO" = "workflows" ]; then
            cp workflow-files/*.yml extension/.github/workflows/
        else
            cp workflow-files/shared/*.yml extension/.github/workflows/
        fi
      env:
        REMOVED_CI: ${{ needs.fetch_extension_repos.outputs.removed_ci }}
        REMOVED_SHARED: ${{ needs.fetch_extension_repos.outputs.removed_shared }}
        MATRIX_REPO: ${{ matrix.repo }}
    - id: short-sha
      name: extension_workflow_rollout::rollout_workflows_to_extension::get_short_sha
      run: |
        echo "sha_short=$(echo "$GITHUB_SHA" | cut -c1-7)" >> "$GITHUB_OUTPUT"
    - id: create-pr
      name: extension_workflow_rollout::rollout_workflows_to_extension::create_pull_request
      uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725
      with:
        path: extension
        title: Update CI workflows to `${{ steps.short-sha.outputs.sha_short }}`
        body: |
          This PR updates the CI workflow files from the main Zed repository
          based on the commit zed-industries/zed@${{ github.sha }}

          ${{ inputs.change-description }}
        commit-message: Update CI workflows to `${{ steps.short-sha.outputs.sha_short }}`
        branch: update-workflows
        committer: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>
        author: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>
        base: main
        delete-branch: true
        token: ${{ steps.generate-token.outputs.token }}
        sign-commits: true
    - name: extension_workflow_rollout::rollout_workflows_to_extension::enable_auto_merge
      run: |
        if [ -n "$PR_NUMBER" ]; then
            gh pr merge "$PR_NUMBER" --auto --squash
        fi
      env:
        GH_TOKEN: ${{ steps.generate-token.outputs.token }}
        PR_NUMBER: ${{ steps.create-pr.outputs.pull-request-number }}
      working-directory: extension
    timeout-minutes: 10
  create_rollout_tag:
    needs:
    - rollout_workflows_to_extension
    if: inputs.filter-repos == ''
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - id: generate-token
      name: steps::generate_token
      uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859
      with:
        app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
        private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
        permission-contents: write
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
        fetch-depth: 0
        token: ${{ steps.generate-token.outputs.token }}
    - name: extension_workflow_rollout::create_rollout_tag::configure_git
      run: |
        git config user.name "zed-zippy[bot]"
        git config user.email "234243425+zed-zippy[bot]@users.noreply.github.com"
    - name: extension_workflow_rollout::create_rollout_tag::update_rollout_tag
      run: |
        if git rev-parse "extension-workflows" >/dev/null 2>&1; then
            git tag -d "extension-workflows"
            git push origin ":refs/tags/extension-workflows" || true
        fi

        echo "Creating new tag 'extension-workflows' at $(git rev-parse --short HEAD)"
        git tag "extension-workflows"
        git push origin "extension-workflows"
    timeout-minutes: 1
defaults:
  run:
    shell: bash -euxo pipefail {0}
good_first_issue_notifier .github/workflows/good_first_issue_notifier.yml
Triggers
issues
Runs on
namespace-profile-2x4-ubuntu-2404
Jobs
handle-good-first-issue
Actions
tsickert/discord-webhook
Commands
  • MESSAGE="[${ISSUE_TITLE} (#${ISSUE_NUMBER})](<${ISSUE_URL}>)" { echo "message<<EOF" echo "$MESSAGE" echo "EOF" } >> "$GITHUB_OUTPUT"
View raw YAML
name: Good First Issue Notifier

on:
  issues:
    types: [labeled]

jobs:
  handle-good-first-issue:
    if: github.event.label.name == '.contrib/good first issue' && github.repository_owner == 'zed-industries'
    runs-on: namespace-profile-2x4-ubuntu-2404

    steps:
      - name: Checkout repository
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1

      - name: Prepare Discord message
        id: prepare-message
        env:
          ISSUE_TITLE: ${{ github.event.issue.title }}
          ISSUE_NUMBER: ${{ github.event.issue.number }}
          ISSUE_URL: ${{ github.event.issue.html_url }}
          ISSUE_AUTHOR: ${{ github.event.issue.user.login }}
        run: |
          MESSAGE="[${ISSUE_TITLE} (#${ISSUE_NUMBER})](<${ISSUE_URL}>)"

          {
            echo "message<<EOF"
            echo "$MESSAGE"
            echo "EOF"
          } >> "$GITHUB_OUTPUT"

      - name: Discord Webhook Action
        uses: tsickert/discord-webhook@c840d45a03a323fbc3f7507ac7769dbd91bfb164 # v5.3.0
        with:
          webhook-url: ${{ secrets.DISCORD_WEBHOOK_GOOD_FIRST_ISSUE }}
          content: ${{ steps.prepare-message.outputs.message }}
hotfix-review-monitor perms .github/workflows/hotfix-review-monitor.yml
Triggers
schedule, workflow_dispatch
Runs on
ubuntu-latest
Jobs
check-hotfix-reviews
Commands
  • # 80h lookback covers the Friday-to-Monday gap (72h) with buffer. # Overlap on weekdays is harmless — reviewed PRs are filtered out below. SINCE=$(date -u -v-80H +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \ || date -u -d '80 hours ago' +%Y-%m-%dT%H:%M:%SZ) SINCE_DATE=$(echo "$SINCE" | cut -dT -f1) # Use the Search API to find hotfix PRs merged in the lookback window. # The Pulls API with state=closed paginates through all closed PRs in # the repo, which times out on large repos. The Search API supports # merged:>DATE natively so GitHub does the filtering server-side. gh api --paginate \ "search/issues?q=repo:${REPO}+is:pr+is:merged+label:hotfix+merged:>${SINCE_DATE}&per_page=100" \ --jq '[.items[] | {number, title, merged_at: .pull_request.merged_at}]' \ > /tmp/hotfix_prs.json # Check each hotfix PR for a post-merge approving review jq -r '.[].number' /tmp/hotfix_prs.json | while read -r PR_NUMBER; do APPROVALS=$(gh api \ "repos/${REPO}/pulls/${PR_NUMBER}/reviews" \ --jq "[.[] | select(.state == \"APPROVED\")] | length") if [ "$APPROVALS" -eq 0 ]; then jq ".[] | select(.number == ${PR_NUMBER})" /tmp/hotfix_prs.json fi done | jq -s '.' > /tmp/unreviewed.json COUNT=$(jq 'length' /tmp/unreviewed.json) echo "count=$COUNT" >> "$GITHUB_OUTPUT"
  • # Build Block Kit payload from JSON — no shell interpolation of PR titles. # Why jq? PR titles are attacker-controllable input. By reading them # through jq -r from the JSON file and passing the result to jq --arg, # the content stays safely JSON-encoded in the final payload. Block Kit # doesn't change this — the same jq pipeline feeds into the blocks # structure instead of plain text. PRS=$(jq -r '.[] | "• <https://github.com/'"${REPO}"'/pull/\(.number)|#\(.number)> — \(.title) (merged \(.merged_at | split("T")[0]))"' /tmp/unreviewed.json) jq -n \ --arg count "$COUNT" \ --arg prs "$PRS" \ '{ text: ($count + " hotfix PR(s) still need post-merge review"), blocks: [ { type: "section", text: { type: "mrkdwn", text: (":rotating_light: *" + $count + " Hotfix PR(s) Need Post-Merge Review*") } }, { type: "section", text: { type: "mrkdwn", text: $prs } }, { type: "divider" }, { type: "context", elements: [{ type: "mrkdwn", text: "Hotfix PRs require review within one business day of merge." }] } ] }' | \ curl -s -X POST "$SLACK_WEBHOOK_PR_REVIEW_BOT" \ -H 'Content-Type: application/json' \ -d @-
View raw YAML
# Hotfix Review Monitor
#
# Runs daily and checks for merged PRs with the 'hotfix' label that have not
# received a post-merge review approval within one business day. Posts a summary to
# Slack if any are found. This is a SOC2 compensating control for the
# emergency hotfix fast path.
#
# Security note: No untrusted input (PR titles, bodies, etc.) is interpolated
# into shell commands. All PR metadata is read via gh API + jq, not via
# github.event context expressions.
#
# Required secrets:
#   SLACK_WEBHOOK_PR_REVIEW_BOT - Incoming webhook URL for the #pr-review-ops channel

name: Hotfix Review Monitor

on:
  schedule:
    - cron: "30 13 * * 1-5" # 1:30 PM UTC weekdays
  workflow_dispatch: {}

permissions:
  contents: read
  pull-requests: read

jobs:
  check-hotfix-reviews:
    if: github.repository_owner == 'zed-industries'
    runs-on: ubuntu-latest
    timeout-minutes: 5
    env:
      REPO: ${{ github.repository }}
    steps:
      - name: Find unreviewed hotfixes
        id: check
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          # 80h lookback covers the Friday-to-Monday gap (72h) with buffer.
          # Overlap on weekdays is harmless — reviewed PRs are filtered out below.
          SINCE=$(date -u -v-80H +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
            || date -u -d '80 hours ago' +%Y-%m-%dT%H:%M:%SZ)
          SINCE_DATE=$(echo "$SINCE" | cut -dT -f1)

          # Use the Search API to find hotfix PRs merged in the lookback window.
          # The Pulls API with state=closed paginates through all closed PRs in
          # the repo, which times out on large repos. The Search API supports
          # merged:>DATE natively so GitHub does the filtering server-side.
          gh api --paginate \
            "search/issues?q=repo:${REPO}+is:pr+is:merged+label:hotfix+merged:>${SINCE_DATE}&per_page=100" \
            --jq '[.items[] | {number, title, merged_at: .pull_request.merged_at}]' \
            > /tmp/hotfix_prs.json

          # Check each hotfix PR for a post-merge approving review
          jq -r '.[].number' /tmp/hotfix_prs.json | while read -r PR_NUMBER; do
            APPROVALS=$(gh api \
              "repos/${REPO}/pulls/${PR_NUMBER}/reviews" \
              --jq "[.[] | select(.state == \"APPROVED\")] | length")

            if [ "$APPROVALS" -eq 0 ]; then
              jq ".[] | select(.number == ${PR_NUMBER})" /tmp/hotfix_prs.json
            fi
          done | jq -s '.' > /tmp/unreviewed.json

          COUNT=$(jq 'length' /tmp/unreviewed.json)
          echo "count=$COUNT" >> "$GITHUB_OUTPUT"

      - name: Notify Slack
        if: steps.check.outputs.count != '0'
        env:
          SLACK_WEBHOOK_PR_REVIEW_BOT: ${{ secrets.SLACK_WEBHOOK_PR_REVIEW_BOT }}
          COUNT: ${{ steps.check.outputs.count }}
        run: |
          # Build Block Kit payload from JSON — no shell interpolation of PR titles.
          # Why jq? PR titles are attacker-controllable input. By reading them
          # through jq -r from the JSON file and passing the result to jq --arg,
          # the content stays safely JSON-encoded in the final payload. Block Kit
          # doesn't change this — the same jq pipeline feeds into the blocks
          # structure instead of plain text.
          PRS=$(jq -r '.[] | "• <https://github.com/'"${REPO}"'/pull/\(.number)|#\(.number)> — \(.title) (merged \(.merged_at | split("T")[0]))"' /tmp/unreviewed.json)

          jq -n \
            --arg count "$COUNT" \
            --arg prs "$PRS" \
            '{
              text: ($count + " hotfix PR(s) still need post-merge review"),
              blocks: [
                {
                  type: "section",
                  text: {
                    type: "mrkdwn",
                    text: (":rotating_light: *" + $count + " Hotfix PR(s) Need Post-Merge Review*")
                  }
                },
                {
                  type: "section",
                  text: { type: "mrkdwn", text: $prs }
                },
                { type: "divider" },
                {
                  type: "context",
                  elements: [{
                    type: "mrkdwn",
                    text: "Hotfix PRs require review within one business day of merge."
                  }]
                }
              ]
            }' | \
          curl -s -X POST "$SLACK_WEBHOOK_PR_REVIEW_BOT" \
            -H 'Content-Type: application/json' \
            -d @-
defaults:
  run:
    shell: bash -euxo pipefail {0}
pr_labeler perms .github/workflows/pr_labeler.yml
Triggers
pull_request_target
Runs on
namespace-profile-2x4-ubuntu-2404
Jobs
check-authorship-and-label
Actions
actions/create-github-app-token
View raw YAML
# Labels pull requests by author: 'bot' for bot accounts, 'staff' for
# staff team members, 'guild' for guild members, 'first contribution' for
# first-time external contributors.
name: PR Labeler

on:
  pull_request_target:
    types: [opened]

permissions:
  contents: read

jobs:
  check-authorship-and-label:
    if: github.repository == 'zed-industries/zed'
    runs-on: namespace-profile-2x4-ubuntu-2404
    timeout-minutes: 5
    steps:
      - id: get-app-token
        uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
        with:
          app-id: ${{ secrets.ZED_COMMUNITY_BOT_APP_ID }}
          private-key: ${{ secrets.ZED_COMMUNITY_BOT_PRIVATE_KEY }}
          owner: zed-industries

      - id: apply-authorship-label
        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
        with:
          github-token: ${{ steps.get-app-token.outputs.token }}
          script: |
            const BOT_LABEL = 'bot';
            const STAFF_LABEL = 'staff';
            const GUILD_LABEL = 'guild';
            const FIRST_CONTRIBUTION_LABEL = 'first contribution';
            const STAFF_TEAM_SLUG = 'staff';
            const GUILD_MEMBERS = [
              '11happy',
              'AidanV',
              'AmaanBilwar',
              'OmChillure',
              'Palanikannan1437',
              'Shivansh-25',
              'SkandaBhat',
              'TwistingTwists',
              'YEDASAVG',
              'Ziqi-Yang',
              'alanpjohn',
              'arjunkomath',
              'austincummings',
              'ayushk-1801',
              'claiwe',
              'criticic',
              'dongdong867',
              'emamulandalib',
              'eureka928',
              'feitreim',
              'iam-liam',
              'iksuddle',
              'ishaksebsib',
              'lingyaochu',
              'loadingalias',
              'marcocondrache',
              'mchisolm0',
              'mostlyKIGuess',
              'nairadithya',
              'nihalxkumar',
              'notJoon',
              'polyesterswing',
              'prayanshchh',
              'razeghi71',
              'sarmadgulzar',
              'seanstrom',
              'th0jensen',
              'tommyming',
              'virajbhartiya',
            ];

            const pr = context.payload.pull_request;
            const author = pr.user.login;

            if (pr.user.type === 'Bot') {
              await github.rest.issues.addLabels({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: pr.number,
                labels: [BOT_LABEL]
              });
              console.log(`PR #${pr.number} by ${author}: labeled '${BOT_LABEL}' (user type: '${pr.user.type}')`);
              return;
            }

            let isStaff = false;
            try {
              const response = await github.rest.teams.getMembershipForUserInOrg({
                org: 'zed-industries',
                team_slug: STAFF_TEAM_SLUG,
                username: author
              });
              isStaff = response.data.state === 'active';
            } catch (error) {
              if (error.status !== 404) {
                throw error;
              }
            }

            if (isStaff) {
              await github.rest.issues.addLabels({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: pr.number,
                labels: [STAFF_LABEL]
              });
              console.log(`PR #${pr.number} by ${author}: labeled '${STAFF_LABEL}' (staff team member)`);
              return;
            }

            if (GUILD_MEMBERS.includes(author)) {
              await github.rest.issues.addLabels({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: pr.number,
                labels: [GUILD_LABEL]
              });
              console.log(`PR #${pr.number} by ${author}: labeled '${GUILD_LABEL}' (guild member)`);
              // No early return: guild members can also get 'first contribution'
            }

            // We use inverted logic here due to a suspected GitHub bug where first-time contributors
            // get 'NONE' instead of 'FIRST_TIME_CONTRIBUTOR' or 'FIRST_TIMER'.
            // https://github.com/orgs/community/discussions/78038
            // This will break if GitHub ever adds new associations.
            const association = pr.author_association;
            const knownAssociations = ['CONTRIBUTOR', 'COLLABORATOR', 'MEMBER', 'OWNER', 'MANNEQUIN'];

            if (knownAssociations.includes(association)) {
              console.log(`PR #${pr.number} by ${author}: not a first-time contributor (association: '${association}')`);
              return;
            }

            await github.rest.issues.addLabels({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: pr.number,
              labels: [FIRST_CONTRIBUTION_LABEL]
            });
            console.log(`PR #${pr.number} by ${author}: labeled '${FIRST_CONTRIBUTION_LABEL}' (association: '${association}')`);
publish_extension_cli .github/workflows/publish_extension_cli.yml
Triggers
push
Runs on
namespace-profile-16x32-ubuntu-2204, namespace-profile-8x16-ubuntu-2204, namespace-profile-2x4-ubuntu-2404
Jobs
publish_job, update_sha_in_zed, update_sha_in_extensions
Actions
namespacelabs/nscloud-cache-action, actions/create-github-app-token, namespacelabs/nscloud-cache-action, peter-evans/create-pull-request, actions/create-github-app-token, peter-evans/create-pull-request
Commands
  • ./script/linux
  • cargo build --release --package extension_cli
  • script/upload-extension-cli "$GITHUB_SHA"
  • echo "sha_short=$(echo "$GITHUB_SHA" | cut -c1-7)" >> "$GITHUB_OUTPUT"
  • sed -i "s/ZED_EXTENSION_CLI_SHA: &str = \"[a-f0-9]*\"/ZED_EXTENSION_CLI_SHA: \&str = \"$GITHUB_SHA\"/" \ tooling/xtask/src/tasks/workflows/extension_tests.rs
  • cargo xtask workflows
  • echo "sha_short=$(echo "$GITHUB_SHA" | cut -c1-7)" >> "$GITHUB_OUTPUT"
  • sed -i "s/ZED_EXTENSION_CLI_SHA: [a-f0-9]*/ZED_EXTENSION_CLI_SHA: $GITHUB_SHA/" \ .github/workflows/ci.yml
View raw YAML
# Generated from xtask::workflows::publish_extension_cli
# Rebuild with `cargo xtask workflows`.
name: publish_extension_cli
env:
  CARGO_TERM_COLOR: always
  CARGO_INCREMENTAL: '0'
on:
  push:
    tags:
    - extension-cli
jobs:
  publish_job:
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: namespace-profile-16x32-ubuntu-2204
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: steps::setup_linux
      run: ./script/linux
    - name: publish_extension_cli::publish_job::build_extension_cli
      run: cargo build --release --package extension_cli
    - name: publish_extension_cli::publish_job::upload_binary
      run: script/upload-extension-cli "$GITHUB_SHA"
      env:
        DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
        DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
  update_sha_in_zed:
    needs:
    - publish_job
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: namespace-profile-8x16-ubuntu-2204
    steps:
    - id: generate-token
      name: steps::generate_token
      uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859
      with:
        app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
        private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - id: short-sha
      name: publish_extension_cli::get_short_sha
      run: |
        echo "sha_short=$(echo "$GITHUB_SHA" | cut -c1-7)" >> "$GITHUB_OUTPUT"
    - name: publish_extension_cli::update_sha_in_zed::replace_sha
      run: |
        sed -i "s/ZED_EXTENSION_CLI_SHA: &str = \"[a-f0-9]*\"/ZED_EXTENSION_CLI_SHA: \&str = \"$GITHUB_SHA\"/" \
            tooling/xtask/src/tasks/workflows/extension_tests.rs
    - name: publish_extension_cli::update_sha_in_zed::regenerate_workflows
      run: cargo xtask workflows
    - name: publish_extension_cli::create_pull_request_zed
      uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725
      with:
        title: 'extension_ci: Bump extension CLI version to `${{ steps.short-sha.outputs.sha_short }}`'
        body: |
          This PR bumps the extension CLI version used in the extension workflows to `${{ github.sha }}`.

          Release Notes:

          - N/A
        commit-message: 'extension_ci: Bump extension CLI version to `${{ steps.short-sha.outputs.sha_short }}`'
        branch: update-extension-cli-sha
        committer: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>
        base: main
        delete-branch: true
        token: ${{ steps.generate-token.outputs.token }}
        sign-commits: true
        assignees: ${{ github.actor }}
  update_sha_in_extensions:
    needs:
    - publish_job
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - id: generate-token
      name: steps::generate_token
      uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859
      with:
        app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
        private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
        owner: zed-industries
        repositories: extensions
    - id: short-sha
      name: publish_extension_cli::get_short_sha
      run: |
        echo "sha_short=$(echo "$GITHUB_SHA" | cut -c1-7)" >> "$GITHUB_OUTPUT"
    - name: publish_extension_cli::update_sha_in_extensions::checkout_extensions_repo
      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
      with:
        repository: zed-industries/extensions
        token: ${{ steps.generate-token.outputs.token }}
    - name: publish_extension_cli::update_sha_in_extensions::replace_sha
      run: |
        sed -i "s/ZED_EXTENSION_CLI_SHA: [a-f0-9]*/ZED_EXTENSION_CLI_SHA: $GITHUB_SHA/" \
            .github/workflows/ci.yml
    - name: publish_extension_cli::create_pull_request_extensions
      uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725
      with:
        title: Bump extension CLI version to `${{ steps.short-sha.outputs.sha_short }}`
        body: |
          This PR bumps the extension CLI version to https://github.com/zed-industries/zed/commit/${{ github.sha }}.
        commit-message: Bump extension CLI version to `${{ steps.short-sha.outputs.sha_short }}`
        branch: update-extension-cli-sha
        committer: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>
        base: main
        delete-branch: true
        token: ${{ steps.generate-token.outputs.token }}
        sign-commits: true
        labels: allow-no-extension
        assignees: ${{ github.actor }}
defaults:
  run:
    shell: bash -euxo pipefail {0}
randomized_tests .github/workflows/randomized_tests.yml
Triggers
push
Runs on
namespace-profile-16x32-ubuntu-2204
Jobs
tests
Commands
  • script/randomized-test-ci
View raw YAML
name: Randomized Tests

concurrency: randomized-tests

on:
  push:
    branches:
      - randomized-tests-runner
  # schedule:
  #    - cron: '0 * * * *'

env:
  CARGO_TERM_COLOR: always
  CARGO_INCREMENTAL: 0
  RUST_BACKTRACE: 1
  ZED_SERVER_URL: https://zed.dev

jobs:
  tests:
    name: Run randomized tests
    if: github.repository_owner == 'zed-industries'
    runs-on:
      - namespace-profile-16x32-ubuntu-2204
    steps:
      - name: Install Node
        uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
        with:
          node-version: "18"

      - name: Checkout repo
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          clean: false

      - name: Run randomized tests
        run: script/randomized-test-ci
release .github/workflows/release.yml
Triggers
push
Runs on
namespace-profile-mac-large, namespace-profile-16x32-ubuntu-2204, self-32vcpu-windows-2022, namespace-profile-mac-large, namespace-profile-16x32-ubuntu-2204, self-32vcpu-windows-2022, namespace-profile-2x4-ubuntu-2404, namespace-profile-2x4-ubuntu-2404, namespace-profile-8x32-ubuntu-2004-arm-m4, namespace-profile-32x64-ubuntu-2004, namespace-profile-mac-large, namespace-profile-mac-large, self-32vcpu-windows-2022, self-32vcpu-windows-2022, namespace-profile-4x8-ubuntu-2204, namespace-profile-2x4-ubuntu-2404, namespace-profile-2x4-ubuntu-2404, namespace-profile-2x4-ubuntu-2404
Jobs
run_tests_mac, run_tests_linux, run_tests_windows, clippy_mac, clippy_linux, clippy_windows, check_scripts, create_draft_release, bundle_linux_aarch64, bundle_linux_x86_64, bundle_mac_aarch64, bundle_mac_x86_64, bundle_windows_aarch64, bundle_windows_x86_64, upload_release_assets, validate_release_assets, auto_release_preview, push_release_update_notification
Actions
namespacelabs/nscloud-cache-action, taiki-e/install-action, namespacelabs/nscloud-cache-action, taiki-e/install-action, namespacelabs/nscloud-cache-action, namespacelabs/nscloud-cache-action, namespacelabs/nscloud-cache-action, matbour/setup-sentry-cli, matbour/setup-sentry-cli, matbour/setup-sentry-cli, matbour/setup-sentry-cli, matbour/setup-sentry-cli, matbour/setup-sentry-cli, actions/create-github-app-token
Commands
  • mkdir -p ./../.cargo cp ./.cargo/ci-config.toml ./../.cargo/config.toml
  • ./script/clear-target-dir-if-larger-than 300
  • ./script/setup-sccache
  • cargo nextest run --workspace --no-fail-fast --no-tests=warn
  • sccache --show-stats || true
  • rm -rf ./../.cargo
  • mkdir -p ./../.cargo cp ./.cargo/ci-config.toml ./../.cargo/config.toml
  • ./script/linux
View raw YAML
# Generated from xtask::workflows::release
# Rebuild with `cargo xtask workflows`.
name: release
env:
  CARGO_TERM_COLOR: always
  RUST_BACKTRACE: '1'
on:
  push:
    tags:
    - v*
jobs:
  run_tests_mac:
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: namespace-profile-mac-large
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_cargo_config
      run: |
        mkdir -p ./../.cargo
        cp ./.cargo/ci-config.toml ./../.cargo/config.toml
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: steps::setup_node
      uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
      with:
        node-version: '20'
    - name: steps::cargo_install_nextest
      uses: taiki-e/install-action@921e2c9f7148d7ba14cd819f417db338f63e733c
    - name: steps::clear_target_dir_if_large
      run: ./script/clear-target-dir-if-larger-than 300
    - name: steps::setup_sccache
      run: ./script/setup-sccache
      env:
        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
        SCCACHE_BUCKET: sccache-zed
    - name: steps::cargo_nextest
      run: cargo nextest run --workspace --no-fail-fast --no-tests=warn
    - name: steps::show_sccache_stats
      run: sccache --show-stats || true
    - name: steps::cleanup_cargo_config
      if: always()
      run: |
        rm -rf ./../.cargo
    timeout-minutes: 60
  run_tests_linux:
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: namespace-profile-16x32-ubuntu-2204
    env:
      CC: clang
      CXX: clang++
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_cargo_config
      run: |
        mkdir -p ./../.cargo
        cp ./.cargo/ci-config.toml ./../.cargo/config.toml
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: steps::setup_linux
      run: ./script/linux
    - name: steps::download_wasi_sdk
      run: ./script/download-wasi-sdk
    - name: steps::setup_node
      uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
      with:
        node-version: '20'
    - name: steps::cargo_install_nextest
      uses: taiki-e/install-action@921e2c9f7148d7ba14cd819f417db338f63e733c
    - name: steps::clear_target_dir_if_large
      run: ./script/clear-target-dir-if-larger-than 250
    - name: steps::setup_sccache
      run: ./script/setup-sccache
      env:
        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
        SCCACHE_BUCKET: sccache-zed
    - name: steps::cargo_nextest
      run: cargo nextest run --workspace --no-fail-fast --no-tests=warn
    - name: steps::show_sccache_stats
      run: sccache --show-stats || true
    - name: steps::cleanup_cargo_config
      if: always()
      run: |
        rm -rf ./../.cargo
    timeout-minutes: 60
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_HOST_AUTH_METHOD: trust
        ports:
        - 5432:5432
        options: --health-cmd pg_isready --health-interval 500ms --health-timeout 5s --health-retries 10
  run_tests_windows:
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: self-32vcpu-windows-2022
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_cargo_config
      run: |
        New-Item -ItemType Directory -Path "./../.cargo" -Force
        Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
      shell: pwsh
    - name: steps::setup_node
      uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
      with:
        node-version: '20'
    - name: steps::clear_target_dir_if_large
      run: ./script/clear-target-dir-if-larger-than.ps1 250
      shell: pwsh
    - name: steps::setup_sccache
      run: ./script/setup-sccache.ps1
      shell: pwsh
      env:
        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
        SCCACHE_BUCKET: sccache-zed
    - name: steps::cargo_nextest
      run: cargo nextest run --workspace --no-fail-fast --no-tests=warn
      shell: pwsh
    - name: steps::show_sccache_stats
      run: if ($env:RUSTC_WRAPPER) { & $env:RUSTC_WRAPPER --show-stats }; exit 0
      shell: pwsh
    - name: steps::cleanup_cargo_config
      if: always()
      run: |
        Remove-Item -Recurse -Path "./../.cargo" -Force -ErrorAction SilentlyContinue
      shell: pwsh
    timeout-minutes: 60
  clippy_mac:
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: namespace-profile-mac-large
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_cargo_config
      run: |
        mkdir -p ./../.cargo
        cp ./.cargo/ci-config.toml ./../.cargo/config.toml
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: steps::setup_sccache
      run: ./script/setup-sccache
      env:
        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
        SCCACHE_BUCKET: sccache-zed
    - name: steps::clippy
      run: ./script/clippy
    - name: steps::show_sccache_stats
      run: sccache --show-stats || true
    timeout-minutes: 60
  clippy_linux:
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: namespace-profile-16x32-ubuntu-2204
    env:
      CC: clang
      CXX: clang++
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_cargo_config
      run: |
        mkdir -p ./../.cargo
        cp ./.cargo/ci-config.toml ./../.cargo/config.toml
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: steps::setup_linux
      run: ./script/linux
    - name: steps::download_wasi_sdk
      run: ./script/download-wasi-sdk
    - name: steps::setup_sccache
      run: ./script/setup-sccache
      env:
        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
        SCCACHE_BUCKET: sccache-zed
    - name: steps::clippy
      run: ./script/clippy
    - name: steps::show_sccache_stats
      run: sccache --show-stats || true
    timeout-minutes: 60
  clippy_windows:
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: self-32vcpu-windows-2022
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_cargo_config
      run: |
        New-Item -ItemType Directory -Path "./../.cargo" -Force
        Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
      shell: pwsh
    - name: steps::setup_sccache
      run: ./script/setup-sccache.ps1
      shell: pwsh
      env:
        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
        SCCACHE_BUCKET: sccache-zed
    - name: steps::clippy
      run: ./script/clippy.ps1
      shell: pwsh
    - name: steps::show_sccache_stats
      run: if ($env:RUSTC_WRAPPER) { & $env:RUSTC_WRAPPER --show-stats }; exit 0
      shell: pwsh
    timeout-minutes: 60
  check_scripts:
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: run_tests::check_scripts::run_shellcheck
      run: ./script/shellcheck-scripts error
    - id: get_actionlint
      name: run_tests::check_scripts::download_actionlint
      run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
    - name: run_tests::check_scripts::run_actionlint
      run: '"$ACTIONLINT_BIN" -color'
      env:
        ACTIONLINT_BIN: ${{ steps.get_actionlint.outputs.executable }}
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: run_tests::check_scripts::check_xtask_workflows
      run: |
        cargo xtask workflows
        if ! git diff --exit-code .github; then
          echo "Error: .github directory has uncommitted changes after running 'cargo xtask workflows'"
          echo "Please run 'cargo xtask workflows' locally and commit the changes"
          exit 1
        fi
    timeout-minutes: 60
  create_draft_release:
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
        fetch-depth: 25
        ref: ${{ github.ref }}
    - name: script/determine-release-channel
      run: script/determine-release-channel
    - name: mkdir -p target/
      run: mkdir -p target/
    - name: release::create_draft_release::generate_release_notes
      run: node --redirect-warnings=/dev/null ./script/draft-release-notes "$RELEASE_VERSION" "$RELEASE_CHANNEL" > target/release-notes.md
    - name: release::create_draft_release::create_release
      run: script/create-draft-release target/release-notes.md
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    timeout-minutes: 60
  bundle_linux_aarch64:
    needs:
    - run_tests_linux
    - clippy_linux
    - check_scripts
    runs-on: namespace-profile-8x32-ubuntu-2004-arm-m4
    env:
      CARGO_INCREMENTAL: 0
      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
      CC: clang-18
      CXX: clang++-18
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_sentry
      uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
      with:
        token: ${{ secrets.SENTRY_AUTH_TOKEN }}
    - name: steps::setup_linux
      run: ./script/linux
    - name: steps::download_wasi_sdk
      run: ./script/download-wasi-sdk
    - name: ./script/bundle-linux
      run: ./script/bundle-linux
    - name: '@actions/upload-artifact zed-linux-aarch64.tar.gz'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: zed-linux-aarch64.tar.gz
        path: target/release/zed-linux-aarch64.tar.gz
        if-no-files-found: error
    - name: '@actions/upload-artifact zed-remote-server-linux-aarch64.gz'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: zed-remote-server-linux-aarch64.gz
        path: target/zed-remote-server-linux-aarch64.gz
        if-no-files-found: error
    timeout-minutes: 60
  bundle_linux_x86_64:
    needs:
    - run_tests_linux
    - clippy_linux
    - check_scripts
    runs-on: namespace-profile-32x64-ubuntu-2004
    env:
      CARGO_INCREMENTAL: 0
      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
      CC: clang-18
      CXX: clang++-18
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_sentry
      uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
      with:
        token: ${{ secrets.SENTRY_AUTH_TOKEN }}
    - name: steps::setup_linux
      run: ./script/linux
    - name: steps::download_wasi_sdk
      run: ./script/download-wasi-sdk
    - name: ./script/bundle-linux
      run: ./script/bundle-linux
    - name: '@actions/upload-artifact zed-linux-x86_64.tar.gz'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: zed-linux-x86_64.tar.gz
        path: target/release/zed-linux-x86_64.tar.gz
        if-no-files-found: error
    - name: '@actions/upload-artifact zed-remote-server-linux-x86_64.gz'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: zed-remote-server-linux-x86_64.gz
        path: target/zed-remote-server-linux-x86_64.gz
        if-no-files-found: error
    timeout-minutes: 60
  bundle_mac_aarch64:
    needs:
    - run_tests_mac
    - clippy_mac
    - check_scripts
    runs-on: namespace-profile-mac-large
    env:
      CARGO_INCREMENTAL: 0
      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
      MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
      MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
      APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
      APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
      APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_node
      uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
      with:
        node-version: '20'
    - name: steps::setup_sentry
      uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
      with:
        token: ${{ secrets.SENTRY_AUTH_TOKEN }}
    - name: steps::clear_target_dir_if_large
      run: ./script/clear-target-dir-if-larger-than 300
    - name: run_bundling::bundle_mac::bundle_mac
      run: ./script/bundle-mac aarch64-apple-darwin
    - name: '@actions/upload-artifact Zed-aarch64.dmg'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: Zed-aarch64.dmg
        path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
        if-no-files-found: error
    - name: '@actions/upload-artifact zed-remote-server-macos-aarch64.gz'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: zed-remote-server-macos-aarch64.gz
        path: target/zed-remote-server-macos-aarch64.gz
        if-no-files-found: error
    timeout-minutes: 60
  bundle_mac_x86_64:
    needs:
    - run_tests_mac
    - clippy_mac
    - check_scripts
    runs-on: namespace-profile-mac-large
    env:
      CARGO_INCREMENTAL: 0
      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
      MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
      MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
      APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
      APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
      APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_node
      uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
      with:
        node-version: '20'
    - name: steps::setup_sentry
      uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
      with:
        token: ${{ secrets.SENTRY_AUTH_TOKEN }}
    - name: steps::clear_target_dir_if_large
      run: ./script/clear-target-dir-if-larger-than 300
    - name: run_bundling::bundle_mac::bundle_mac
      run: ./script/bundle-mac x86_64-apple-darwin
    - name: '@actions/upload-artifact Zed-x86_64.dmg'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: Zed-x86_64.dmg
        path: target/x86_64-apple-darwin/release/Zed-x86_64.dmg
        if-no-files-found: error
    - name: '@actions/upload-artifact zed-remote-server-macos-x86_64.gz'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: zed-remote-server-macos-x86_64.gz
        path: target/zed-remote-server-macos-x86_64.gz
        if-no-files-found: error
    timeout-minutes: 60
  bundle_windows_aarch64:
    needs:
    - run_tests_windows
    - clippy_windows
    - check_scripts
    runs-on: self-32vcpu-windows-2022
    env:
      CARGO_INCREMENTAL: 0
      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
      AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
      AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
      AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
      ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
      CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
      ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
      FILE_DIGEST: SHA256
      TIMESTAMP_DIGEST: SHA256
      TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_sentry
      uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
      with:
        token: ${{ secrets.SENTRY_AUTH_TOKEN }}
    - name: run_bundling::bundle_windows::bundle_windows
      run: script/bundle-windows.ps1 -Architecture aarch64
      shell: pwsh
      working-directory: ${{ env.ZED_WORKSPACE }}
    - name: '@actions/upload-artifact Zed-aarch64.exe'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: Zed-aarch64.exe
        path: target/Zed-aarch64.exe
        if-no-files-found: error
    - name: '@actions/upload-artifact zed-remote-server-windows-aarch64.zip'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: zed-remote-server-windows-aarch64.zip
        path: target/zed-remote-server-windows-aarch64.zip
        if-no-files-found: error
    timeout-minutes: 60
  bundle_windows_x86_64:
    needs:
    - run_tests_windows
    - clippy_windows
    - check_scripts
    runs-on: self-32vcpu-windows-2022
    env:
      CARGO_INCREMENTAL: 0
      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
      AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
      AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
      AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
      ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
      CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
      ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
      FILE_DIGEST: SHA256
      TIMESTAMP_DIGEST: SHA256
      TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_sentry
      uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
      with:
        token: ${{ secrets.SENTRY_AUTH_TOKEN }}
    - name: run_bundling::bundle_windows::bundle_windows
      run: script/bundle-windows.ps1 -Architecture x86_64
      shell: pwsh
      working-directory: ${{ env.ZED_WORKSPACE }}
    - name: '@actions/upload-artifact Zed-x86_64.exe'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: Zed-x86_64.exe
        path: target/Zed-x86_64.exe
        if-no-files-found: error
    - name: '@actions/upload-artifact zed-remote-server-windows-x86_64.zip'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: zed-remote-server-windows-x86_64.zip
        path: target/zed-remote-server-windows-x86_64.zip
        if-no-files-found: error
    timeout-minutes: 60
  upload_release_assets:
    needs:
    - create_draft_release
    - bundle_linux_aarch64
    - bundle_linux_x86_64
    - bundle_mac_aarch64
    - bundle_mac_x86_64
    - bundle_windows_aarch64
    - bundle_windows_x86_64
    runs-on: namespace-profile-4x8-ubuntu-2204
    steps:
    - name: release::download_workflow_artifacts
      uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
      with:
        path: ./artifacts/
    - name: ls -lR ./artifacts
      run: ls -lR ./artifacts
    - name: release::prep_release_artifacts
      run: |-
        mkdir -p release-artifacts/

        mv ./artifacts/Zed-aarch64.dmg/Zed-aarch64.dmg release-artifacts/Zed-aarch64.dmg
        mv ./artifacts/Zed-x86_64.dmg/Zed-x86_64.dmg release-artifacts/Zed-x86_64.dmg
        mv ./artifacts/zed-linux-aarch64.tar.gz/zed-linux-aarch64.tar.gz release-artifacts/zed-linux-aarch64.tar.gz
        mv ./artifacts/zed-linux-x86_64.tar.gz/zed-linux-x86_64.tar.gz release-artifacts/zed-linux-x86_64.tar.gz
        mv ./artifacts/Zed-x86_64.exe/Zed-x86_64.exe release-artifacts/Zed-x86_64.exe
        mv ./artifacts/Zed-aarch64.exe/Zed-aarch64.exe release-artifacts/Zed-aarch64.exe
        mv ./artifacts/zed-remote-server-macos-aarch64.gz/zed-remote-server-macos-aarch64.gz release-artifacts/zed-remote-server-macos-aarch64.gz
        mv ./artifacts/zed-remote-server-macos-x86_64.gz/zed-remote-server-macos-x86_64.gz release-artifacts/zed-remote-server-macos-x86_64.gz
        mv ./artifacts/zed-remote-server-linux-aarch64.gz/zed-remote-server-linux-aarch64.gz release-artifacts/zed-remote-server-linux-aarch64.gz
        mv ./artifacts/zed-remote-server-linux-x86_64.gz/zed-remote-server-linux-x86_64.gz release-artifacts/zed-remote-server-linux-x86_64.gz
        mv ./artifacts/zed-remote-server-windows-aarch64.zip/zed-remote-server-windows-aarch64.zip release-artifacts/zed-remote-server-windows-aarch64.zip
        mv ./artifacts/zed-remote-server-windows-x86_64.zip/zed-remote-server-windows-x86_64.zip release-artifacts/zed-remote-server-windows-x86_64.zip
    - name: gh release upload "$GITHUB_REF_NAME" --repo=zed-industries/zed release-artifacts/*
      run: gh release upload "$GITHUB_REF_NAME" --repo=zed-industries/zed release-artifacts/*
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  validate_release_assets:
    needs:
    - upload_release_assets
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - name: release::validate_release_assets
      run: |
        EXPECTED_ASSETS='["Zed-aarch64.dmg", "Zed-x86_64.dmg", "zed-linux-aarch64.tar.gz", "zed-linux-x86_64.tar.gz", "Zed-x86_64.exe", "Zed-aarch64.exe", "zed-remote-server-macos-aarch64.gz", "zed-remote-server-macos-x86_64.gz", "zed-remote-server-linux-aarch64.gz", "zed-remote-server-linux-x86_64.gz", "zed-remote-server-windows-aarch64.zip", "zed-remote-server-windows-x86_64.zip"]'
        TAG="$GITHUB_REF_NAME"

        ACTUAL_ASSETS=$(gh release view "$TAG" --repo=zed-industries/zed --json assets -q '[.assets[].name]')

        MISSING_ASSETS=$(echo "$EXPECTED_ASSETS" | jq -r --argjson actual "$ACTUAL_ASSETS" '. - $actual | .[]')

        if [ -n "$MISSING_ASSETS" ]; then
            echo "Error: The following assets are missing from the release:"
            echo "$MISSING_ASSETS"
            exit 1
        fi

        echo "All expected assets are present in the release."
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  auto_release_preview:
    needs:
    - validate_release_assets
    if: startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre')
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - id: generate-token
      name: steps::authenticate_as_zippy
      uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859
      with:
        app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
        private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
    - name: gh release edit "$GITHUB_REF_NAME" --repo=zed-industries/zed --draft=false
      run: gh release edit "$GITHUB_REF_NAME" --repo=zed-industries/zed --draft=false
      env:
        GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
  push_release_update_notification:
    needs:
    - create_draft_release
    - upload_release_assets
    - validate_release_assets
    - auto_release_preview
    - run_tests_mac
    - run_tests_linux
    - run_tests_windows
    - clippy_mac
    - clippy_linux
    - clippy_windows
    - check_scripts
    - bundle_linux_aarch64
    - bundle_linux_x86_64
    - bundle_mac_aarch64
    - bundle_mac_x86_64
    - bundle_windows_aarch64
    - bundle_windows_x86_64
    if: always()
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - id: generate-webhook-message
      name: release::generate_slack_message
      run: |
        MESSAGE=$(TAG="$GITHUB_REF_NAME"

        if [ "$DRAFT_RESULT" == "failure" ]; then
            echo "❌ Draft release creation failed for $TAG: $RUN_URL"
        else
            RELEASE_URL=$(gh release view "$TAG" --repo=zed-industries/zed --json url -q '.url')
            if [ "$UPLOAD_RESULT" == "failure" ]; then
                echo "❌ Release asset upload failed for $TAG: $RELEASE_URL"
            elif [ "$UPLOAD_RESULT" == "cancelled" ] || [ "$UPLOAD_RESULT" == "skipped" ]; then
                FAILED_JOBS=""
                if [ "$RESULT_RUN_TESTS_MAC" == "failure" ];then FAILED_JOBS="$FAILED_JOBS run_tests_mac"; fi
                if [ "$RESULT_RUN_TESTS_LINUX" == "failure" ];then FAILED_JOBS="$FAILED_JOBS run_tests_linux"; fi
                if [ "$RESULT_RUN_TESTS_WINDOWS" == "failure" ];then FAILED_JOBS="$FAILED_JOBS run_tests_windows"; fi
                if [ "$RESULT_CLIPPY_MAC" == "failure" ];then FAILED_JOBS="$FAILED_JOBS clippy_mac"; fi
                if [ "$RESULT_CLIPPY_LINUX" == "failure" ];then FAILED_JOBS="$FAILED_JOBS clippy_linux"; fi
                if [ "$RESULT_CLIPPY_WINDOWS" == "failure" ];then FAILED_JOBS="$FAILED_JOBS clippy_windows"; fi
                if [ "$RESULT_CHECK_SCRIPTS" == "failure" ];then FAILED_JOBS="$FAILED_JOBS check_scripts"; fi
                if [ "$RESULT_BUNDLE_LINUX_AARCH64" == "failure" ];then FAILED_JOBS="$FAILED_JOBS bundle_linux_aarch64"; fi
                if [ "$RESULT_BUNDLE_LINUX_X86_64" == "failure" ];then FAILED_JOBS="$FAILED_JOBS bundle_linux_x86_64"; fi
                if [ "$RESULT_BUNDLE_MAC_AARCH64" == "failure" ];then FAILED_JOBS="$FAILED_JOBS bundle_mac_aarch64"; fi
                if [ "$RESULT_BUNDLE_MAC_X86_64" == "failure" ];then FAILED_JOBS="$FAILED_JOBS bundle_mac_x86_64"; fi
                if [ "$RESULT_BUNDLE_WINDOWS_AARCH64" == "failure" ];then FAILED_JOBS="$FAILED_JOBS bundle_windows_aarch64"; fi
                if [ "$RESULT_BUNDLE_WINDOWS_X86_64" == "failure" ];then FAILED_JOBS="$FAILED_JOBS bundle_windows_x86_64"; fi
                FAILED_JOBS=$(echo "$FAILED_JOBS" | xargs)
                if [ "$UPLOAD_RESULT" == "cancelled" ]; then
                    if [ -n "$FAILED_JOBS" ]; then
                        echo "❌ Release job for $TAG was cancelled, most likely because tests \`$FAILED_JOBS\` failed: $RUN_URL"
                    else
                        echo "❌ Release job for $TAG was cancelled: $RUN_URL"
                    fi
                else
                    if [ -n "$FAILED_JOBS" ]; then
                        echo "❌ Tests \`$FAILED_JOBS\` for $TAG failed: $RUN_URL"
                    else
                        echo "❌ Tests for $TAG failed: $RUN_URL"
                    fi
                fi
            elif [ "$VALIDATE_RESULT" == "failure" ]; then
                echo "❌ Release asset validation failed for $TAG (missing assets): $RUN_URL"
            elif [ "$AUTO_RELEASE_RESULT" == "success" ]; then
                echo "✅ Release $TAG was auto-released successfully: $RELEASE_URL"
            elif [ "$AUTO_RELEASE_RESULT" == "failure" ]; then
                echo "❌ Auto release failed for $TAG: $RUN_URL"
            else
                echo "👀 Release $TAG sitting freshly baked in the oven and waiting to be published: $RELEASE_URL"
            fi
        fi
        )
        echo "message=$MESSAGE" >> "$GITHUB_OUTPUT"
      env:
        GH_TOKEN: ${{ github.token }}
        DRAFT_RESULT: ${{ needs.create_draft_release.result }}
        UPLOAD_RESULT: ${{ needs.upload_release_assets.result }}
        VALIDATE_RESULT: ${{ needs.validate_release_assets.result }}
        AUTO_RELEASE_RESULT: ${{ needs.auto_release_preview.result }}
        RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
        RESULT_RUN_TESTS_MAC: ${{ needs.run_tests_mac.result }}
        RESULT_RUN_TESTS_LINUX: ${{ needs.run_tests_linux.result }}
        RESULT_RUN_TESTS_WINDOWS: ${{ needs.run_tests_windows.result }}
        RESULT_CLIPPY_MAC: ${{ needs.clippy_mac.result }}
        RESULT_CLIPPY_LINUX: ${{ needs.clippy_linux.result }}
        RESULT_CLIPPY_WINDOWS: ${{ needs.clippy_windows.result }}
        RESULT_CHECK_SCRIPTS: ${{ needs.check_scripts.result }}
        RESULT_BUNDLE_LINUX_AARCH64: ${{ needs.bundle_linux_aarch64.result }}
        RESULT_BUNDLE_LINUX_X86_64: ${{ needs.bundle_linux_x86_64.result }}
        RESULT_BUNDLE_MAC_AARCH64: ${{ needs.bundle_mac_aarch64.result }}
        RESULT_BUNDLE_MAC_X86_64: ${{ needs.bundle_mac_x86_64.result }}
        RESULT_BUNDLE_WINDOWS_AARCH64: ${{ needs.bundle_windows_aarch64.result }}
        RESULT_BUNDLE_WINDOWS_X86_64: ${{ needs.bundle_windows_x86_64.result }}
    - name: release::send_slack_message
      run: 'curl -X POST -H ''Content-type: application/json'' --data "$(jq -n --arg text "$SLACK_MESSAGE" ''{"text": $text}'')" "$SLACK_WEBHOOK"'
      env:
        SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_WORKFLOW_FAILURES }}
        SLACK_MESSAGE: ${{ steps.generate-webhook-message.outputs.message }}
concurrency:
  group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
  cancel-in-progress: true
defaults:
  run:
    shell: bash -euxo pipefail {0}
release_nightly .github/workflows/release_nightly.yml
Triggers
push, schedule
Runs on
namespace-profile-mac-large, self-32vcpu-windows-2022, self-32vcpu-windows-2022, namespace-profile-8x32-ubuntu-2004-arm-m4, namespace-profile-32x64-ubuntu-2004, namespace-profile-mac-large, namespace-profile-mac-large, self-32vcpu-windows-2022, self-32vcpu-windows-2022, namespace-profile-32x64-ubuntu-2004, namespace-profile-mac-large, namespace-profile-4x8-ubuntu-2204, namespace-profile-2x4-ubuntu-2404
Jobs
check_style, run_tests_windows, clippy_windows, bundle_linux_aarch64, bundle_linux_x86_64, bundle_mac_aarch64, bundle_mac_x86_64, bundle_windows_aarch64, bundle_windows_x86_64, build_nix_linux_x86_64, build_nix_mac_aarch64, update_nightly_tag, notify_on_failure
Actions
matbour/setup-sentry-cli, matbour/setup-sentry-cli, matbour/setup-sentry-cli, matbour/setup-sentry-cli, matbour/setup-sentry-cli, matbour/setup-sentry-cli, namespacelabs/nscloud-cache-action, cachix/install-nix-action, cachix/cachix-action, namespacelabs/nscloud-cache-action, cachix/install-nix-action, cachix/cachix-action, getsentry/action-release
Commands
  • cargo fmt --all -- --check
  • ./script/clippy
  • New-Item -ItemType Directory -Path "./../.cargo" -Force Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
  • ./script/clear-target-dir-if-larger-than.ps1 250
  • ./script/setup-sccache.ps1
  • cargo nextest run --workspace --no-fail-fast --no-tests=warn
  • if ($env:RUSTC_WRAPPER) { & $env:RUSTC_WRAPPER --show-stats }; exit 0
  • Remove-Item -Recurse -Path "./../.cargo" -Force -ErrorAction SilentlyContinue
View raw YAML
# Generated from xtask::workflows::release_nightly
# Rebuild with `cargo xtask workflows`.
name: release_nightly
env:
  CARGO_TERM_COLOR: always
  RUST_BACKTRACE: '1'
on:
  push:
    tags:
    - nightly
  schedule:
  - cron: 0 7 * * *
jobs:
  check_style:
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: namespace-profile-mac-large
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
        fetch-depth: 0
    - name: steps::cargo_fmt
      run: cargo fmt --all -- --check
    - name: ./script/clippy
      run: ./script/clippy
    timeout-minutes: 60
  run_tests_windows:
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: self-32vcpu-windows-2022
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_cargo_config
      run: |
        New-Item -ItemType Directory -Path "./../.cargo" -Force
        Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
      shell: pwsh
    - name: steps::setup_node
      uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
      with:
        node-version: '20'
    - name: steps::clear_target_dir_if_large
      run: ./script/clear-target-dir-if-larger-than.ps1 250
      shell: pwsh
    - name: steps::setup_sccache
      run: ./script/setup-sccache.ps1
      shell: pwsh
      env:
        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
        SCCACHE_BUCKET: sccache-zed
    - name: steps::cargo_nextest
      run: cargo nextest run --workspace --no-fail-fast --no-tests=warn
      shell: pwsh
    - name: steps::show_sccache_stats
      run: if ($env:RUSTC_WRAPPER) { & $env:RUSTC_WRAPPER --show-stats }; exit 0
      shell: pwsh
    - name: steps::cleanup_cargo_config
      if: always()
      run: |
        Remove-Item -Recurse -Path "./../.cargo" -Force -ErrorAction SilentlyContinue
      shell: pwsh
    timeout-minutes: 60
  clippy_windows:
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: self-32vcpu-windows-2022
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_cargo_config
      run: |
        New-Item -ItemType Directory -Path "./../.cargo" -Force
        Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
      shell: pwsh
    - name: steps::setup_sccache
      run: ./script/setup-sccache.ps1
      shell: pwsh
      env:
        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
        SCCACHE_BUCKET: sccache-zed
    - name: steps::clippy
      run: ./script/clippy.ps1
      shell: pwsh
    - name: steps::show_sccache_stats
      run: if ($env:RUSTC_WRAPPER) { & $env:RUSTC_WRAPPER --show-stats }; exit 0
      shell: pwsh
    timeout-minutes: 60
  bundle_linux_aarch64:
    needs:
    - check_style
    - run_tests_windows
    - clippy_windows
    runs-on: namespace-profile-8x32-ubuntu-2004-arm-m4
    env:
      CARGO_INCREMENTAL: 0
      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
      CC: clang-18
      CXX: clang++-18
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: run_bundling::set_release_channel_to_nightly
      run: |
        set -eu
        version=$(git rev-parse --short HEAD)
        echo "Publishing version: ${version} on release channel nightly"
        echo "nightly" > crates/zed/RELEASE_CHANNEL
    - name: steps::setup_sentry
      uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
      with:
        token: ${{ secrets.SENTRY_AUTH_TOKEN }}
    - name: steps::setup_linux
      run: ./script/linux
    - name: steps::download_wasi_sdk
      run: ./script/download-wasi-sdk
    - name: ./script/bundle-linux
      run: ./script/bundle-linux
    - name: '@actions/upload-artifact zed-linux-aarch64.tar.gz'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: zed-linux-aarch64.tar.gz
        path: target/release/zed-linux-aarch64.tar.gz
        if-no-files-found: error
    - name: '@actions/upload-artifact zed-remote-server-linux-aarch64.gz'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: zed-remote-server-linux-aarch64.gz
        path: target/zed-remote-server-linux-aarch64.gz
        if-no-files-found: error
    timeout-minutes: 60
  bundle_linux_x86_64:
    needs:
    - check_style
    - run_tests_windows
    - clippy_windows
    runs-on: namespace-profile-32x64-ubuntu-2004
    env:
      CARGO_INCREMENTAL: 0
      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
      CC: clang-18
      CXX: clang++-18
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: run_bundling::set_release_channel_to_nightly
      run: |
        set -eu
        version=$(git rev-parse --short HEAD)
        echo "Publishing version: ${version} on release channel nightly"
        echo "nightly" > crates/zed/RELEASE_CHANNEL
    - name: steps::setup_sentry
      uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
      with:
        token: ${{ secrets.SENTRY_AUTH_TOKEN }}
    - name: steps::setup_linux
      run: ./script/linux
    - name: steps::download_wasi_sdk
      run: ./script/download-wasi-sdk
    - name: ./script/bundle-linux
      run: ./script/bundle-linux
    - name: '@actions/upload-artifact zed-linux-x86_64.tar.gz'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: zed-linux-x86_64.tar.gz
        path: target/release/zed-linux-x86_64.tar.gz
        if-no-files-found: error
    - name: '@actions/upload-artifact zed-remote-server-linux-x86_64.gz'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: zed-remote-server-linux-x86_64.gz
        path: target/zed-remote-server-linux-x86_64.gz
        if-no-files-found: error
    timeout-minutes: 60
  bundle_mac_aarch64:
    needs:
    - check_style
    - run_tests_windows
    - clippy_windows
    runs-on: namespace-profile-mac-large
    env:
      CARGO_INCREMENTAL: 0
      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
      MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
      MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
      APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
      APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
      APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: run_bundling::set_release_channel_to_nightly
      run: |
        set -eu
        version=$(git rev-parse --short HEAD)
        echo "Publishing version: ${version} on release channel nightly"
        echo "nightly" > crates/zed/RELEASE_CHANNEL
    - name: steps::setup_node
      uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
      with:
        node-version: '20'
    - name: steps::setup_sentry
      uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
      with:
        token: ${{ secrets.SENTRY_AUTH_TOKEN }}
    - name: steps::clear_target_dir_if_large
      run: ./script/clear-target-dir-if-larger-than 300
    - name: run_bundling::bundle_mac::bundle_mac
      run: ./script/bundle-mac aarch64-apple-darwin
    - name: '@actions/upload-artifact Zed-aarch64.dmg'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: Zed-aarch64.dmg
        path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
        if-no-files-found: error
    - name: '@actions/upload-artifact zed-remote-server-macos-aarch64.gz'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: zed-remote-server-macos-aarch64.gz
        path: target/zed-remote-server-macos-aarch64.gz
        if-no-files-found: error
    timeout-minutes: 60
  bundle_mac_x86_64:
    needs:
    - check_style
    - run_tests_windows
    - clippy_windows
    runs-on: namespace-profile-mac-large
    env:
      CARGO_INCREMENTAL: 0
      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
      MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
      MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
      APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
      APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
      APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: run_bundling::set_release_channel_to_nightly
      run: |
        set -eu
        version=$(git rev-parse --short HEAD)
        echo "Publishing version: ${version} on release channel nightly"
        echo "nightly" > crates/zed/RELEASE_CHANNEL
    - name: steps::setup_node
      uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
      with:
        node-version: '20'
    - name: steps::setup_sentry
      uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
      with:
        token: ${{ secrets.SENTRY_AUTH_TOKEN }}
    - name: steps::clear_target_dir_if_large
      run: ./script/clear-target-dir-if-larger-than 300
    - name: run_bundling::bundle_mac::bundle_mac
      run: ./script/bundle-mac x86_64-apple-darwin
    - name: '@actions/upload-artifact Zed-x86_64.dmg'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: Zed-x86_64.dmg
        path: target/x86_64-apple-darwin/release/Zed-x86_64.dmg
        if-no-files-found: error
    - name: '@actions/upload-artifact zed-remote-server-macos-x86_64.gz'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: zed-remote-server-macos-x86_64.gz
        path: target/zed-remote-server-macos-x86_64.gz
        if-no-files-found: error
    timeout-minutes: 60
  bundle_windows_aarch64:
    needs:
    - check_style
    - run_tests_windows
    - clippy_windows
    runs-on: self-32vcpu-windows-2022
    env:
      CARGO_INCREMENTAL: 0
      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
      AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
      AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
      AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
      ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
      CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
      ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
      FILE_DIGEST: SHA256
      TIMESTAMP_DIGEST: SHA256
      TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: run_bundling::set_release_channel_to_nightly
      run: |
        $ErrorActionPreference = "Stop"
        $version = git rev-parse --short HEAD
        Write-Host "Publishing version: $version on release channel nightly"
        "nightly" | Set-Content -Path "crates/zed/RELEASE_CHANNEL"
      shell: pwsh
      working-directory: ${{ env.ZED_WORKSPACE }}
    - name: steps::setup_sentry
      uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
      with:
        token: ${{ secrets.SENTRY_AUTH_TOKEN }}
    - name: run_bundling::bundle_windows::bundle_windows
      run: script/bundle-windows.ps1 -Architecture aarch64
      shell: pwsh
      working-directory: ${{ env.ZED_WORKSPACE }}
    - name: '@actions/upload-artifact Zed-aarch64.exe'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: Zed-aarch64.exe
        path: target/Zed-aarch64.exe
        if-no-files-found: error
    - name: '@actions/upload-artifact zed-remote-server-windows-aarch64.zip'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: zed-remote-server-windows-aarch64.zip
        path: target/zed-remote-server-windows-aarch64.zip
        if-no-files-found: error
    timeout-minutes: 60
  bundle_windows_x86_64:
    needs:
    - check_style
    - run_tests_windows
    - clippy_windows
    runs-on: self-32vcpu-windows-2022
    env:
      CARGO_INCREMENTAL: 0
      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
      AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
      AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
      AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
      ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
      CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
      ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
      FILE_DIGEST: SHA256
      TIMESTAMP_DIGEST: SHA256
      TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: run_bundling::set_release_channel_to_nightly
      run: |
        $ErrorActionPreference = "Stop"
        $version = git rev-parse --short HEAD
        Write-Host "Publishing version: $version on release channel nightly"
        "nightly" | Set-Content -Path "crates/zed/RELEASE_CHANNEL"
      shell: pwsh
      working-directory: ${{ env.ZED_WORKSPACE }}
    - name: steps::setup_sentry
      uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
      with:
        token: ${{ secrets.SENTRY_AUTH_TOKEN }}
    - name: run_bundling::bundle_windows::bundle_windows
      run: script/bundle-windows.ps1 -Architecture x86_64
      shell: pwsh
      working-directory: ${{ env.ZED_WORKSPACE }}
    - name: '@actions/upload-artifact Zed-x86_64.exe'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: Zed-x86_64.exe
        path: target/Zed-x86_64.exe
        if-no-files-found: error
    - name: '@actions/upload-artifact zed-remote-server-windows-x86_64.zip'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: zed-remote-server-windows-x86_64.zip
        path: target/zed-remote-server-windows-x86_64.zip
        if-no-files-found: error
    timeout-minutes: 60
  build_nix_linux_x86_64:
    needs:
    - check_style
    - run_tests_windows
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: namespace-profile-32x64-ubuntu-2004
    env:
      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
      ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
      GIT_LFS_SKIP_SMUDGE: '1'
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::cache_nix_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: nix
    - name: nix_build::build_nix::install_nix
      uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f
      with:
        github_access_token: ${{ secrets.GITHUB_TOKEN }}
    - name: nix_build::build_nix::cachix_action
      uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad
      with:
        name: zed
        authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
        cachixArgs: -v
    - name: nix_build::build_nix::build
      run: nix build .#default -L --accept-flake-config
    timeout-minutes: 60
    continue-on-error: true
  build_nix_mac_aarch64:
    needs:
    - check_style
    - run_tests_windows
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: namespace-profile-mac-large
    env:
      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
      ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
      GIT_LFS_SKIP_SMUDGE: '1'
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::cache_nix_store_macos
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        path: ~/nix-cache
    - name: nix_build::build_nix::install_nix
      uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f
      with:
        github_access_token: ${{ secrets.GITHUB_TOKEN }}
    - name: nix_build::build_nix::configure_local_nix_cache
      run: |
        mkdir -p ~/nix-cache
        echo "extra-substituters = file://$HOME/nix-cache?priority=10" | sudo tee -a /etc/nix/nix.conf
        echo "require-sigs = false" | sudo tee -a /etc/nix/nix.conf
        sudo launchctl kickstart -k system/org.nixos.nix-daemon
    - name: nix_build::build_nix::cachix_action
      uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad
      with:
        name: zed
        authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
        cachixArgs: -v
    - name: nix_build::build_nix::build
      run: nix build .#default -L --accept-flake-config
    - name: nix_build::build_nix::export_to_local_nix_cache
      if: always()
      run: |
        if [ -L result ]; then
          echo "Copying build closure to local binary cache..."
          nix copy --to "file://$HOME/nix-cache" ./result || echo "Warning: nix copy to local cache failed"
        else
          echo "No build result found, skipping cache export."
        fi
    timeout-minutes: 60
    continue-on-error: true
  update_nightly_tag:
    needs:
    - bundle_linux_aarch64
    - bundle_linux_x86_64
    - bundle_mac_aarch64
    - bundle_mac_x86_64
    - bundle_windows_aarch64
    - bundle_windows_x86_64
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: namespace-profile-4x8-ubuntu-2204
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
        fetch-depth: 0
    - name: release::download_workflow_artifacts
      uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
      with:
        path: ./artifacts/
    - name: ls -lR ./artifacts
      run: ls -lR ./artifacts
    - name: release::prep_release_artifacts
      run: |-
        mkdir -p release-artifacts/

        mv ./artifacts/Zed-aarch64.dmg/Zed-aarch64.dmg release-artifacts/Zed-aarch64.dmg
        mv ./artifacts/Zed-x86_64.dmg/Zed-x86_64.dmg release-artifacts/Zed-x86_64.dmg
        mv ./artifacts/zed-linux-aarch64.tar.gz/zed-linux-aarch64.tar.gz release-artifacts/zed-linux-aarch64.tar.gz
        mv ./artifacts/zed-linux-x86_64.tar.gz/zed-linux-x86_64.tar.gz release-artifacts/zed-linux-x86_64.tar.gz
        mv ./artifacts/Zed-x86_64.exe/Zed-x86_64.exe release-artifacts/Zed-x86_64.exe
        mv ./artifacts/Zed-aarch64.exe/Zed-aarch64.exe release-artifacts/Zed-aarch64.exe
        mv ./artifacts/zed-remote-server-macos-aarch64.gz/zed-remote-server-macos-aarch64.gz release-artifacts/zed-remote-server-macos-aarch64.gz
        mv ./artifacts/zed-remote-server-macos-x86_64.gz/zed-remote-server-macos-x86_64.gz release-artifacts/zed-remote-server-macos-x86_64.gz
        mv ./artifacts/zed-remote-server-linux-aarch64.gz/zed-remote-server-linux-aarch64.gz release-artifacts/zed-remote-server-linux-aarch64.gz
        mv ./artifacts/zed-remote-server-linux-x86_64.gz/zed-remote-server-linux-x86_64.gz release-artifacts/zed-remote-server-linux-x86_64.gz
        mv ./artifacts/zed-remote-server-windows-aarch64.zip/zed-remote-server-windows-aarch64.zip release-artifacts/zed-remote-server-windows-aarch64.zip
        mv ./artifacts/zed-remote-server-windows-x86_64.zip/zed-remote-server-windows-x86_64.zip release-artifacts/zed-remote-server-windows-x86_64.zip
    - name: ./script/upload-nightly
      run: ./script/upload-nightly
      env:
        DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
        DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
    - name: release_nightly::update_nightly_tag_job::update_nightly_tag
      run: |
        if [ "$(git rev-parse nightly)" = "$(git rev-parse HEAD)" ]; then
          echo "Nightly tag already points to current commit. Skipping tagging."
          exit 0
        fi
        git config user.name github-actions
        git config user.email github-actions@github.com
        git tag -f nightly
        git push origin nightly --force
    - name: release::create_sentry_release
      uses: getsentry/action-release@526942b68292201ac6bbb99b9a0747d4abee354c
      with:
        environment: production
      env:
        SENTRY_ORG: zed-dev
        SENTRY_PROJECT: zed
        SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
    timeout-minutes: 60
  notify_on_failure:
    needs:
    - bundle_linux_aarch64
    - bundle_linux_x86_64
    - bundle_mac_aarch64
    - bundle_mac_x86_64
    - bundle_windows_aarch64
    - bundle_windows_x86_64
    if: failure()
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - name: release::send_slack_message
      run: 'curl -X POST -H ''Content-type: application/json'' --data "$(jq -n --arg text "$SLACK_MESSAGE" ''{"text": $text}'')" "$SLACK_WEBHOOK"'
      env:
        SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_WORKFLOW_FAILURES }}
        SLACK_MESSAGE: '❌ ${{ github.workflow }} failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
defaults:
  run:
    shell: bash -euxo pipefail {0}
run_agent_evals .github/workflows/run_agent_evals.yml
Triggers
workflow_dispatch
Runs on
namespace-profile-16x32-ubuntu-2204
Jobs
agent_evals
Actions
namespacelabs/nscloud-cache-action
Commands
  • ./script/linux
  • ./script/download-wasi-sdk
  • mkdir -p ./../.cargo cp ./.cargo/ci-config.toml ./../.cargo/config.toml
  • ./script/setup-sccache
  • cargo build --package=eval
  • cargo run --package=eval -- --repetitions=8 --concurrency=1 --model "${MODEL_NAME}"
  • sccache --show-stats || true
  • rm -rf ./../.cargo
View raw YAML
# Generated from xtask::workflows::run_agent_evals
# Rebuild with `cargo xtask workflows`.
name: run_agent_evals
env:
  CARGO_TERM_COLOR: always
  CARGO_INCREMENTAL: '0'
  RUST_BACKTRACE: '1'
  ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
  OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
  GOOGLE_AI_API_KEY: ${{ secrets.GOOGLE_AI_API_KEY }}
  GOOGLE_CLOUD_PROJECT: ${{ secrets.GOOGLE_CLOUD_PROJECT }}
  ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
  ZED_EVAL_TELEMETRY: '1'
  MODEL_NAME: ${{ inputs.model_name }}
on:
  workflow_dispatch:
    inputs:
      model_name:
        description: model_name
        required: true
        type: string
jobs:
  agent_evals:
    runs-on: namespace-profile-16x32-ubuntu-2204
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: steps::setup_linux
      run: ./script/linux
    - name: steps::download_wasi_sdk
      run: ./script/download-wasi-sdk
    - name: steps::setup_cargo_config
      run: |
        mkdir -p ./../.cargo
        cp ./.cargo/ci-config.toml ./../.cargo/config.toml
    - name: steps::setup_sccache
      run: ./script/setup-sccache
      env:
        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
        SCCACHE_BUCKET: sccache-zed
    - name: cargo build --package=eval
      run: cargo build --package=eval
    - name: run_agent_evals::agent_evals::run_eval
      run: cargo run --package=eval -- --repetitions=8 --concurrency=1 --model "${MODEL_NAME}"
      env:
        ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        GOOGLE_AI_API_KEY: ${{ secrets.GOOGLE_AI_API_KEY }}
        GOOGLE_CLOUD_PROJECT: ${{ secrets.GOOGLE_CLOUD_PROJECT }}
    - name: steps::show_sccache_stats
      run: sccache --show-stats || true
    - name: steps::cleanup_cargo_config
      if: always()
      run: |
        rm -rf ./../.cargo
    timeout-minutes: 600
concurrency:
  group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
  cancel-in-progress: true
defaults:
  run:
    shell: bash -euxo pipefail {0}
run_bundling .github/workflows/run_bundling.yml
Triggers
pull_request
Runs on
namespace-profile-8x32-ubuntu-2004-arm-m4, namespace-profile-32x64-ubuntu-2004, namespace-profile-mac-large, namespace-profile-mac-large, self-32vcpu-windows-2022, self-32vcpu-windows-2022, namespace-profile-32x64-ubuntu-2004, namespace-profile-mac-large
Jobs
bundle_linux_aarch64, bundle_linux_x86_64, bundle_mac_aarch64, bundle_mac_x86_64, bundle_windows_aarch64, bundle_windows_x86_64, build_nix_linux_x86_64, build_nix_mac_aarch64
Actions
matbour/setup-sentry-cli, matbour/setup-sentry-cli, matbour/setup-sentry-cli, matbour/setup-sentry-cli, matbour/setup-sentry-cli, matbour/setup-sentry-cli, namespacelabs/nscloud-cache-action, cachix/install-nix-action, cachix/cachix-action, namespacelabs/nscloud-cache-action, cachix/install-nix-action, cachix/cachix-action
Commands
  • ./script/linux
  • ./script/download-wasi-sdk
  • ./script/bundle-linux
  • ./script/linux
  • ./script/download-wasi-sdk
  • ./script/bundle-linux
  • ./script/clear-target-dir-if-larger-than 300
  • ./script/bundle-mac aarch64-apple-darwin
View raw YAML
# Generated from xtask::workflows::run_bundling
# Rebuild with `cargo xtask workflows`.
name: run_bundling
env:
  CARGO_TERM_COLOR: always
  RUST_BACKTRACE: '1'
on:
  pull_request:
    types:
    - labeled
    - synchronize
jobs:
  bundle_linux_aarch64:
    if: |-
      (github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
      (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
    runs-on: namespace-profile-8x32-ubuntu-2004-arm-m4
    env:
      CARGO_INCREMENTAL: 0
      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
      CC: clang-18
      CXX: clang++-18
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_sentry
      uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
      with:
        token: ${{ secrets.SENTRY_AUTH_TOKEN }}
    - name: steps::setup_linux
      run: ./script/linux
    - name: steps::download_wasi_sdk
      run: ./script/download-wasi-sdk
    - name: ./script/bundle-linux
      run: ./script/bundle-linux
    - name: '@actions/upload-artifact zed-linux-aarch64.tar.gz'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: zed-linux-aarch64.tar.gz
        path: target/release/zed-linux-aarch64.tar.gz
        if-no-files-found: error
    - name: '@actions/upload-artifact zed-remote-server-linux-aarch64.gz'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: zed-remote-server-linux-aarch64.gz
        path: target/zed-remote-server-linux-aarch64.gz
        if-no-files-found: error
    timeout-minutes: 60
  bundle_linux_x86_64:
    if: |-
      (github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
      (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
    runs-on: namespace-profile-32x64-ubuntu-2004
    env:
      CARGO_INCREMENTAL: 0
      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
      CC: clang-18
      CXX: clang++-18
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_sentry
      uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
      with:
        token: ${{ secrets.SENTRY_AUTH_TOKEN }}
    - name: steps::setup_linux
      run: ./script/linux
    - name: steps::download_wasi_sdk
      run: ./script/download-wasi-sdk
    - name: ./script/bundle-linux
      run: ./script/bundle-linux
    - name: '@actions/upload-artifact zed-linux-x86_64.tar.gz'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: zed-linux-x86_64.tar.gz
        path: target/release/zed-linux-x86_64.tar.gz
        if-no-files-found: error
    - name: '@actions/upload-artifact zed-remote-server-linux-x86_64.gz'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: zed-remote-server-linux-x86_64.gz
        path: target/zed-remote-server-linux-x86_64.gz
        if-no-files-found: error
    timeout-minutes: 60
  bundle_mac_aarch64:
    if: |-
      (github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
      (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
    runs-on: namespace-profile-mac-large
    env:
      CARGO_INCREMENTAL: 0
      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
      MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
      MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
      APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
      APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
      APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_node
      uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
      with:
        node-version: '20'
    - name: steps::setup_sentry
      uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
      with:
        token: ${{ secrets.SENTRY_AUTH_TOKEN }}
    - name: steps::clear_target_dir_if_large
      run: ./script/clear-target-dir-if-larger-than 300
    - name: run_bundling::bundle_mac::bundle_mac
      run: ./script/bundle-mac aarch64-apple-darwin
    - name: '@actions/upload-artifact Zed-aarch64.dmg'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: Zed-aarch64.dmg
        path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
        if-no-files-found: error
    - name: '@actions/upload-artifact zed-remote-server-macos-aarch64.gz'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: zed-remote-server-macos-aarch64.gz
        path: target/zed-remote-server-macos-aarch64.gz
        if-no-files-found: error
    timeout-minutes: 60
  bundle_mac_x86_64:
    if: |-
      (github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
      (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
    runs-on: namespace-profile-mac-large
    env:
      CARGO_INCREMENTAL: 0
      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
      MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
      MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
      APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
      APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
      APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_node
      uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
      with:
        node-version: '20'
    - name: steps::setup_sentry
      uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
      with:
        token: ${{ secrets.SENTRY_AUTH_TOKEN }}
    - name: steps::clear_target_dir_if_large
      run: ./script/clear-target-dir-if-larger-than 300
    - name: run_bundling::bundle_mac::bundle_mac
      run: ./script/bundle-mac x86_64-apple-darwin
    - name: '@actions/upload-artifact Zed-x86_64.dmg'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: Zed-x86_64.dmg
        path: target/x86_64-apple-darwin/release/Zed-x86_64.dmg
        if-no-files-found: error
    - name: '@actions/upload-artifact zed-remote-server-macos-x86_64.gz'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: zed-remote-server-macos-x86_64.gz
        path: target/zed-remote-server-macos-x86_64.gz
        if-no-files-found: error
    timeout-minutes: 60
  bundle_windows_aarch64:
    if: |-
      (github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
      (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
    runs-on: self-32vcpu-windows-2022
    env:
      CARGO_INCREMENTAL: 0
      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
      AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
      AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
      AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
      ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
      CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
      ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
      FILE_DIGEST: SHA256
      TIMESTAMP_DIGEST: SHA256
      TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_sentry
      uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
      with:
        token: ${{ secrets.SENTRY_AUTH_TOKEN }}
    - name: run_bundling::bundle_windows::bundle_windows
      run: script/bundle-windows.ps1 -Architecture aarch64
      shell: pwsh
      working-directory: ${{ env.ZED_WORKSPACE }}
    - name: '@actions/upload-artifact Zed-aarch64.exe'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: Zed-aarch64.exe
        path: target/Zed-aarch64.exe
        if-no-files-found: error
    - name: '@actions/upload-artifact zed-remote-server-windows-aarch64.zip'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: zed-remote-server-windows-aarch64.zip
        path: target/zed-remote-server-windows-aarch64.zip
        if-no-files-found: error
    timeout-minutes: 60
  bundle_windows_x86_64:
    if: |-
      (github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
      (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
    runs-on: self-32vcpu-windows-2022
    env:
      CARGO_INCREMENTAL: 0
      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
      AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
      AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
      AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
      ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
      CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
      ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
      FILE_DIGEST: SHA256
      TIMESTAMP_DIGEST: SHA256
      TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_sentry
      uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
      with:
        token: ${{ secrets.SENTRY_AUTH_TOKEN }}
    - name: run_bundling::bundle_windows::bundle_windows
      run: script/bundle-windows.ps1 -Architecture x86_64
      shell: pwsh
      working-directory: ${{ env.ZED_WORKSPACE }}
    - name: '@actions/upload-artifact Zed-x86_64.exe'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: Zed-x86_64.exe
        path: target/Zed-x86_64.exe
        if-no-files-found: error
    - name: '@actions/upload-artifact zed-remote-server-windows-x86_64.zip'
      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
      with:
        name: zed-remote-server-windows-x86_64.zip
        path: target/zed-remote-server-windows-x86_64.zip
        if-no-files-found: error
    timeout-minutes: 60
  build_nix_linux_x86_64:
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions') && ((github.event.action == 'labeled' && github.event.label.name == 'run-bundling') || (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling')))
    runs-on: namespace-profile-32x64-ubuntu-2004
    env:
      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
      ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
      GIT_LFS_SKIP_SMUDGE: '1'
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::cache_nix_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: nix
    - name: nix_build::build_nix::install_nix
      uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f
      with:
        github_access_token: ${{ secrets.GITHUB_TOKEN }}
    - name: nix_build::build_nix::cachix_action
      uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad
      with:
        name: zed
        authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
        cachixArgs: -v
        pushFilter: -zed-editor-[0-9.]*
    - name: nix_build::build_nix::build
      run: nix build .#default -L --accept-flake-config
    timeout-minutes: 60
    continue-on-error: true
  build_nix_mac_aarch64:
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions') && ((github.event.action == 'labeled' && github.event.label.name == 'run-bundling') || (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling')))
    runs-on: namespace-profile-mac-large
    env:
      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
      ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
      GIT_LFS_SKIP_SMUDGE: '1'
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::cache_nix_store_macos
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        path: ~/nix-cache
    - name: nix_build::build_nix::install_nix
      uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f
      with:
        github_access_token: ${{ secrets.GITHUB_TOKEN }}
    - name: nix_build::build_nix::configure_local_nix_cache
      run: |
        mkdir -p ~/nix-cache
        echo "extra-substituters = file://$HOME/nix-cache?priority=10" | sudo tee -a /etc/nix/nix.conf
        echo "require-sigs = false" | sudo tee -a /etc/nix/nix.conf
        sudo launchctl kickstart -k system/org.nixos.nix-daemon
    - name: nix_build::build_nix::cachix_action
      uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad
      with:
        name: zed
        authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
        cachixArgs: -v
        pushFilter: -zed-editor-[0-9.]*
    - name: nix_build::build_nix::build
      run: nix build .#default -L --accept-flake-config
    - name: nix_build::build_nix::export_to_local_nix_cache
      if: always()
      run: |
        if [ -L result ]; then
          echo "Copying build closure to local binary cache..."
          nix copy --to "file://$HOME/nix-cache" ./result || echo "Warning: nix copy to local cache failed"
        else
          echo "No build result found, skipping cache export."
        fi
    timeout-minutes: 60
    continue-on-error: true
concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
  cancel-in-progress: true
defaults:
  run:
    shell: bash -euxo pipefail {0}
run_cron_unit_evals matrix .github/workflows/run_cron_unit_evals.yml
Triggers
workflow_dispatch
Runs on
namespace-profile-16x32-ubuntu-2204
Jobs
cron_unit_evals
Matrix
model→ anthropic/claude-opus-4-5-latest, anthropic/claude-sonnet-4-5-latest, google/gemini-3.1-pro, openai/gpt-5
Actions
namespacelabs/nscloud-cache-action, taiki-e/install-action, slackapi/slack-github-action
Commands
  • mkdir -p ./../.cargo cp ./.cargo/ci-config.toml ./../.cargo/config.toml
  • ./script/linux
  • ./script/download-wasi-sdk
  • ./script/clear-target-dir-if-larger-than 250
  • ./script/setup-sccache
  • ./script/run-unit-evals
  • sccache --show-stats || true
  • rm -rf ./../.cargo
View raw YAML
# Generated from xtask::workflows::run_cron_unit_evals
# Rebuild with `cargo xtask workflows`.
name: run_cron_unit_evals
env:
  CARGO_TERM_COLOR: always
  CARGO_INCREMENTAL: '0'
  RUST_BACKTRACE: '1'
  ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
on:
  workflow_dispatch: {}
jobs:
  cron_unit_evals:
    runs-on: namespace-profile-16x32-ubuntu-2204
    strategy:
      matrix:
        model:
        - anthropic/claude-sonnet-4-5-latest
        - anthropic/claude-opus-4-5-latest
        - google/gemini-3.1-pro
        - openai/gpt-5
      fail-fast: false
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_cargo_config
      run: |
        mkdir -p ./../.cargo
        cp ./.cargo/ci-config.toml ./../.cargo/config.toml
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: steps::setup_linux
      run: ./script/linux
    - name: steps::download_wasi_sdk
      run: ./script/download-wasi-sdk
    - name: steps::cargo_install_nextest
      uses: taiki-e/install-action@921e2c9f7148d7ba14cd819f417db338f63e733c
    - name: steps::clear_target_dir_if_large
      run: ./script/clear-target-dir-if-larger-than 250
    - name: steps::setup_sccache
      run: ./script/setup-sccache
      env:
        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
        SCCACHE_BUCKET: sccache-zed
    - name: ./script/run-unit-evals
      run: ./script/run-unit-evals
      env:
        ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        GOOGLE_AI_API_KEY: ${{ secrets.GOOGLE_AI_API_KEY }}
        GOOGLE_CLOUD_PROJECT: ${{ secrets.GOOGLE_CLOUD_PROJECT }}
        ZED_AGENT_MODEL: ${{ matrix.model }}
    - name: steps::show_sccache_stats
      run: sccache --show-stats || true
    - name: steps::cleanup_cargo_config
      if: always()
      run: |
        rm -rf ./../.cargo
    - name: run_agent_evals::cron_unit_evals::send_failure_to_slack
      if: ${{ failure() }}
      uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52
      with:
        method: chat.postMessage
        token: ${{ secrets.SLACK_APP_ZED_UNIT_EVALS_BOT_TOKEN }}
        payload: |
          channel: C04UDRNNJFQ
          text: "Unit Evals Failed: https://github.com/zed-industries/zed/actions/runs/${{ github.run_id }}"
concurrency:
  group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
  cancel-in-progress: true
defaults:
  run:
    shell: bash -euxo pipefail {0}
run_tests matrix .github/workflows/run_tests.yml
Triggers
pull_request, push
Runs on
namespace-profile-2x4-ubuntu-2404, namespace-profile-4x8-ubuntu-2204, self-32vcpu-windows-2022, namespace-profile-16x32-ubuntu-2204, namespace-profile-mac-large, namespace-profile-mac-large, self-32vcpu-windows-2022, namespace-profile-16x32-ubuntu-2204, namespace-profile-mac-large, namespace-profile-16x32-ubuntu-2204, namespace-profile-8x16-ubuntu-2204, namespace-profile-8x16-ubuntu-2204, namespace-profile-2x4-ubuntu-2404, namespace-profile-8x16-ubuntu-2204, namespace-profile-2x4-ubuntu-2404, namespace-profile-2x4-ubuntu-2404, namespace-profile-16x32-ubuntu-2204, namespace-profile-2x4-ubuntu-2404
Jobs
orchestrate, check_style, clippy_windows, clippy_linux, clippy_mac, clippy_mac_x86_64, run_tests_windows, run_tests_linux, run_tests_mac, doctests, check_workspace_binaries, check_wasm, check_dependencies, check_docs, check_licenses, check_scripts, check_postgres_and_protobuf_migrations, extension_tests, tests_pass
Matrix
extension→ ${{ fromJson(needs.orchestrate.outputs.changed_extensions) }}
Actions
namespacelabs/nscloud-cache-action, pnpm/action-setup, crate-ci/typos, dsaltares/fetch-gh-release-asset, namespacelabs/nscloud-cache-action, namespacelabs/nscloud-cache-action, namespacelabs/nscloud-cache-action, namespacelabs/nscloud-cache-action, taiki-e/install-action, namespacelabs/nscloud-cache-action, taiki-e/install-action, namespacelabs/nscloud-cache-action, namespacelabs/nscloud-cache-action, namespacelabs/nscloud-cache-action, namespacelabs/nscloud-cache-action, clechasseur/rs-cargo, clechasseur/rs-cargo, actions/dependency-review-action, namespacelabs/nscloud-cache-action, lycheeverse/lychee-action, peaceiris/actions-mdbook, lycheeverse/lychee-action, namespacelabs/nscloud-cache-action, namespacelabs/nscloud-cache-action, bufbuild/buf-setup-action, bufbuild/buf-breaking-action
Commands
  • set -euo pipefail if [ -z "$GITHUB_BASE_REF" ]; then echo "Not in a PR context (i.e., push to main/stable/preview)" COMPARE_REV="$(git rev-parse HEAD~1)" else echo "In a PR context comparing to pull_request.base.ref" git fetch origin "$GITHUB_BASE_REF" --depth=350 COMPARE_REV="$(git merge-base "origin/${GITHUB_BASE_REF}" HEAD)" fi CHANGED_FILES="$(git diff --name-only "$COMPARE_REV" "$GITHUB_SHA")" check_pattern() { local output_name="$1" local pattern="$2" local grep_arg="$3" echo "$CHANGED_FILES" | grep "$grep_arg" "$pattern" && \ echo "${output_name}=true" >> "$GITHUB_OUTPUT" || \ echo "${output_name}=false" >> "$GITHUB_OUTPUT" } # Check for changes that require full rebuild (no filter) # Direct pushes to main/stable/preview always run full suite if [ -z "$GITHUB_BASE_REF" ]; then echo "Not a PR, running full test suite" echo "changed_packages=" >> "$GITHUB_OUTPUT" elif echo "$CHANGED_FILES" | grep -qP '^(rust-toolchain\.toml|\.cargo/|\.github/|Cargo\.(toml|lock)$)'; then echo "Toolchain, cargo config, or root Cargo files changed, will run all tests" echo "changed_packages=" >> "$GITHUB_OUTPUT" else # Extract changed directories from file paths CHANGED_DIRS=$(echo "$CHANGED_FILES" | \ grep -oP '^(crates|tooling)/\K[^/]+' | \ sort -u || true) # Build directory-to-package mapping using cargo metadata DIR_TO_PKG=$(cargo metadata --format-version=1 --no-deps 2>/dev/null | \ jq -r '.packages[] | select(.manifest_path | test("crates/|tooling/")) | "\(.manifest_path | capture("(crates|tooling)/(?<dir>[^/]+)") | .dir)=\(.name)"') # Map directory names to package names FILE_CHANGED_PKGS="" for dir in $CHANGED_DIRS; do pkg=$(echo "$DIR_TO_PKG" | grep "^${dir}=" | cut -d= -f2 | head -1) if [ -n "$pkg" ]; then FILE_CHANGED_PKGS=$(printf '%s\n%s' "$FILE_CHANGED_PKGS" "$pkg") else # Fall back to directory name if no mapping found FILE_CHANGED_PKGS=$(printf '%s\n%s' "$FILE_CHANGED_PKGS" "$dir") fi done FILE_CHANGED_PKGS=$(echo "$FILE_CHANGED_PKGS" | grep -v '^$' | sort -u || true) # If assets/ changed, add crates that depend on those assets if echo "$CHANGED_FILES" | grep -qP '^assets/'; then FILE_CHANGED_PKGS=$(printf '%s\n%s\n%s\n%s' "$FILE_CHANGED_PKGS" "settings" "storybook" "assets" | sort -u) fi # Combine all changed packages ALL_CHANGED_PKGS=$(echo "$FILE_CHANGED_PKGS" | grep -v '^$' || true) if [ -z "$ALL_CHANGED_PKGS" ]; then echo "No package changes detected, will run all tests" echo "changed_packages=" >> "$GITHUB_OUTPUT" else # Build nextest filterset with rdeps for each package FILTERSET=$(echo "$ALL_CHANGED_PKGS" | \ sed 's/.*/rdeps(&)/' | \ tr '\n' '|' | \ sed 's/|$//') echo "Changed packages filterset: $FILTERSET" echo "changed_packages=$FILTERSET" >> "$GITHUB_OUTPUT" fi fi check_pattern "run_action_checks" '^\.github/(workflows/|actions/|actionlint.yml)|tooling/xtask|script/' -qP check_pattern "run_docs" '^(docs/|crates/.*\.rs)' -qP check_pattern "run_licenses" '^(Cargo.lock|script/.*licenses)' -qP check_pattern "run_tests" '^(docs/|script/update_top_ranking_issues/|\.github/(ISSUE_TEMPLATE|workflows/(?!run_tests))|extensions/)' -qvP # Detect changed extension directories (excluding extensions/workflows) CHANGED_EXTENSIONS=$(echo "$CHANGED_FILES" | grep -oP '^extensions/[^/]+(?=/)' | sort -u | grep -v '^extensions/workflows$' || true) if [ -n "$CHANGED_EXTENSIONS" ]; then EXTENSIONS_JSON=$(echo "$CHANGED_EXTENSIONS" | jq -R -s -c 'split("\n") | map(select(length > 0))') else EXTENSIONS_JSON="[]" fi echo "changed_extensions=$EXTENSIONS_JSON" >> "$GITHUB_OUTPUT"
  • ./script/prettier
  • cargo fmt --all -- --check
  • ./script/check-todos
  • ./script/check-keymaps
  • tar -xf "$GITHUB_WORKSPACE/ts_query_ls-x86_64-unknown-linux-gnu.tar.gz" -C "$GITHUB_WORKSPACE" "$GITHUB_WORKSPACE/ts_query_ls" format --check . || { echo "Found unformatted queries, please format them with ts_query_ls." echo "For easy use, install the Tree-sitter query extension:" echo "zed://extension/tree-sitter-query" false }
  • New-Item -ItemType Directory -Path "./../.cargo" -Force Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
  • ./script/setup-sccache.ps1
View raw YAML
# Generated from xtask::workflows::run_tests
# Rebuild with `cargo xtask workflows`.
name: run_tests
env:
  CARGO_TERM_COLOR: always
  RUST_BACKTRACE: '1'
  CARGO_INCREMENTAL: '0'
on:
  pull_request:
    branches:
    - '**'
  push:
    branches:
    - main
    - v[0-9]+.[0-9]+.x
jobs:
  orchestrate:
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
        fetch-depth: ${{ github.ref == 'refs/heads/main' && 2 || 350 }}
    - id: filter
      name: filter
      run: |
        set -euo pipefail
        if [ -z "$GITHUB_BASE_REF" ]; then
          echo "Not in a PR context (i.e., push to main/stable/preview)"
          COMPARE_REV="$(git rev-parse HEAD~1)"
        else
          echo "In a PR context comparing to pull_request.base.ref"
          git fetch origin "$GITHUB_BASE_REF" --depth=350
          COMPARE_REV="$(git merge-base "origin/${GITHUB_BASE_REF}" HEAD)"
        fi
        CHANGED_FILES="$(git diff --name-only "$COMPARE_REV" "$GITHUB_SHA")"

        check_pattern() {
          local output_name="$1"
          local pattern="$2"
          local grep_arg="$3"

          echo "$CHANGED_FILES" | grep "$grep_arg" "$pattern" && \
            echo "${output_name}=true" >> "$GITHUB_OUTPUT" || \
            echo "${output_name}=false" >> "$GITHUB_OUTPUT"
        }

        # Check for changes that require full rebuild (no filter)
        # Direct pushes to main/stable/preview always run full suite
        if [ -z "$GITHUB_BASE_REF" ]; then
          echo "Not a PR, running full test suite"
          echo "changed_packages=" >> "$GITHUB_OUTPUT"
        elif echo "$CHANGED_FILES" | grep -qP '^(rust-toolchain\.toml|\.cargo/|\.github/|Cargo\.(toml|lock)$)'; then
          echo "Toolchain, cargo config, or root Cargo files changed, will run all tests"
          echo "changed_packages=" >> "$GITHUB_OUTPUT"
        else
          # Extract changed directories from file paths
          CHANGED_DIRS=$(echo "$CHANGED_FILES" | \
            grep -oP '^(crates|tooling)/\K[^/]+' | \
            sort -u || true)

          # Build directory-to-package mapping using cargo metadata
          DIR_TO_PKG=$(cargo metadata --format-version=1 --no-deps 2>/dev/null | \
            jq -r '.packages[] | select(.manifest_path | test("crates/|tooling/")) | "\(.manifest_path | capture("(crates|tooling)/(?<dir>[^/]+)") | .dir)=\(.name)"')

          # Map directory names to package names
          FILE_CHANGED_PKGS=""
          for dir in $CHANGED_DIRS; do
            pkg=$(echo "$DIR_TO_PKG" | grep "^${dir}=" | cut -d= -f2 | head -1)
            if [ -n "$pkg" ]; then
              FILE_CHANGED_PKGS=$(printf '%s\n%s' "$FILE_CHANGED_PKGS" "$pkg")
            else
              # Fall back to directory name if no mapping found
              FILE_CHANGED_PKGS=$(printf '%s\n%s' "$FILE_CHANGED_PKGS" "$dir")
            fi
          done
          FILE_CHANGED_PKGS=$(echo "$FILE_CHANGED_PKGS" | grep -v '^$' | sort -u || true)

          # If assets/ changed, add crates that depend on those assets
          if echo "$CHANGED_FILES" | grep -qP '^assets/'; then
            FILE_CHANGED_PKGS=$(printf '%s\n%s\n%s\n%s' "$FILE_CHANGED_PKGS" "settings" "storybook" "assets" | sort -u)
          fi

          # Combine all changed packages
          ALL_CHANGED_PKGS=$(echo "$FILE_CHANGED_PKGS" | grep -v '^$' || true)

          if [ -z "$ALL_CHANGED_PKGS" ]; then
            echo "No package changes detected, will run all tests"
            echo "changed_packages=" >> "$GITHUB_OUTPUT"
          else
            # Build nextest filterset with rdeps for each package
            FILTERSET=$(echo "$ALL_CHANGED_PKGS" | \
              sed 's/.*/rdeps(&)/' | \
              tr '\n' '|' | \
              sed 's/|$//')
            echo "Changed packages filterset: $FILTERSET"
            echo "changed_packages=$FILTERSET" >> "$GITHUB_OUTPUT"
          fi
        fi

        check_pattern "run_action_checks" '^\.github/(workflows/|actions/|actionlint.yml)|tooling/xtask|script/' -qP
        check_pattern "run_docs" '^(docs/|crates/.*\.rs)' -qP
        check_pattern "run_licenses" '^(Cargo.lock|script/.*licenses)' -qP
        check_pattern "run_tests" '^(docs/|script/update_top_ranking_issues/|\.github/(ISSUE_TEMPLATE|workflows/(?!run_tests))|extensions/)' -qvP
        # Detect changed extension directories (excluding extensions/workflows)
        CHANGED_EXTENSIONS=$(echo "$CHANGED_FILES" | grep -oP '^extensions/[^/]+(?=/)' | sort -u | grep -v '^extensions/workflows$' || true)
        if [ -n "$CHANGED_EXTENSIONS" ]; then
            EXTENSIONS_JSON=$(echo "$CHANGED_EXTENSIONS" | jq -R -s -c 'split("\n") | map(select(length > 0))')
        else
            EXTENSIONS_JSON="[]"
        fi
        echo "changed_extensions=$EXTENSIONS_JSON" >> "$GITHUB_OUTPUT"
    outputs:
      changed_packages: ${{ steps.filter.outputs.changed_packages }}
      run_action_checks: ${{ steps.filter.outputs.run_action_checks }}
      run_docs: ${{ steps.filter.outputs.run_docs }}
      run_licenses: ${{ steps.filter.outputs.run_licenses }}
      run_tests: ${{ steps.filter.outputs.run_tests }}
      changed_extensions: ${{ steps.filter.outputs.changed_extensions }}
  check_style:
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
    runs-on: namespace-profile-4x8-ubuntu-2204
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: steps::setup_pnpm
      uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2
      with:
        version: '9'
    - name: steps::prettier
      run: ./script/prettier
    - name: steps::cargo_fmt
      run: cargo fmt --all -- --check
    - name: ./script/check-todos
      run: ./script/check-todos
    - name: ./script/check-keymaps
      run: ./script/check-keymaps
    - name: run_tests::check_style::check_for_typos
      uses: crate-ci/typos@2d0ce569feab1f8752f1dde43cc2f2aa53236e06
      with:
        config: ./typos.toml
    - name: run_tests::fetch_ts_query_ls
      uses: dsaltares/fetch-gh-release-asset@aa37ae5c44d3c9820bc12fe675e8670ecd93bd1c
      with:
        repo: ribru17/ts_query_ls
        version: tags/v3.15.1
        file: ts_query_ls-x86_64-unknown-linux-gnu.tar.gz
    - name: run_tests::run_ts_query_ls
      run: |-
        tar -xf "$GITHUB_WORKSPACE/ts_query_ls-x86_64-unknown-linux-gnu.tar.gz" -C "$GITHUB_WORKSPACE"
        "$GITHUB_WORKSPACE/ts_query_ls" format --check . || {
            echo "Found unformatted queries, please format them with ts_query_ls."
            echo "For easy use, install the Tree-sitter query extension:"
            echo "zed://extension/tree-sitter-query"
            false
        }
    timeout-minutes: 60
  clippy_windows:
    needs:
    - orchestrate
    if: needs.orchestrate.outputs.run_tests == 'true'
    runs-on: self-32vcpu-windows-2022
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_cargo_config
      run: |
        New-Item -ItemType Directory -Path "./../.cargo" -Force
        Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
      shell: pwsh
    - name: steps::setup_sccache
      run: ./script/setup-sccache.ps1
      shell: pwsh
      env:
        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
        SCCACHE_BUCKET: sccache-zed
    - name: steps::clippy
      run: ./script/clippy.ps1
      shell: pwsh
    - name: steps::show_sccache_stats
      run: if ($env:RUSTC_WRAPPER) { & $env:RUSTC_WRAPPER --show-stats }; exit 0
      shell: pwsh
    timeout-minutes: 60
  clippy_linux:
    needs:
    - orchestrate
    if: needs.orchestrate.outputs.run_tests == 'true'
    runs-on: namespace-profile-16x32-ubuntu-2204
    env:
      CC: clang
      CXX: clang++
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_cargo_config
      run: |
        mkdir -p ./../.cargo
        cp ./.cargo/ci-config.toml ./../.cargo/config.toml
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: steps::setup_linux
      run: ./script/linux
    - name: steps::download_wasi_sdk
      run: ./script/download-wasi-sdk
    - name: steps::setup_sccache
      run: ./script/setup-sccache
      env:
        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
        SCCACHE_BUCKET: sccache-zed
    - name: steps::clippy
      run: ./script/clippy
    - name: steps::show_sccache_stats
      run: sccache --show-stats || true
    timeout-minutes: 60
  clippy_mac:
    needs:
    - orchestrate
    if: needs.orchestrate.outputs.run_tests == 'true'
    runs-on: namespace-profile-mac-large
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_cargo_config
      run: |
        mkdir -p ./../.cargo
        cp ./.cargo/ci-config.toml ./../.cargo/config.toml
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: steps::setup_sccache
      run: ./script/setup-sccache
      env:
        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
        SCCACHE_BUCKET: sccache-zed
    - name: steps::clippy
      run: ./script/clippy
    - name: steps::show_sccache_stats
      run: sccache --show-stats || true
    timeout-minutes: 60
  clippy_mac_x86_64:
    needs:
    - orchestrate
    if: needs.orchestrate.outputs.run_tests == 'true'
    runs-on: namespace-profile-mac-large
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_cargo_config
      run: |
        mkdir -p ./../.cargo
        cp ./.cargo/ci-config.toml ./../.cargo/config.toml
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: steps::install_rustup_target
      run: rustup target add x86_64-apple-darwin
    - name: steps::setup_sccache
      run: ./script/setup-sccache
      env:
        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
        SCCACHE_BUCKET: sccache-zed
    - name: steps::clippy
      run: ./script/clippy --target x86_64-apple-darwin
    - name: steps::show_sccache_stats
      run: sccache --show-stats || true
    timeout-minutes: 60
  run_tests_windows:
    needs:
    - orchestrate
    if: needs.orchestrate.outputs.run_tests == 'true'
    runs-on: self-32vcpu-windows-2022
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_cargo_config
      run: |
        New-Item -ItemType Directory -Path "./../.cargo" -Force
        Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
      shell: pwsh
    - name: steps::setup_node
      uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
      with:
        node-version: '20'
    - name: steps::clear_target_dir_if_large
      run: ./script/clear-target-dir-if-larger-than.ps1 250
      shell: pwsh
    - name: steps::setup_sccache
      run: ./script/setup-sccache.ps1
      shell: pwsh
      env:
        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
        SCCACHE_BUCKET: sccache-zed
    - name: steps::cargo_nextest
      run: cargo nextest run --workspace --no-fail-fast --no-tests=warn${{ needs.orchestrate.outputs.changed_packages && format(' -E "{0}"', needs.orchestrate.outputs.changed_packages) || '' }}
      shell: pwsh
    - name: steps::show_sccache_stats
      run: if ($env:RUSTC_WRAPPER) { & $env:RUSTC_WRAPPER --show-stats }; exit 0
      shell: pwsh
    - name: steps::cleanup_cargo_config
      if: always()
      run: |
        Remove-Item -Recurse -Path "./../.cargo" -Force -ErrorAction SilentlyContinue
      shell: pwsh
    timeout-minutes: 60
  run_tests_linux:
    needs:
    - orchestrate
    if: needs.orchestrate.outputs.run_tests == 'true'
    runs-on: namespace-profile-16x32-ubuntu-2204
    env:
      CC: clang
      CXX: clang++
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_cargo_config
      run: |
        mkdir -p ./../.cargo
        cp ./.cargo/ci-config.toml ./../.cargo/config.toml
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: steps::setup_linux
      run: ./script/linux
    - name: steps::download_wasi_sdk
      run: ./script/download-wasi-sdk
    - name: steps::setup_node
      uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
      with:
        node-version: '20'
    - name: steps::cargo_install_nextest
      uses: taiki-e/install-action@921e2c9f7148d7ba14cd819f417db338f63e733c
    - name: steps::clear_target_dir_if_large
      run: ./script/clear-target-dir-if-larger-than 250
    - name: steps::setup_sccache
      run: ./script/setup-sccache
      env:
        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
        SCCACHE_BUCKET: sccache-zed
    - name: steps::cargo_nextest
      run: cargo nextest run --workspace --no-fail-fast --no-tests=warn${{ needs.orchestrate.outputs.changed_packages && format(' -E "{0}"', needs.orchestrate.outputs.changed_packages) || '' }}
    - name: steps::show_sccache_stats
      run: sccache --show-stats || true
    - name: steps::cleanup_cargo_config
      if: always()
      run: |
        rm -rf ./../.cargo
    timeout-minutes: 60
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_HOST_AUTH_METHOD: trust
        ports:
        - 5432:5432
        options: --health-cmd pg_isready --health-interval 500ms --health-timeout 5s --health-retries 10
  run_tests_mac:
    needs:
    - orchestrate
    if: needs.orchestrate.outputs.run_tests == 'true'
    runs-on: namespace-profile-mac-large
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_cargo_config
      run: |
        mkdir -p ./../.cargo
        cp ./.cargo/ci-config.toml ./../.cargo/config.toml
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: steps::setup_node
      uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
      with:
        node-version: '20'
    - name: steps::cargo_install_nextest
      uses: taiki-e/install-action@921e2c9f7148d7ba14cd819f417db338f63e733c
    - name: steps::clear_target_dir_if_large
      run: ./script/clear-target-dir-if-larger-than 300
    - name: steps::setup_sccache
      run: ./script/setup-sccache
      env:
        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
        SCCACHE_BUCKET: sccache-zed
    - name: steps::cargo_nextest
      run: cargo nextest run --workspace --no-fail-fast --no-tests=warn${{ needs.orchestrate.outputs.changed_packages && format(' -E "{0}"', needs.orchestrate.outputs.changed_packages) || '' }}
    - name: steps::show_sccache_stats
      run: sccache --show-stats || true
    - name: steps::cleanup_cargo_config
      if: always()
      run: |
        rm -rf ./../.cargo
    timeout-minutes: 60
  doctests:
    needs:
    - orchestrate
    if: needs.orchestrate.outputs.run_tests == 'true'
    runs-on: namespace-profile-16x32-ubuntu-2204
    env:
      CC: clang
      CXX: clang++
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: steps::setup_linux
      run: ./script/linux
    - name: steps::download_wasi_sdk
      run: ./script/download-wasi-sdk
    - name: steps::setup_cargo_config
      run: |
        mkdir -p ./../.cargo
        cp ./.cargo/ci-config.toml ./../.cargo/config.toml
    - name: steps::setup_sccache
      run: ./script/setup-sccache
      env:
        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
        SCCACHE_BUCKET: sccache-zed
    - id: run_doctests
      name: run_tests::doctests::run_doctests
      run: |
        cargo test --workspace --doc --no-fail-fast
    - name: steps::show_sccache_stats
      run: sccache --show-stats || true
    - name: steps::cleanup_cargo_config
      if: always()
      run: |
        rm -rf ./../.cargo
    timeout-minutes: 60
  check_workspace_binaries:
    needs:
    - orchestrate
    if: needs.orchestrate.outputs.run_tests == 'true'
    runs-on: namespace-profile-8x16-ubuntu-2204
    env:
      CC: clang
      CXX: clang++
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_cargo_config
      run: |
        mkdir -p ./../.cargo
        cp ./.cargo/ci-config.toml ./../.cargo/config.toml
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: steps::setup_linux
      run: ./script/linux
    - name: steps::download_wasi_sdk
      run: ./script/download-wasi-sdk
    - name: steps::setup_sccache
      run: ./script/setup-sccache
      env:
        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
        SCCACHE_BUCKET: sccache-zed
    - name: cargo build -p collab
      run: cargo build -p collab
    - name: cargo build --workspace --bins --examples
      run: cargo build --workspace --bins --examples
    - name: steps::show_sccache_stats
      run: sccache --show-stats || true
    - name: steps::cleanup_cargo_config
      if: always()
      run: |
        rm -rf ./../.cargo
    timeout-minutes: 60
  check_wasm:
    needs:
    - orchestrate
    if: needs.orchestrate.outputs.run_tests == 'true'
    runs-on: namespace-profile-8x16-ubuntu-2204
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_cargo_config
      run: |
        mkdir -p ./../.cargo
        cp ./.cargo/ci-config.toml ./../.cargo/config.toml
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: run_tests::check_wasm::install_nightly_wasm_toolchain
      run: rustup toolchain install nightly --component rust-src --target wasm32-unknown-unknown
    - name: steps::setup_sccache
      run: ./script/setup-sccache
      env:
        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
        SCCACHE_BUCKET: sccache-zed
    - name: run_tests::check_wasm::cargo_check_wasm
      run: cargo +nightly -Zbuild-std=std,panic_abort check --target wasm32-unknown-unknown -p gpui_platform
      env:
        CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUSTFLAGS: -C target-feature=+atomics,+bulk-memory,+mutable-globals
    - name: steps::show_sccache_stats
      run: sccache --show-stats || true
    - name: steps::cleanup_cargo_config
      if: always()
      run: |
        rm -rf ./../.cargo
    timeout-minutes: 60
  check_dependencies:
    needs:
    - orchestrate
    if: needs.orchestrate.outputs.run_tests == 'true'
    runs-on: namespace-profile-2x4-ubuntu-2404
    env:
      CC: clang
      CXX: clang++
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: run_tests::check_dependencies::install_cargo_machete
      uses: clechasseur/rs-cargo@8435b10f6e71c2e3d4d3b7573003a8ce4bfc6386
      with:
        command: install
        args: cargo-machete@0.7.0
    - name: run_tests::check_dependencies::run_cargo_machete
      uses: clechasseur/rs-cargo@8435b10f6e71c2e3d4d3b7573003a8ce4bfc6386
      with:
        command: machete
    - name: run_tests::check_dependencies::check_cargo_lock
      run: cargo update --locked --workspace
    - name: run_tests::check_dependencies::check_vulnerable_dependencies
      if: github.event_name == 'pull_request'
      uses: actions/dependency-review-action@67d4f4bd7a9b17a0db54d2a7519187c65e339de8
      with:
        license-check: false
    timeout-minutes: 60
  check_docs:
    needs:
    - orchestrate
    if: needs.orchestrate.outputs.run_docs == 'true'
    runs-on: namespace-profile-8x16-ubuntu-2204
    env:
      CC: clang
      CXX: clang++
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_cargo_config
      run: |
        mkdir -p ./../.cargo
        cp ./.cargo/ci-config.toml ./../.cargo/config.toml
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: run_tests::check_docs::lychee_link_check
      uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332
      with:
        args: --no-progress --exclude '^http' './docs/src/**/*'
        fail: true
        jobSummary: false
    - name: steps::setup_linux
      run: ./script/linux
    - name: steps::download_wasi_sdk
      run: ./script/download-wasi-sdk
    - name: ./script/generate-action-metadata
      run: ./script/generate-action-metadata
    - name: run_tests::check_docs::install_mdbook
      uses: peaceiris/actions-mdbook@ee69d230fe19748b7abf22df32acaa93833fad08
      with:
        mdbook-version: 0.4.37
    - name: run_tests::check_docs::build_docs
      run: |
        mkdir -p target/deploy
        mdbook build ./docs --dest-dir=../target/deploy/docs/
    - name: run_tests::check_docs::lychee_link_check
      uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332
      with:
        args: --no-progress --exclude '^http' 'target/deploy/docs'
        fail: true
        jobSummary: false
    timeout-minutes: 60
  check_licenses:
    needs:
    - orchestrate
    if: needs.orchestrate.outputs.run_licenses == 'true'
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: ./script/check-licenses
      run: ./script/check-licenses
    - name: ./script/generate-licenses
      run: ./script/generate-licenses
  check_scripts:
    needs:
    - orchestrate
    if: needs.orchestrate.outputs.run_action_checks == 'true'
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: run_tests::check_scripts::run_shellcheck
      run: ./script/shellcheck-scripts error
    - id: get_actionlint
      name: run_tests::check_scripts::download_actionlint
      run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
    - name: run_tests::check_scripts::run_actionlint
      run: '"$ACTIONLINT_BIN" -color'
      env:
        ACTIONLINT_BIN: ${{ steps.get_actionlint.outputs.executable }}
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: run_tests::check_scripts::check_xtask_workflows
      run: |
        cargo xtask workflows
        if ! git diff --exit-code .github; then
          echo "Error: .github directory has uncommitted changes after running 'cargo xtask workflows'"
          echo "Please run 'cargo xtask workflows' locally and commit the changes"
          exit 1
        fi
    timeout-minutes: 60
  check_postgres_and_protobuf_migrations:
    needs:
    - orchestrate
    if: needs.orchestrate.outputs.run_tests == 'true'
    runs-on: namespace-profile-16x32-ubuntu-2204
    env:
      GIT_AUTHOR_NAME: Protobuf Action
      GIT_AUTHOR_EMAIL: ci@zed.dev
      GIT_COMMITTER_NAME: Protobuf Action
      GIT_COMMITTER_EMAIL: ci@zed.dev
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
        fetch-depth: 0
    - name: run_tests::check_postgres_and_protobuf_migrations::ensure_fresh_merge
      run: |
        if [ -z "$GITHUB_BASE_REF" ];
        then
          echo "BUF_BASE_BRANCH=$(git merge-base origin/main HEAD)" >> "$GITHUB_ENV"
        else
          git checkout -B temp
          git merge -q "origin/$GITHUB_BASE_REF" -m "merge main into temp"
          echo "BUF_BASE_BRANCH=$GITHUB_BASE_REF" >> "$GITHUB_ENV"
        fi
    - name: run_tests::check_postgres_and_protobuf_migrations::bufbuild_setup_action
      uses: bufbuild/buf-setup-action@v1
      with:
        version: v1.29.0
        github_token: ${{ secrets.GITHUB_TOKEN }}
    - name: run_tests::check_postgres_and_protobuf_migrations::bufbuild_breaking_action
      uses: bufbuild/buf-breaking-action@v1
      with:
        input: crates/proto/proto/
        against: https://github.com/${GITHUB_REPOSITORY}.git#branch=${BUF_BASE_BRANCH},subdir=crates/proto/proto/
    - name: run_tests::check_postgres_and_protobuf_migrations::buf_lint
      run: buf lint crates/proto/proto
    - name: run_tests::check_postgres_and_protobuf_migrations::check_protobuf_formatting
      run: buf format --diff --exit-code crates/proto/proto
    timeout-minutes: 60
  extension_tests:
    needs:
    - orchestrate
    if: needs.orchestrate.outputs.changed_extensions != '[]'
    permissions:
      contents: read
    strategy:
      matrix:
        extension: ${{ fromJson(needs.orchestrate.outputs.changed_extensions) }}
      fail-fast: false
      max-parallel: 1
    uses: ./.github/workflows/extension_tests.yml
    with:
      working-directory: ${{ matrix.extension }}
  tests_pass:
    needs:
    - orchestrate
    - check_style
    - clippy_windows
    - clippy_linux
    - clippy_mac
    - clippy_mac_x86_64
    - run_tests_windows
    - run_tests_linux
    - run_tests_mac
    - doctests
    - check_workspace_binaries
    - check_wasm
    - check_dependencies
    - check_docs
    - check_licenses
    - check_scripts
    - extension_tests
    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions') && always()
    runs-on: namespace-profile-2x4-ubuntu-2404
    steps:
    - name: run_tests::tests_pass
      run: |
        set +x
        EXIT_CODE=0

        check_result() {
          echo "* $1: $2"
          if [[ "$2" != "skipped" && "$2" != "success" ]]; then EXIT_CODE=1; fi
        }

        check_result "orchestrate" "$RESULT_ORCHESTRATE"
        check_result "check_style" "$RESULT_CHECK_STYLE"
        check_result "clippy_windows" "$RESULT_CLIPPY_WINDOWS"
        check_result "clippy_linux" "$RESULT_CLIPPY_LINUX"
        check_result "clippy_mac" "$RESULT_CLIPPY_MAC"
        check_result "clippy_mac_x86_64" "$RESULT_CLIPPY_MAC_X86_64"
        check_result "run_tests_windows" "$RESULT_RUN_TESTS_WINDOWS"
        check_result "run_tests_linux" "$RESULT_RUN_TESTS_LINUX"
        check_result "run_tests_mac" "$RESULT_RUN_TESTS_MAC"
        check_result "doctests" "$RESULT_DOCTESTS"
        check_result "check_workspace_binaries" "$RESULT_CHECK_WORKSPACE_BINARIES"
        check_result "check_wasm" "$RESULT_CHECK_WASM"
        check_result "check_dependencies" "$RESULT_CHECK_DEPENDENCIES"
        check_result "check_docs" "$RESULT_CHECK_DOCS"
        check_result "check_licenses" "$RESULT_CHECK_LICENSES"
        check_result "check_scripts" "$RESULT_CHECK_SCRIPTS"
        check_result "extension_tests" "$RESULT_EXTENSION_TESTS"

        exit $EXIT_CODE
      env:
        RESULT_ORCHESTRATE: ${{ needs.orchestrate.result }}
        RESULT_CHECK_STYLE: ${{ needs.check_style.result }}
        RESULT_CLIPPY_WINDOWS: ${{ needs.clippy_windows.result }}
        RESULT_CLIPPY_LINUX: ${{ needs.clippy_linux.result }}
        RESULT_CLIPPY_MAC: ${{ needs.clippy_mac.result }}
        RESULT_CLIPPY_MAC_X86_64: ${{ needs.clippy_mac_x86_64.result }}
        RESULT_RUN_TESTS_WINDOWS: ${{ needs.run_tests_windows.result }}
        RESULT_RUN_TESTS_LINUX: ${{ needs.run_tests_linux.result }}
        RESULT_RUN_TESTS_MAC: ${{ needs.run_tests_mac.result }}
        RESULT_DOCTESTS: ${{ needs.doctests.result }}
        RESULT_CHECK_WORKSPACE_BINARIES: ${{ needs.check_workspace_binaries.result }}
        RESULT_CHECK_WASM: ${{ needs.check_wasm.result }}
        RESULT_CHECK_DEPENDENCIES: ${{ needs.check_dependencies.result }}
        RESULT_CHECK_DOCS: ${{ needs.check_docs.result }}
        RESULT_CHECK_LICENSES: ${{ needs.check_licenses.result }}
        RESULT_CHECK_SCRIPTS: ${{ needs.check_scripts.result }}
        RESULT_EXTENSION_TESTS: ${{ needs.extension_tests.result }}
concurrency:
  group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
  cancel-in-progress: true
defaults:
  run:
    shell: bash -euxo pipefail {0}
run_unit_evals .github/workflows/run_unit_evals.yml
Triggers
workflow_dispatch
Runs on
namespace-profile-16x32-ubuntu-2204
Jobs
run_unit_evals
Actions
namespacelabs/nscloud-cache-action, taiki-e/install-action
Commands
  • mkdir -p ./../.cargo cp ./.cargo/ci-config.toml ./../.cargo/config.toml
  • ./script/linux
  • ./script/download-wasi-sdk
  • ./script/clear-target-dir-if-larger-than 250
  • ./script/setup-sccache
  • ./script/run-unit-evals
  • sccache --show-stats || true
  • rm -rf ./../.cargo
View raw YAML
# Generated from xtask::workflows::run_unit_evals
# Rebuild with `cargo xtask workflows`.
name: run_unit_evals
env:
  CARGO_TERM_COLOR: always
  CARGO_INCREMENTAL: '0'
  RUST_BACKTRACE: '1'
  ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
  ZED_EVAL_TELEMETRY: '1'
  MODEL_NAME: ${{ inputs.model_name }}
on:
  workflow_dispatch:
    inputs:
      model_name:
        description: model_name
        required: true
        type: string
      commit_sha:
        description: commit_sha
        required: true
        type: string
jobs:
  run_unit_evals:
    runs-on: namespace-profile-16x32-ubuntu-2204
    steps:
    - name: steps::checkout_repo
      uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
      with:
        clean: false
    - name: steps::setup_cargo_config
      run: |
        mkdir -p ./../.cargo
        cp ./.cargo/ci-config.toml ./../.cargo/config.toml
    - name: steps::cache_rust_dependencies_namespace
      uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
      with:
        cache: rust
        path: ~/.rustup
    - name: steps::setup_linux
      run: ./script/linux
    - name: steps::download_wasi_sdk
      run: ./script/download-wasi-sdk
    - name: steps::cargo_install_nextest
      uses: taiki-e/install-action@921e2c9f7148d7ba14cd819f417db338f63e733c
    - name: steps::clear_target_dir_if_large
      run: ./script/clear-target-dir-if-larger-than 250
    - name: steps::setup_sccache
      run: ./script/setup-sccache
      env:
        R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
        R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
        R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
        SCCACHE_BUCKET: sccache-zed
    - name: ./script/run-unit-evals
      run: ./script/run-unit-evals
      env:
        ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        GOOGLE_AI_API_KEY: ${{ secrets.GOOGLE_AI_API_KEY }}
        GOOGLE_CLOUD_PROJECT: ${{ secrets.GOOGLE_CLOUD_PROJECT }}
        UNIT_EVAL_COMMIT: ${{ inputs.commit_sha }}
    - name: steps::show_sccache_stats
      run: sccache --show-stats || true
    - name: steps::cleanup_cargo_config
      if: always()
      run: |
        rm -rf ./../.cargo
concurrency:
  group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.run_id }}
  cancel-in-progress: true
defaults:
  run:
    shell: bash -euxo pipefail {0}
slack_notify_first_responders .github/workflows/slack_notify_first_responders.yml
Triggers
issues
Runs on
namespace-profile-2x4-ubuntu-2404
Jobs
notify-slack
Commands
  • if echo "$FIRST_RESPONDER_LABELS" | jq -e --arg label "$LABEL_NAME" 'index($label) != null' > /dev/null; then echo "should_notify=true" >> "$GITHUB_OUTPUT" echo "Label '$LABEL_NAME' requires first responder notification" else echo "should_notify=false" >> "$GITHUB_OUTPUT" echo "Label '$LABEL_NAME' does not require first responder notification, skipping" fi
  • LABELS=$(echo "$LABELS_JSON" | jq -r 'join(", ")') jq -n \ --arg label_name "$LABEL_NAME" \ --arg issue_title "$ISSUE_TITLE" \ --arg issue_url "$ISSUE_URL" \ --arg labeled_by "$LABELED_BY" \ --arg labels "$LABELS" \ '{ "blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": "<!subteam^S096CPUUGLF> Issue labeled *\($label_name)*" } }, { "type": "section", "fields": [ { "type": "mrkdwn", "text": "*Issue:*\n<\($issue_url)|\($issue_title)>" }, { "type": "mrkdwn", "text": "*Labeled by:*\n\($labeled_by)" }, { "type": "mrkdwn", "text": "*Labels:*\n\($labels)" } ] } ] }' > payload.json echo "Payload built successfully:" cat payload.json
  • if [ -z "$SLACK_WEBHOOK_URL" ]; then echo "::error::SLACK_WEBHOOK_FIRST_RESPONDERS secret is not set" exit 1 fi HTTP_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$SLACK_WEBHOOK_URL" \ -H "Content-Type: application/json" \ -d @payload.json) HTTP_BODY=$(echo "$HTTP_RESPONSE" | sed '$d') HTTP_STATUS=$(echo "$HTTP_RESPONSE" | tail -n 1) echo "Slack API response status: $HTTP_STATUS" echo "Slack API response body: $HTTP_BODY" if [ "$HTTP_STATUS" -ne 200 ]; then echo "::error::Slack notification failed with status $HTTP_STATUS: $HTTP_BODY" exit 1 fi echo "Slack notification sent successfully"
View raw YAML
name: Slack Notify First Responders

on:
  issues:
    types: [labeled]

env:
  FIRST_RESPONDER_LABELS: '["priority:P0", "priority:P1"]'

jobs:
  notify-slack:
    if: github.repository_owner == 'zed-industries' && github.event.issue.state == 'open'
    runs-on: namespace-profile-2x4-ubuntu-2404

    steps:
      - name: Check if label requires first responder notification
        id: check-label
        env:
          LABEL_NAME: ${{ github.event.label.name }}
          FIRST_RESPONDER_LABELS: ${{ env.FIRST_RESPONDER_LABELS }}
        run: |
          if echo "$FIRST_RESPONDER_LABELS" | jq -e --arg label "$LABEL_NAME" 'index($label) != null' > /dev/null; then
            echo "should_notify=true" >> "$GITHUB_OUTPUT"
            echo "Label '$LABEL_NAME' requires first responder notification"
          else
            echo "should_notify=false" >> "$GITHUB_OUTPUT"
            echo "Label '$LABEL_NAME' does not require first responder notification, skipping"
          fi

      - name: Build Slack message payload
        if: steps.check-label.outputs.should_notify == 'true'
        env:
          ISSUE_TITLE: ${{ github.event.issue.title }}
          ISSUE_URL: ${{ github.event.issue.html_url }}
          LABELED_BY: ${{ github.event.sender.login }}
          LABEL_NAME: ${{ github.event.label.name }}
          LABELS_JSON: ${{ toJson(github.event.issue.labels.*.name) }}
        run: |
          LABELS=$(echo "$LABELS_JSON" | jq -r 'join(", ")')

          jq -n \
            --arg label_name "$LABEL_NAME" \
            --arg issue_title "$ISSUE_TITLE" \
            --arg issue_url "$ISSUE_URL" \
            --arg labeled_by "$LABELED_BY" \
            --arg labels "$LABELS" \
            '{
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "<!subteam^S096CPUUGLF> Issue labeled *\($label_name)*"
                  }
                },
                {
                  "type": "section",
                  "fields": [
                    {
                      "type": "mrkdwn",
                      "text": "*Issue:*\n<\($issue_url)|\($issue_title)>"
                    },
                    {
                      "type": "mrkdwn",
                      "text": "*Labeled by:*\n\($labeled_by)"
                    },
                    {
                      "type": "mrkdwn",
                      "text": "*Labels:*\n\($labels)"
                    }
                  ]
                }
              ]
            }' > payload.json

          echo "Payload built successfully:"
          cat payload.json

      - name: Send Slack notification
        if: steps.check-label.outputs.should_notify == 'true'
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_FIRST_RESPONDERS }}
        run: |
          if [ -z "$SLACK_WEBHOOK_URL" ]; then
            echo "::error::SLACK_WEBHOOK_FIRST_RESPONDERS secret is not set"
            exit 1
          fi

          HTTP_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$SLACK_WEBHOOK_URL" \
            -H "Content-Type: application/json" \
            -d @payload.json)

          HTTP_BODY=$(echo "$HTTP_RESPONSE" | sed '$d')
          HTTP_STATUS=$(echo "$HTTP_RESPONSE" | tail -n 1)

          echo "Slack API response status: $HTTP_STATUS"
          echo "Slack API response body: $HTTP_BODY"

          if [ "$HTTP_STATUS" -ne 200 ]; then
            echo "::error::Slack notification failed with status $HTTP_STATUS: $HTTP_BODY"
            exit 1
          fi

          echo "Slack notification sent successfully"
stale-pr-reminder perms .github/workflows/stale-pr-reminder.yml
Triggers
schedule, workflow_dispatch
Runs on
ubuntu-latest
Jobs
check-stale-prs
Commands
  • CUTOFF=$(date -u -v-72H +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \ || date -u -d '72 hours ago' +%Y-%m-%dT%H:%M:%SZ) # Get open, non-draft PRs with pending review requests, created before cutoff # but after the review process start date (to exclude pre-existing backlog) gh api --paginate \ "repos/${REPO}/pulls?state=open&sort=updated&direction=asc&per_page=100" \ --jq "[ .[] | select(.draft == false) | select(.created_at > \"$PROCESS_START_DATE\") | select(.created_at < \"$CUTOFF\") | select((.requested_reviewers | length > 0) or (.requested_teams | length > 0)) ]" > /tmp/candidates.json # Filter to PRs with zero approving reviews jq -r '.[].number' /tmp/candidates.json | while read -r PR_NUMBER; do APPROVALS=$(gh api \ "repos/${REPO}/pulls/${PR_NUMBER}/reviews" \ --jq "[.[] | select(.state == \"APPROVED\")] | length" 2>/dev/null || echo "0") if [ "$APPROVALS" -eq 0 ]; then jq ".[] | select(.number == ${PR_NUMBER}) | {number, title, author: .user.login, created_at}" \ /tmp/candidates.json fi done | jq -s '.' > /tmp/awaiting.json COUNT=$(jq 'length' /tmp/awaiting.json) echo "count=$COUNT" >> "$GITHUB_OUTPUT"
  • # Build Block Kit payload from JSON — no shell interpolation of PR titles. # Why jq? PR titles are attacker-controllable input. By reading them # through jq -r from the JSON file and passing the result to jq --arg, # the content stays safely JSON-encoded in the final payload. PRS=$(jq -r '.[] | "• <https://github.com/'"${REPO}"'/pull/\(.number)|#\(.number)> — \(.title) (by \(.author), opened \(.created_at | split("T")[0]))"' /tmp/awaiting.json) jq -n \ --arg count "$COUNT" \ --arg prs "$PRS" \ '{ text: ($count + " PR(s) awaiting review for >72 hours"), blocks: [ { type: "section", text: { type: "mrkdwn", text: (":hourglass_flowing_sand: *" + $count + " PR(s) Awaiting Review >72 Hours*") } }, { type: "section", text: { type: "mrkdwn", text: $prs } }, { type: "divider" }, { type: "context", elements: [{ type: "mrkdwn", text: "PRs awaiting review are surfaced daily. Reviewers: pick one up or reassign." }] } ] }' | \ curl -s -X POST "$SLACK_WEBHOOK_PR_REVIEW_BOT" \ -H 'Content-Type: application/json' \ -d @-
View raw YAML
# Stale PR Review Reminder
#
# Runs daily on weekdays (second run at 8 PM UTC disabled during rollout) and posts a Slack summary of open PRs that
# have been awaiting review for more than 72 hours. Team-level signal only —
# no individual shaming.
#
# Security note: No untrusted input is interpolated into shell commands.
# All PR metadata is read via gh API + jq.
#
# Required secrets:
#   SLACK_WEBHOOK_PR_REVIEW_BOT - Incoming webhook URL for the #pr-review-ops channel

name: Stale PR Review Reminder

on:
  schedule:
    - cron: "0 14 * * 1-5" # 2 PM UTC weekdays
    # - cron: "0 20 * * 1-5" # 8 PM UTC weekdays — enable after initial rollout
  workflow_dispatch: {}

permissions:
  contents: read
  pull-requests: read

jobs:
  check-stale-prs:
    if: github.repository_owner == 'zed-industries'
    runs-on: ubuntu-latest
    timeout-minutes: 5
    env:
      REPO: ${{ github.repository }}
      # Only surface PRs created on or after this date. Update this if the
      # review process enforcement date changes.
      PROCESS_START_DATE: "2026-03-19T00:00:00Z"
    steps:
      - name: Find PRs awaiting review longer than 72h
        id: stale
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          CUTOFF=$(date -u -v-72H +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
            || date -u -d '72 hours ago' +%Y-%m-%dT%H:%M:%SZ)

          # Get open, non-draft PRs with pending review requests, created before cutoff
          # but after the review process start date (to exclude pre-existing backlog)
          gh api --paginate \
            "repos/${REPO}/pulls?state=open&sort=updated&direction=asc&per_page=100" \
            --jq "[
              .[] |
              select(.draft == false) |
              select(.created_at > \"$PROCESS_START_DATE\") |
              select(.created_at < \"$CUTOFF\") |
              select((.requested_reviewers | length > 0) or (.requested_teams | length > 0))
            ]" > /tmp/candidates.json

          # Filter to PRs with zero approving reviews
          jq -r '.[].number' /tmp/candidates.json | while read -r PR_NUMBER; do
            APPROVALS=$(gh api \
              "repos/${REPO}/pulls/${PR_NUMBER}/reviews" \
              --jq "[.[] | select(.state == \"APPROVED\")] | length" 2>/dev/null || echo "0")

            if [ "$APPROVALS" -eq 0 ]; then
              jq ".[] | select(.number == ${PR_NUMBER}) | {number, title, author: .user.login, created_at}" \
                /tmp/candidates.json
            fi
          done | jq -s '.' > /tmp/awaiting.json

          COUNT=$(jq 'length' /tmp/awaiting.json)
          echo "count=$COUNT" >> "$GITHUB_OUTPUT"

      - name: Notify Slack
        if: steps.stale.outputs.count != '0'
        env:
          SLACK_WEBHOOK_PR_REVIEW_BOT: ${{ secrets.SLACK_WEBHOOK_PR_REVIEW_BOT }}
          COUNT: ${{ steps.stale.outputs.count }}
        run: |
          # Build Block Kit payload from JSON — no shell interpolation of PR titles.
          # Why jq? PR titles are attacker-controllable input. By reading them
          # through jq -r from the JSON file and passing the result to jq --arg,
          # the content stays safely JSON-encoded in the final payload.
          PRS=$(jq -r '.[] | "• <https://github.com/'"${REPO}"'/pull/\(.number)|#\(.number)> — \(.title) (by \(.author), opened \(.created_at | split("T")[0]))"' /tmp/awaiting.json)

          jq -n \
            --arg count "$COUNT" \
            --arg prs "$PRS" \
            '{
              text: ($count + " PR(s) awaiting review for >72 hours"),
              blocks: [
                {
                  type: "section",
                  text: {
                    type: "mrkdwn",
                    text: (":hourglass_flowing_sand: *" + $count + " PR(s) Awaiting Review >72 Hours*")
                  }
                },
                {
                  type: "section",
                  text: { type: "mrkdwn", text: $prs }
                },
                { type: "divider" },
                {
                  type: "context",
                  elements: [{
                    type: "mrkdwn",
                    text: "PRs awaiting review are surfaced daily. Reviewers: pick one up or reassign."
                  }]
                }
              ]
            }' | \
          curl -s -X POST "$SLACK_WEBHOOK_PR_REVIEW_BOT" \
            -H 'Content-Type: application/json' \
            -d @-
defaults:
  run:
    shell: bash -euxo pipefail {0}
track_duplicate_bot_effectiveness perms .github/workflows/track_duplicate_bot_effectiveness.yml
Triggers
issues, schedule, workflow_dispatch
Runs on
ubuntu-latest, ubuntu-latest
Jobs
classify-closed-issue, classify-open
Actions
actions/create-github-app-token, actions/create-github-app-token
Commands
  • pip install requests
  • python script/github-track-duplicate-bot-effectiveness.py \ classify-closed "$ISSUE_NUMBER" "$CLOSER_LOGIN" "$STATE_REASON"
  • pip install requests
  • python script/github-track-duplicate-bot-effectiveness.py classify-open
View raw YAML
name: Track duplicate bot effectiveness

on:
  issues:
    types: [closed]
  schedule:
    - cron: "0 8 */2 * *" # every 2 days at 8 AM UTC
  workflow_dispatch:

permissions:
  contents: read

jobs:
  classify-closed-issue:
    if: >
      github.event_name == 'issues' &&
      github.repository == 'zed-industries/zed' &&
      github.event.issue.pull_request == null &&
      github.event.issue.type != null &&
      (github.event.issue.type.name == 'Bug' || github.event.issue.type.name == 'Crash')
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - name: Checkout repository
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          sparse-checkout: script/github-track-duplicate-bot-effectiveness.py
          sparse-checkout-cone-mode: false

      - name: Get github app token
        id: get-app-token
        uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
        with:
          app-id: ${{ secrets.ZED_COMMUNITY_BOT_APP_ID }}
          private-key: ${{ secrets.ZED_COMMUNITY_BOT_PRIVATE_KEY }}
          owner: zed-industries

      - name: Set up Python
        uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
        with:
          python-version: "3.12"

      - name: Install dependencies
        run: pip install requests

      - name: Classify closed issue
        env:
          GITHUB_TOKEN: ${{ steps.get-app-token.outputs.token }}
          ISSUE_NUMBER: ${{ github.event.issue.number }}
          CLOSER_LOGIN: ${{ github.event.sender.login }}
          STATE_REASON: ${{ github.event.issue.state_reason }}
        run: |
          python script/github-track-duplicate-bot-effectiveness.py \
            classify-closed "$ISSUE_NUMBER" "$CLOSER_LOGIN" "$STATE_REASON"

  classify-open:
    if: >
      github.repository == 'zed-industries/zed' &&
      (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - name: Checkout repository
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          sparse-checkout: script/github-track-duplicate-bot-effectiveness.py
          sparse-checkout-cone-mode: false

      - name: Get github app token
        id: get-app-token
        uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
        with:
          app-id: ${{ secrets.ZED_COMMUNITY_BOT_APP_ID }}
          private-key: ${{ secrets.ZED_COMMUNITY_BOT_PRIVATE_KEY }}
          owner: zed-industries

      - name: Set up Python
        uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
        with:
          python-version: "3.12"

      - name: Install dependencies
        run: pip install requests

      - name: Classify open issues
        env:
          GITHUB_TOKEN: ${{ steps.get-app-token.outputs.token }}
        run: |
          python script/github-track-duplicate-bot-effectiveness.py classify-open
update_duplicate_magnets .github/workflows/update_duplicate_magnets.yml
Triggers
schedule, workflow_dispatch
Runs on
ubuntu-latest
Jobs
update-duplicate-magnets
Commands
  • pip install requests
  • python script/github-find-top-duplicated-bugs.py \ --github-token "$GITHUB_TOKEN" \ --issue-number 46355
View raw YAML
name: Update Duplicate Magnets Issue

on:
  schedule:
    - cron: "0 6 * * 1,4" # Mondays and Thursdays at 6 AM UTC
  workflow_dispatch:

jobs:
  update-duplicate-magnets:
    runs-on: ubuntu-latest
    if: github.repository == 'zed-industries/zed'
    steps:
      - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1

      - name: Set up Python
        uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
        with:
          python-version: "3.12"

      - name: Install dependencies
        run: pip install requests

      - name: Update duplicate magnets issue
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          python script/github-find-top-duplicated-bugs.py \
            --github-token "$GITHUB_TOKEN" \
            --issue-number 46355