Skip to content
Latchkey

PR Lint and Format Check workflow (Wei-Shaw/claude-relay-service)

The PR Lint and Format Check workflow from Wei-Shaw/claude-relay-service, explained and optimized by Latchkey.

C

CI health: C - fair

Point runs-on at Latchkey and get run de-duplication, job timeouts, SHA-pinned actions, self-healing for flaky steps, and up to 58% lower cost, applied automatically.

Grade your own workflow free or run it on Latchkey →
Source: Wei-Shaw/claude-relay-service.github/workflows/pr-lint-check.ymlLicense MITView source

What it does

This is the PR Lint and Format Check workflow from the Wei-Shaw/claude-relay-service repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.

Below, Latchkey shows a faster, safer version produced by its optimization engine.

The workflow

workflow (.yml)
name: PR Lint and Format Check

on:
  pull_request:
    types: [opened, synchronize, reopened]
    paths:
      - '**.js'
      - '**.jsx'
      - '**.ts'
      - '**.tsx'
      - '**.vue'
      - '**.json'
      - '**.cjs'
      - '**.mjs'
      - '.prettierrc'
      - '.eslintrc.cjs'
      - 'package.json'
      - 'web/admin-spa/**'

permissions:
  contents: read
  pull-requests: write
  issues: write

jobs:
  lint-and-format:
    runs-on: ubuntu-latest
    name: Check Code Quality

    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'
          cache: 'npm'

      - name: Cache dependencies
        uses: actions/cache@v3
        with:
          path: ~/.npm
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-

      - name: Install dependencies
        run: |
          npm ci --prefer-offline --no-audit
          # 安装 web 目录的依赖(如果存在)
          if [ -d "web/admin-spa" ] && [ -f "web/admin-spa/package.json" ]; then
            cd web/admin-spa
            npm ci --prefer-offline --no-audit
            cd ../..
          fi

      - name: Get changed files
        id: changed-files
        uses: tj-actions/changed-files@v41
        with:
          files: |
            **/*.js
            **/*.jsx
            **/*.ts
            **/*.tsx
            **/*.vue
            **/*.cjs
            **/*.mjs
            **/*.json
          files_ignore: |
            node_modules/**
            dist/**
            build/**
            coverage/**
            .git/**
            logs/**
            temp/**
            tmp/**

      - name: Check Prettier formatting
        if: steps.changed-files.outputs.any_changed == 'true'
        id: prettier-check
        run: |
          echo "🔍 Checking Prettier formatting for changed files..."
          echo "Changed files: ${{ steps.changed-files.outputs.all_changed_files }}"

          # 初始化标志
          PRETTIER_FAILED=false
          PRETTIER_OUTPUT=""

          # 检查每个改变的文件
          for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
            if [ -f "$file" ]; then
              echo "Checking: $file"
              
              # 根据文件位置选择正确的 prettier 配置
              if [[ "$file" == web/admin-spa/* ]]; then
                # 前端文件:进入前端目录运行 prettier
                cd web/admin-spa
                RELATIVE_FILE="${file#web/admin-spa/}"
                if ! npx prettier --check "$RELATIVE_FILE" 2>&1; then
                  PRETTIER_FAILED=true
                  DIFF=$(npx prettier "$RELATIVE_FILE" | diff -u "$RELATIVE_FILE" - || true)
                  if [ -n "$DIFF" ]; then
                    PRETTIER_OUTPUT="${PRETTIER_OUTPUT}❌ File needs formatting: $file\n"
                    PRETTIER_OUTPUT="${PRETTIER_OUTPUT}\`\`\`diff\n${DIFF}\n\`\`\`\n\n"
                  fi
                else
                  echo "✅ $file is properly formatted"
                fi
                cd ../..
              else
                # 后端文件:使用根目录的 prettier
                if ! npx prettier --check "$file" 2>&1; then
                  PRETTIER_FAILED=true
                  DIFF=$(npx prettier "$file" | diff -u "$file" - || true)
                  if [ -n "$DIFF" ]; then
                    PRETTIER_OUTPUT="${PRETTIER_OUTPUT}❌ File needs formatting: $file\n"
                    PRETTIER_OUTPUT="${PRETTIER_OUTPUT}\`\`\`diff\n${DIFF}\n\`\`\`\n\n"
                  fi
                else
                  echo "✅ $file is properly formatted"
                fi
              fi
            fi
          done

          # 输出结果
          if [ "$PRETTIER_FAILED" = true ]; then
            echo "prettier_failed=true" >> $GITHUB_OUTPUT
            echo -e "$PRETTIER_OUTPUT" > prettier-report.md
            echo "❌ Some files are not properly formatted."
            echo "Please run: npm run format (backend) or cd web/admin-spa && npm run format (frontend)"
            exit 1
          else
            echo "prettier_failed=false" >> $GITHUB_OUTPUT
            echo "✅ All files are properly formatted"
          fi

      - name: Run ESLint
        if: steps.changed-files.outputs.any_changed == 'true'
        id: eslint-check
        run: |
          echo "🔍 Running ESLint on changed files..."

          # 分离前端和后端文件
          BACKEND_FILES=""
          FRONTEND_FILES=""

          for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
            if [[ "$file" =~ \.(js|jsx|vue|cjs|mjs)$ ]] && [ -f "$file" ]; then
              if [[ "$file" == web/admin-spa/* ]]; then
                FRONTEND_FILES="$FRONTEND_FILES ${file#web/admin-spa/}"
              else
                BACKEND_FILES="$BACKEND_FILES $file"
              fi
            fi
          done

          ESLINT_FAILED=false
          ESLINT_OUTPUT=""

          # 检查后端文件
          if [ -n "$BACKEND_FILES" ]; then
            echo "Linting backend files: $BACKEND_FILES"
            set +e
            BACKEND_OUTPUT=$(npx eslint $BACKEND_FILES --format stylish 2>&1)
            BACKEND_EXIT_CODE=$?
            set -e
            
            if [ $BACKEND_EXIT_CODE -ne 0 ]; then
              ESLINT_FAILED=true
              ESLINT_OUTPUT="${ESLINT_OUTPUT}### Backend ESLint Issues\n\`\`\`\n${BACKEND_OUTPUT}\n\`\`\`\n\n"
            fi
          fi

          # 检查前端文件
          if [ -n "$FRONTEND_FILES" ]; then
            echo "Linting frontend files: $FRONTEND_FILES"
            cd web/admin-spa
            set +e
            FRONTEND_OUTPUT=$(npx eslint $FRONTEND_FILES --format stylish 2>&1)
            FRONTEND_EXIT_CODE=$?
            set -e
            cd ../..
            
            if [ $FRONTEND_EXIT_CODE -ne 0 ]; then
              ESLINT_FAILED=true
              ESLINT_OUTPUT="${ESLINT_OUTPUT}### Frontend ESLint Issues\n\`\`\`\n${FRONTEND_OUTPUT}\n\`\`\`\n\n"
            fi
          fi

          # 输出结果
          if [ "$ESLINT_FAILED" = true ]; then
            echo "eslint_failed=true" >> $GITHUB_OUTPUT
            echo "❌ ESLint found issues"
            
            # 创建错误报告
            echo "## ESLint Report" > eslint-report.md
            echo "$ESLINT_OUTPUT" >> eslint-report.md
            echo "" >> eslint-report.md
            echo "Please fix these issues by running:" >> eslint-report.md
            echo '```bash' >> eslint-report.md
            echo "# Backend: npm run lint" >> eslint-report.md
            echo "# Frontend: cd web/admin-spa && npm run lint" >> eslint-report.md
            echo '```' >> eslint-report.md
            
            exit 1
          else
            echo "eslint_failed=false" >> $GITHUB_OUTPUT
            echo "✅ ESLint check passed"
          fi

      - name: Debug PR Context
        if: failure()
        run: |
          echo "PR Number: ${{ github.event.pull_request.number }}"
          echo "Repo: ${{ github.repository }}"
          echo "Event Name: ${{ github.event_name }}"
          echo "Actor: ${{ github.actor }}"

      - name: Comment PR with results
        if: failure()
        continue-on-error: true # 即使评论失败也继续
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.GH_PAT || secrets.GITHUB_TOKEN }}
          script: |
            const fs = require('fs');
            let comment = '## 🚨 Code Quality Check Failed\n\n';

            // 读取 Prettier 报告
            if (fs.existsSync('prettier-report.md')) {
              const prettierReport = fs.readFileSync('prettier-report.md', 'utf8');
              comment += '### Prettier Formatting Issues\n\n';
              comment += prettierReport;
              comment += '\n**Fix command:**\n```bash\nnpm run format\n```\n\n';
            }

            // 读取 ESLint 报告
            if (fs.existsSync('eslint-report.md')) {
              const eslintReport = fs.readFileSync('eslint-report.md', 'utf8');
              comment += '### ESLint Issues\n\n';
              comment += eslintReport;
            }

            comment += '\n---\n';
            comment += '💡 **提示**: 在本地运行以下命令来自动修复大部分问题:\n';
            comment += '```bash\n';
            comment += '# 后端代码\n';
            comment += 'npm run format    # 修复后端 Prettier 格式问题\n';
            comment += 'npm run lint      # 修复后端 ESLint 问题\n';
            comment += '\n';
            comment += '# 前端代码\n';
            comment += 'cd web/admin-spa\n';
            comment += 'npm run format    # 修复前端 Prettier 格式问题\n';
            comment += 'npm run lint      # 修复前端 ESLint 问题\n';
            comment += '```\n';

            // 查找是否已有机器人评论
            const { data: comments } = await github.rest.issues.listComments({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
            });

            const botComment = comments.find(comment => 
              comment.user.type === 'Bot' && 
              comment.body.includes('Code Quality Check Failed')
            );

            if (botComment) {
              // 更新现有评论
              await github.rest.issues.updateComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                comment_id: botComment.id,
                body: comment
              });
            } else {
              // 创建新评论
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.issue.number,
                body: comment
              });
            }

      - name: Success comment
        if: success() && steps.changed-files.outputs.any_changed == 'true'
        continue-on-error: true # 即使评论失败也继续
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.GH_PAT || secrets.GITHUB_TOKEN }}
          script: |
            // 查找是否已有失败的评论
            const { data: comments } = await github.rest.issues.listComments({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
            });

            const botComment = comments.find(comment => 
              comment.user.type === 'Bot' && 
              comment.body.includes('Code Quality Check Failed')
            );

            if (botComment) {
              // 如果之前有失败评论,更新为成功
              await github.rest.issues.updateComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                comment_id: botComment.id,
                body: '## ✅ Code Quality Check Passed\n\nAll files are properly formatted and pass linting checks!'
              });
            }

The same workflow, on Latchkey

Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.

name: PR Lint and Format Check
 
on:
  pull_request:
    types: [opened, synchronize, reopened]
    paths:
      - '**.js'
      - '**.jsx'
      - '**.ts'
      - '**.tsx'
      - '**.vue'
      - '**.json'
      - '**.cjs'
      - '**.mjs'
      - '.prettierrc'
      - '.eslintrc.cjs'
      - 'package.json'
      - 'web/admin-spa/**'
 
permissions:
  contents: read
  pull-requests: write
  issues: write
 
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
 
jobs:
  lint-and-format:
    timeout-minutes: 30
    runs-on: latchkey-small
    name: Check Code Quality
 
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
 
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'
          cache: 'npm'
 
      - name: Cache dependencies
        uses: actions/cache@v3
        with:
          path: ~/.npm
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-
 
      - name: Install dependencies
        run: |
          npm ci --prefer-offline --no-audit
          # 安装 web 目录的依赖(如果存在)
          if [ -d "web/admin-spa" ] && [ -f "web/admin-spa/package.json" ]; then
            cd web/admin-spa
            npm ci --prefer-offline --no-audit
            cd ../..
          fi
 
      - name: Get changed files
        id: changed-files
        uses: tj-actions/changed-files@v41
        with:
          files: |
            **/*.js
            **/*.jsx
            **/*.ts
            **/*.tsx
            **/*.vue
            **/*.cjs
            **/*.mjs
            **/*.json
          files_ignore: |
            node_modules/**
            dist/**
            build/**
            coverage/**
            .git/**
            logs/**
            temp/**
            tmp/**
 
      - name: Check Prettier formatting
        if: steps.changed-files.outputs.any_changed == 'true'
        id: prettier-check
        run: |
          echo "🔍 Checking Prettier formatting for changed files..."
          echo "Changed files: ${{ steps.changed-files.outputs.all_changed_files }}"
 
          # 初始化标志
          PRETTIER_FAILED=false
          PRETTIER_OUTPUT=""
 
          # 检查每个改变的文件
          for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
            if [ -f "$file" ]; then
              echo "Checking: $file"
              
              # 根据文件位置选择正确的 prettier 配置
              if [[ "$file" == web/admin-spa/* ]]; then
                # 前端文件:进入前端目录运行 prettier
                cd web/admin-spa
                RELATIVE_FILE="${file#web/admin-spa/}"
                if ! npx prettier --check "$RELATIVE_FILE" 2>&1; then
                  PRETTIER_FAILED=true
                  DIFF=$(npx prettier "$RELATIVE_FILE" | diff -u "$RELATIVE_FILE" - || true)
                  if [ -n "$DIFF" ]; then
                    PRETTIER_OUTPUT="${PRETTIER_OUTPUT}❌ File needs formatting: $file\n"
                    PRETTIER_OUTPUT="${PRETTIER_OUTPUT}\`\`\`diff\n${DIFF}\n\`\`\`\n\n"
                  fi
                else
                  echo "✅ $file is properly formatted"
                fi
                cd ../..
              else
                # 后端文件:使用根目录的 prettier
                if ! npx prettier --check "$file" 2>&1; then
                  PRETTIER_FAILED=true
                  DIFF=$(npx prettier "$file" | diff -u "$file" - || true)
                  if [ -n "$DIFF" ]; then
                    PRETTIER_OUTPUT="${PRETTIER_OUTPUT}❌ File needs formatting: $file\n"
                    PRETTIER_OUTPUT="${PRETTIER_OUTPUT}\`\`\`diff\n${DIFF}\n\`\`\`\n\n"
                  fi
                else
                  echo "✅ $file is properly formatted"
                fi
              fi
            fi
          done
 
          # 输出结果
          if [ "$PRETTIER_FAILED" = true ]; then
            echo "prettier_failed=true" >> $GITHUB_OUTPUT
            echo -e "$PRETTIER_OUTPUT" > prettier-report.md
            echo "❌ Some files are not properly formatted."
            echo "Please run: npm run format (backend) or cd web/admin-spa && npm run format (frontend)"
            exit 1
          else
            echo "prettier_failed=false" >> $GITHUB_OUTPUT
            echo "✅ All files are properly formatted"
          fi
 
      - name: Run ESLint
        if: steps.changed-files.outputs.any_changed == 'true'
        id: eslint-check
        run: |
          echo "🔍 Running ESLint on changed files..."
 
          # 分离前端和后端文件
          BACKEND_FILES=""
          FRONTEND_FILES=""
 
          for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
            if [[ "$file" =~ \.(js|jsx|vue|cjs|mjs)$ ]] && [ -f "$file" ]; then
              if [[ "$file" == web/admin-spa/* ]]; then
                FRONTEND_FILES="$FRONTEND_FILES ${file#web/admin-spa/}"
              else
                BACKEND_FILES="$BACKEND_FILES $file"
              fi
            fi
          done
 
          ESLINT_FAILED=false
          ESLINT_OUTPUT=""
 
          # 检查后端文件
          if [ -n "$BACKEND_FILES" ]; then
            echo "Linting backend files: $BACKEND_FILES"
            set +e
            BACKEND_OUTPUT=$(npx eslint $BACKEND_FILES --format stylish 2>&1)
            BACKEND_EXIT_CODE=$?
            set -e
            
            if [ $BACKEND_EXIT_CODE -ne 0 ]; then
              ESLINT_FAILED=true
              ESLINT_OUTPUT="${ESLINT_OUTPUT}### Backend ESLint Issues\n\`\`\`\n${BACKEND_OUTPUT}\n\`\`\`\n\n"
            fi
          fi
 
          # 检查前端文件
          if [ -n "$FRONTEND_FILES" ]; then
            echo "Linting frontend files: $FRONTEND_FILES"
            cd web/admin-spa
            set +e
            FRONTEND_OUTPUT=$(npx eslint $FRONTEND_FILES --format stylish 2>&1)
            FRONTEND_EXIT_CODE=$?
            set -e
            cd ../..
            
            if [ $FRONTEND_EXIT_CODE -ne 0 ]; then
              ESLINT_FAILED=true
              ESLINT_OUTPUT="${ESLINT_OUTPUT}### Frontend ESLint Issues\n\`\`\`\n${FRONTEND_OUTPUT}\n\`\`\`\n\n"
            fi
          fi
 
          # 输出结果
          if [ "$ESLINT_FAILED" = true ]; then
            echo "eslint_failed=true" >> $GITHUB_OUTPUT
            echo "❌ ESLint found issues"
            
            # 创建错误报告
            echo "## ESLint Report" > eslint-report.md
            echo "$ESLINT_OUTPUT" >> eslint-report.md
            echo "" >> eslint-report.md
            echo "Please fix these issues by running:" >> eslint-report.md
            echo '```bash' >> eslint-report.md
            echo "# Backend: npm run lint" >> eslint-report.md
            echo "# Frontend: cd web/admin-spa && npm run lint" >> eslint-report.md
            echo '```' >> eslint-report.md
            
            exit 1
          else
            echo "eslint_failed=false" >> $GITHUB_OUTPUT
            echo "✅ ESLint check passed"
          fi
 
      - name: Debug PR Context
        if: failure()
        run: |
          echo "PR Number: ${{ github.event.pull_request.number }}"
          echo "Repo: ${{ github.repository }}"
          echo "Event Name: ${{ github.event_name }}"
          echo "Actor: ${{ github.actor }}"
 
      - name: Comment PR with results
        if: failure()
        continue-on-error: true # 即使评论失败也继续
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.GH_PAT || secrets.GITHUB_TOKEN }}
          script: |
            const fs = require('fs');
            let comment = '## 🚨 Code Quality Check Failed\n\n';
 
            // 读取 Prettier 报告
            if (fs.existsSync('prettier-report.md')) {
              const prettierReport = fs.readFileSync('prettier-report.md', 'utf8');
              comment += '### Prettier Formatting Issues\n\n';
              comment += prettierReport;
              comment += '\n**Fix command:**\n```bash\nnpm run format\n```\n\n';
            }
 
            // 读取 ESLint 报告
            if (fs.existsSync('eslint-report.md')) {
              const eslintReport = fs.readFileSync('eslint-report.md', 'utf8');
              comment += '### ESLint Issues\n\n';
              comment += eslintReport;
            }
 
            comment += '\n---\n';
            comment += '💡 **提示**: 在本地运行以下命令来自动修复大部分问题:\n';
            comment += '```bash\n';
            comment += '# 后端代码\n';
            comment += 'npm run format    # 修复后端 Prettier 格式问题\n';
            comment += 'npm run lint      # 修复后端 ESLint 问题\n';
            comment += '\n';
            comment += '# 前端代码\n';
            comment += 'cd web/admin-spa\n';
            comment += 'npm run format    # 修复前端 Prettier 格式问题\n';
            comment += 'npm run lint      # 修复前端 ESLint 问题\n';
            comment += '```\n';
 
            // 查找是否已有机器人评论
            const { data: comments } = await github.rest.issues.listComments({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
            });
 
            const botComment = comments.find(comment => 
              comment.user.type === 'Bot' && 
              comment.body.includes('Code Quality Check Failed')
            );
 
            if (botComment) {
              // 更新现有评论
              await github.rest.issues.updateComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                comment_id: botComment.id,
                body: comment
              });
            } else {
              // 创建新评论
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.issue.number,
                body: comment
              });
            }
 
      - name: Success comment
        if: success() && steps.changed-files.outputs.any_changed == 'true'
        continue-on-error: true # 即使评论失败也继续
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.GH_PAT || secrets.GITHUB_TOKEN }}
          script: |
            // 查找是否已有失败的评论
            const { data: comments } = await github.rest.issues.listComments({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
            });
 
            const botComment = comments.find(comment => 
              comment.user.type === 'Bot' && 
              comment.body.includes('Code Quality Check Failed')
            );
 
            if (botComment) {
              // 如果之前有失败评论,更新为成功
              await github.rest.issues.updateComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                comment_id: botComment.id,
                body: '## ✅ Code Quality Check Passed\n\nAll files are properly formatted and pass linting checks!'
              });
            }
 

What changed

1 third-party action is referenced by a movable tag. Pin it to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.

What Latchkey heals here

This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:

This workflow runs 1 job per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow