storybookjs/storybook

15 workflows · maturity 50% · 7 patterns · GitHub ↗

Security 11.67/100

Practices

✓ Matrix✓ Permissions○ Security scan○ AI review○ Cache✓ Concurrency○ Reusable workflows

Detected patterns

Security dimensions

permissions
11.7
security scan
0
supply chain
0
secret handling
0
harden runner
0

Workflows (15)

code-simplifier.lock perms .github/workflows/code-simplifier.lock.yml
Triggers
schedule, workflow_dispatch
Runs on
ubuntu-slim, ubuntu-latest, ubuntu-slim, ubuntu-slim, ubuntu-slim
Jobs
activation, agent, conclusion, pre_activation, safe_outputs
Actions
github/gh-aw/actions/setup, github/gh-aw/actions/setup, github/gh-aw/actions/setup, github/gh-aw/actions/setup, github/gh-aw/actions/setup
Commands
  • /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default
  • bash /opt/gh-aw/actions/create_prompt_first.sh { cat << 'GH_AW_PROMPT_EOF' <system> GH_AW_PROMPT_EOF cat "/opt/gh-aw/prompts/xpia.md" cat "/opt/gh-aw/prompts/temp_folder_prompt.md" cat "/opt/gh-aw/prompts/markdown.md" cat "/opt/gh-aw/prompts/safe_outputs_prompt.md" cat << 'GH_AW_PROMPT_EOF' <safe-output-tools> Tools: create_pull_request, missing_tool, missing_data, noop GH_AW_PROMPT_EOF cat "/opt/gh-aw/prompts/safe_outputs_create_pull_request.md" cat << 'GH_AW_PROMPT_EOF' </safe-output-tools> <github-context> The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} - **actor**: __GH_AW_GITHUB_ACTOR__ {{/if}} {{#if __GH_AW_GITHUB_REPOSITORY__ }} - **repository**: __GH_AW_GITHUB_REPOSITORY__ {{/if}} {{#if __GH_AW_GITHUB_WORKSPACE__ }} - **workspace**: __GH_AW_GITHUB_WORKSPACE__ {{/if}} {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ {{/if}} {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ {{/if}} {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ {{/if}} {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ {{/if}} {{#if __GH_AW_GITHUB_RUN_ID__ }} - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ {{/if}} </github-context> GH_AW_PROMPT_EOF cat << 'GH_AW_PROMPT_EOF' </system> GH_AW_PROMPT_EOF cat << 'GH_AW_PROMPT_EOF' {{#runtime-import .github/workflows/shared/mood.md}} GH_AW_PROMPT_EOF cat << 'GH_AW_PROMPT_EOF' {{#runtime-import .github/workflows/shared/reporting.md}} GH_AW_PROMPT_EOF cat << 'GH_AW_PROMPT_EOF' {{#runtime-import .github/workflows/code-simplifier.md}} GH_AW_PROMPT_EOF } > "$GH_AW_PROMPT"
  • bash /opt/gh-aw/actions/validate_prompt_placeholders.sh
  • bash /opt/gh-aw/actions/print_prompt_summary.sh
  • bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh
  • git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" git config --global am.keepcr true # Re-authenticate git with GitHub token SERVER_URL_STRIPPED="${SERVER_URL#https://}" git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" echo "Git configured with standard GitHub Actions identity"
  • /opt/gh-aw/actions/install_copilot_cli.sh latest
  • bash /opt/gh-aw/actions/install_awf_binary.sh v0.23.0
View raw YAML
#
#    ___                   _   _      
#   / _ \                 | | (_)     
#  | |_| | __ _  ___ _ __ | |_ _  ___ 
#  |  _  |/ _` |/ _ \ '_ \| __| |/ __|
#  | | | | (_| |  __/ | | | |_| | (__ 
#  \_| |_/\__, |\___|_| |_|\__|_|\___|
#          __/ |
#  _    _ |___/ 
# | |  | |                / _| |
# | |  | | ___ _ __ _  __| |_| | _____      ____
# | |/\| |/ _ \ '__| |/ /|  _| |/ _ \ \ /\ / / ___|
# \  /\  / (_) | | | | ( | | | | (_) \ V  V /\__ \
#  \/  \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/
#
# This file was automatically generated by gh-aw (v0.55.0). DO NOT EDIT.
#
# To update this file, edit github/gh-aw/.github/workflows/code-simplifier.md@852cb06ad52958b402ed982b69957ffc57ca0619 and run:
#   gh aw compile
# Not all edits will cause changes to this file.
#
# For more information: https://github.github.com/gh-aw/introduction/overview/
#
# Analyzes recently modified code and creates pull requests with simplifications that improve clarity, consistency, and maintainability while preserving functionality
#
# Source: github/gh-aw/.github/workflows/code-simplifier.md@852cb06ad52958b402ed982b69957ffc57ca0619
#
# Resolved workflow manifest:
#   Imports:
#     - shared/mood.md
#     - shared/reporting.md
#
# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"dbddcd7da0eefb6c24a3380b4e555d7aacd3ba78c14d79ebc131c33cb86f02ea","compiler_version":"v0.55.0","strict":true}

name: "Code Simplifier"
"on":
  schedule:
  - cron: "6 12 * * *"
    # Friendly format: daily (scattered)
  # skip-if-match: is:pr is:open in:title "[code-simplifier]" # Skip-if-match processed as search check in pre-activation job
  workflow_dispatch:

permissions: {}

concurrency:
  group: "gh-aw-${{ github.workflow }}"

run-name: "Code Simplifier"

jobs:
  activation:
    needs: pre_activation
    if: needs.pre_activation.outputs.activated == 'true'
    runs-on: ubuntu-slim
    permissions:
      contents: read
    outputs:
      comment_id: ""
      comment_repo: ""
      model: ${{ steps.generate_aw_info.outputs.model }}
      secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }}
    steps:
      - name: Setup Scripts
        uses: github/gh-aw/actions/setup@e211c855a20aa6cf9297b411466e1c382a8686db # v0.55.0
        with:
          destination: /opt/gh-aw/actions
      - name: Generate agentic run info
        id: generate_aw_info
        env:
          GH_AW_INFO_ENGINE_ID: "copilot"
          GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI"
          GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }}
          GH_AW_INFO_VERSION: ""
          GH_AW_INFO_AGENT_VERSION: "latest"
          GH_AW_INFO_CLI_VERSION: "v0.55.0"
          GH_AW_INFO_WORKFLOW_NAME: "Code Simplifier"
          GH_AW_INFO_EXPERIMENTAL: "false"
          GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true"
          GH_AW_INFO_STAGED: "false"
          GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]'
          GH_AW_INFO_FIREWALL_ENABLED: "true"
          GH_AW_INFO_AWF_VERSION: "v0.23.0"
          GH_AW_INFO_AWMG_VERSION: ""
          GH_AW_INFO_FIREWALL_TYPE: "squid"
          GH_AW_COMPILED_STRICT: "true"
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        with:
          script: |
            const { main } = require('/opt/gh-aw/actions/generate_aw_info.cjs');
            await main(core, context);
      - name: Validate COPILOT_GITHUB_TOKEN secret
        id: validate-secret
        run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default
        env:
          COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
      - name: Checkout .github and .agents folders
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          sparse-checkout: |
            .github
            .agents
          sparse-checkout-cone-mode: true
          fetch-depth: 1
          persist-credentials: false
      - name: Check workflow file timestamps
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_WORKFLOW_FILE: "code-simplifier.lock.yml"
        with:
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/check_workflow_timestamp_api.cjs');
            await main();
      - name: Create prompt with built-in context
        env:
          GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
          GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
          GH_AW_GITHUB_ACTOR: ${{ github.actor }}
          GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
          GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
          GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
          GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
          GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
          GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
          GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
        run: |
          bash /opt/gh-aw/actions/create_prompt_first.sh
          {
          cat << 'GH_AW_PROMPT_EOF'
          <system>
          GH_AW_PROMPT_EOF
          cat "/opt/gh-aw/prompts/xpia.md"
          cat "/opt/gh-aw/prompts/temp_folder_prompt.md"
          cat "/opt/gh-aw/prompts/markdown.md"
          cat "/opt/gh-aw/prompts/safe_outputs_prompt.md"
          cat << 'GH_AW_PROMPT_EOF'
          <safe-output-tools>
          Tools: create_pull_request, missing_tool, missing_data, noop
          GH_AW_PROMPT_EOF
          cat "/opt/gh-aw/prompts/safe_outputs_create_pull_request.md"
          cat << 'GH_AW_PROMPT_EOF'
          </safe-output-tools>
          <github-context>
          The following GitHub context information is available for this workflow:
          {{#if __GH_AW_GITHUB_ACTOR__ }}
          - **actor**: __GH_AW_GITHUB_ACTOR__
          {{/if}}
          {{#if __GH_AW_GITHUB_REPOSITORY__ }}
          - **repository**: __GH_AW_GITHUB_REPOSITORY__
          {{/if}}
          {{#if __GH_AW_GITHUB_WORKSPACE__ }}
          - **workspace**: __GH_AW_GITHUB_WORKSPACE__
          {{/if}}
          {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }}
          - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__
          {{/if}}
          {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }}
          - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__
          {{/if}}
          {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }}
          - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__
          {{/if}}
          {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }}
          - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__
          {{/if}}
          {{#if __GH_AW_GITHUB_RUN_ID__ }}
          - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
          {{/if}}
          </github-context>
          
          GH_AW_PROMPT_EOF
          cat << 'GH_AW_PROMPT_EOF'
          </system>
          GH_AW_PROMPT_EOF
          cat << 'GH_AW_PROMPT_EOF'
          {{#runtime-import .github/workflows/shared/mood.md}}
          GH_AW_PROMPT_EOF
          cat << 'GH_AW_PROMPT_EOF'
          {{#runtime-import .github/workflows/shared/reporting.md}}
          GH_AW_PROMPT_EOF
          cat << 'GH_AW_PROMPT_EOF'
          {{#runtime-import .github/workflows/code-simplifier.md}}
          GH_AW_PROMPT_EOF
          } > "$GH_AW_PROMPT"
      - name: Interpolate variables and render templates
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
          GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
          GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
        with:
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/interpolate_prompt.cjs');
            await main();
      - name: Substitute placeholders
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
          GH_AW_GITHUB_ACTOR: ${{ github.actor }}
          GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
          GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
          GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
          GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
          GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
          GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
          GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
          GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }}
        with:
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            
            const substitutePlaceholders = require('/opt/gh-aw/actions/substitute_placeholders.cjs');
            
            // Call the substitution function
            return await substitutePlaceholders({
              file: process.env.GH_AW_PROMPT,
              substitutions: {
                GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR,
                GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID,
                GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER,
                GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER,
                GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER,
                GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY,
                GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID,
                GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE,
                GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED
              }
            });
      - name: Validate prompt placeholders
        env:
          GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
        run: bash /opt/gh-aw/actions/validate_prompt_placeholders.sh
      - name: Print prompt
        env:
          GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
        run: bash /opt/gh-aw/actions/print_prompt_summary.sh
      - name: Upload activation artifact
        if: success()
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
        with:
          name: activation
          path: |
            /tmp/gh-aw/aw_info.json
            /tmp/gh-aw/aw-prompts/prompt.txt
          retention-days: 1

  agent:
    needs: activation
    runs-on: ubuntu-latest
    permissions:
      contents: read
      issues: read
      pull-requests: read
    concurrency:
      group: "gh-aw-copilot-${{ github.workflow }}"
    env:
      DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
      GH_AW_ASSETS_ALLOWED_EXTS: ""
      GH_AW_ASSETS_BRANCH: ""
      GH_AW_ASSETS_MAX_SIZE_KB: 0
      GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
      GH_AW_SAFE_OUTPUTS: /opt/gh-aw/safeoutputs/outputs.jsonl
      GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json
      GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json
      GH_AW_WORKFLOW_ID_SANITIZED: codesimplifier
    outputs:
      checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }}
      detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }}
      detection_success: ${{ steps.detection_conclusion.outputs.success }}
      has_patch: ${{ steps.collect_output.outputs.has_patch }}
      inference_access_error: ${{ steps.detect-inference-error.outputs.inference_access_error || 'false' }}
      model: ${{ needs.activation.outputs.model }}
      output: ${{ steps.collect_output.outputs.output }}
      output_types: ${{ steps.collect_output.outputs.output_types }}
    steps:
      - name: Setup Scripts
        uses: github/gh-aw/actions/setup@e211c855a20aa6cf9297b411466e1c382a8686db # v0.55.0
        with:
          destination: /opt/gh-aw/actions
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - name: Create gh-aw temp directory
        run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh
      - name: Configure Git credentials
        env:
          REPO_NAME: ${{ github.repository }}
          SERVER_URL: ${{ github.server_url }}
        run: |
          git config --global user.email "github-actions[bot]@users.noreply.github.com"
          git config --global user.name "github-actions[bot]"
          git config --global am.keepcr true
          # Re-authenticate git with GitHub token
          SERVER_URL_STRIPPED="${SERVER_URL#https://}"
          git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
          echo "Git configured with standard GitHub Actions identity"
      - name: Checkout PR branch
        id: checkout-pr
        if: |
          (github.event.pull_request) || (github.event.issue.pull_request)
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
        with:
          github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/checkout_pr_branch.cjs');
            await main();
      - name: Install GitHub Copilot CLI
        run: /opt/gh-aw/actions/install_copilot_cli.sh latest
      - name: Install awf binary
        run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.23.0
      - name: Determine automatic lockdown mode for GitHub MCP Server
        id: determine-automatic-lockdown
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
          GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
        with:
          script: |
            const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs');
            await determineAutomaticLockdown(github, context, core);
      - name: Download container images
        run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.23.0 ghcr.io/github/gh-aw-firewall/api-proxy:0.23.0 ghcr.io/github/gh-aw-firewall/squid:0.23.0 ghcr.io/github/gh-aw-mcpg:v0.1.8 ghcr.io/github/github-mcp-server:v0.31.0 node:lts-alpine
      - name: Write Safe Outputs Config
        run: |
          mkdir -p /opt/gh-aw/safeoutputs
          mkdir -p /tmp/gh-aw/safeoutputs
          mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
          cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF'
          {"create_pull_request":{"expires":24,"max":1,"reviewers":["copilot"],"title_prefix":"[code-simplifier] "},"missing_data":{},"missing_tool":{},"noop":{"max":1}}
          GH_AW_SAFE_OUTPUTS_CONFIG_EOF
          cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF'
          [
            {
              "description": "Create a new GitHub pull request to propose code changes. Use this after making file edits to submit them for review and merging. The PR will be created from the current branch with your committed changes. For code review comments on an existing PR, use create_pull_request_review_comment instead. CONSTRAINTS: Maximum 1 pull request(s) can be created. Title will be prefixed with \"[code-simplifier] \". Labels [\"refactoring\" \"code-quality\" \"automation\"] will be automatically added. Reviewers [\"copilot\"] will be assigned.",
              "inputSchema": {
                "additionalProperties": false,
                "properties": {
                  "body": {
                    "description": "Detailed PR description in Markdown. Include what changes were made, why, testing notes, and any breaking changes. Do NOT repeat the title as a heading.",
                    "type": "string"
                  },
                  "branch": {
                    "description": "Source branch name containing the changes. If omitted, uses the current working branch.",
                    "type": "string"
                  },
                  "draft": {
                    "description": "Whether to create the PR as a draft. Draft PRs cannot be merged until marked as ready for review. Use mark_pull_request_as_ready_for_review to convert a draft PR. Default: true.",
                    "type": "boolean"
                  },
                  "integrity": {
                    "description": "Trustworthiness level of the message source (e.g., \"low\", \"medium\", \"high\").",
                    "type": "string"
                  },
                  "labels": {
                    "description": "Labels to categorize the PR (e.g., 'enhancement', 'bugfix'). Labels must exist in the repository.",
                    "items": {
                      "type": "string"
                    },
                    "type": "array"
                  },
                  "repo": {
                    "description": "Target repository in 'owner/repo' format. For multi-repo workflows where the target repo differs from the workflow repo, this must match a repo in the allowed-repos list or the configured target-repo. If omitted, defaults to the configured target-repo (from safe-outputs config), NOT the workflow repository. In most cases, you should omit this parameter and let the system use the configured default.",
                    "type": "string"
                  },
                  "secrecy": {
                    "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\").",
                    "type": "string"
                  },
                  "title": {
                    "description": "Concise PR title describing the changes. Follow repository conventions (e.g., conventional commits). The title appears as the main heading.",
                    "type": "string"
                  }
                },
                "required": [
                  "title",
                  "body"
                ],
                "type": "object"
              },
              "name": "create_pull_request"
            },
            {
              "description": "Report that a tool or capability needed to complete the task is not available, or share any information you deem important about missing functionality or limitations. Use this when you cannot accomplish what was requested because the required functionality is missing or access is restricted.",
              "inputSchema": {
                "additionalProperties": false,
                "properties": {
                  "alternatives": {
                    "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).",
                    "type": "string"
                  },
                  "integrity": {
                    "description": "Trustworthiness level of the message source (e.g., \"low\", \"medium\", \"high\").",
                    "type": "string"
                  },
                  "reason": {
                    "description": "Explanation of why this tool is needed or what information you want to share about the limitation (max 256 characters).",
                    "type": "string"
                  },
                  "secrecy": {
                    "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\").",
                    "type": "string"
                  },
                  "tool": {
                    "description": "Optional: Name or description of the missing tool or capability (max 128 characters). Be specific about what functionality is needed.",
                    "type": "string"
                  }
                },
                "required": [
                  "reason"
                ],
                "type": "object"
              },
              "name": "missing_tool"
            },
            {
              "description": "Log a transparency message when no significant actions are needed. Use this to confirm workflow completion and provide visibility when analysis is complete but no changes or outputs are required (e.g., 'No issues found', 'All checks passed'). This ensures the workflow produces human-visible output even when no other actions are taken.",
              "inputSchema": {
                "additionalProperties": false,
                "properties": {
                  "integrity": {
                    "description": "Trustworthiness level of the message source (e.g., \"low\", \"medium\", \"high\").",
                    "type": "string"
                  },
                  "message": {
                    "description": "Status or completion message to log. Should explain what was analyzed and the outcome (e.g., 'Code review complete - no issues found', 'Analysis complete - all tests passing').",
                    "type": "string"
                  },
                  "secrecy": {
                    "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\").",
                    "type": "string"
                  }
                },
                "required": [
                  "message"
                ],
                "type": "object"
              },
              "name": "noop"
            },
            {
              "description": "Report that data or information needed to complete the task is not available. Use this when you cannot accomplish what was requested because required data, context, or information is missing.",
              "inputSchema": {
                "additionalProperties": false,
                "properties": {
                  "alternatives": {
                    "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).",
                    "type": "string"
                  },
                  "context": {
                    "description": "Additional context about the missing data or where it should come from (max 256 characters).",
                    "type": "string"
                  },
                  "data_type": {
                    "description": "Type or description of the missing data or information (max 128 characters). Be specific about what data is needed.",
                    "type": "string"
                  },
                  "integrity": {
                    "description": "Trustworthiness level of the message source (e.g., \"low\", \"medium\", \"high\").",
                    "type": "string"
                  },
                  "reason": {
                    "description": "Explanation of why this data is needed to complete the task (max 256 characters).",
                    "type": "string"
                  },
                  "secrecy": {
                    "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\").",
                    "type": "string"
                  }
                },
                "required": [],
                "type": "object"
              },
              "name": "missing_data"
            }
          ]
          GH_AW_SAFE_OUTPUTS_TOOLS_EOF
          cat > /opt/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_EOF'
          {
            "create_pull_request": {
              "defaultMax": 1,
              "fields": {
                "body": {
                  "required": true,
                  "type": "string",
                  "sanitize": true,
                  "maxLength": 65000
                },
                "branch": {
                  "required": true,
                  "type": "string",
                  "sanitize": true,
                  "maxLength": 256
                },
                "draft": {
                  "type": "boolean"
                },
                "labels": {
                  "type": "array",
                  "itemType": "string",
                  "itemSanitize": true,
                  "itemMaxLength": 128
                },
                "repo": {
                  "type": "string",
                  "maxLength": 256
                },
                "title": {
                  "required": true,
                  "type": "string",
                  "sanitize": true,
                  "maxLength": 128
                }
              }
            },
            "missing_data": {
              "defaultMax": 20,
              "fields": {
                "alternatives": {
                  "type": "string",
                  "sanitize": true,
                  "maxLength": 256
                },
                "context": {
                  "type": "string",
                  "sanitize": true,
                  "maxLength": 256
                },
                "data_type": {
                  "type": "string",
                  "sanitize": true,
                  "maxLength": 128
                },
                "reason": {
                  "type": "string",
                  "sanitize": true,
                  "maxLength": 256
                }
              }
            },
            "missing_tool": {
              "defaultMax": 20,
              "fields": {
                "alternatives": {
                  "type": "string",
                  "sanitize": true,
                  "maxLength": 512
                },
                "reason": {
                  "required": true,
                  "type": "string",
                  "sanitize": true,
                  "maxLength": 256
                },
                "tool": {
                  "type": "string",
                  "sanitize": true,
                  "maxLength": 128
                }
              }
            },
            "noop": {
              "defaultMax": 1,
              "fields": {
                "message": {
                  "required": true,
                  "type": "string",
                  "sanitize": true,
                  "maxLength": 65000
                }
              }
            }
          }
          GH_AW_SAFE_OUTPUTS_VALIDATION_EOF
      - name: Generate Safe Outputs MCP Server Config
        id: safe-outputs-config
        run: |
          # Generate a secure random API key (360 bits of entropy, 40+ chars)
          # Mask immediately to prevent timing vulnerabilities
          API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
          echo "::add-mask::${API_KEY}"
          
          PORT=3001
          
          # Set outputs for next steps
          {
            echo "safe_outputs_api_key=${API_KEY}"
            echo "safe_outputs_port=${PORT}"
          } >> "$GITHUB_OUTPUT"
          
          echo "Safe Outputs MCP server will run on port ${PORT}"
          
      - name: Start Safe Outputs MCP HTTP Server
        id: safe-outputs-start
        env:
          DEBUG: '*'
          GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }}
          GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }}
          GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json
          GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json
          GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
        run: |
          # Environment variables are set above to prevent template injection
          export DEBUG
          export GH_AW_SAFE_OUTPUTS_PORT
          export GH_AW_SAFE_OUTPUTS_API_KEY
          export GH_AW_SAFE_OUTPUTS_TOOLS_PATH
          export GH_AW_SAFE_OUTPUTS_CONFIG_PATH
          export GH_AW_MCP_LOG_DIR
          
          bash /opt/gh-aw/actions/start_safe_outputs_server.sh
          
      - name: Start MCP Gateway
        id: start-mcp-gateway
        env:
          GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
          GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }}
          GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }}
          GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }}
          GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
        run: |
          set -eo pipefail
          mkdir -p /tmp/gh-aw/mcp-config
          
          # Export gateway environment variables for MCP config and gateway script
          export MCP_GATEWAY_PORT="80"
          export MCP_GATEWAY_DOMAIN="host.docker.internal"
          MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
          echo "::add-mask::${MCP_GATEWAY_API_KEY}"
          export MCP_GATEWAY_API_KEY
          export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads"
          mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}"
          export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288"
          export DEBUG="*"
          
          export GH_AW_ENGINE="copilot"
          export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.1.8'
          
          mkdir -p /home/runner/.copilot
          cat << GH_AW_MCP_CONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh
          {
            "mcpServers": {
              "github": {
                "type": "stdio",
                "container": "ghcr.io/github/github-mcp-server:v0.31.0",
                "env": {
                  "GITHUB_LOCKDOWN_MODE": "$GITHUB_MCP_LOCKDOWN",
                  "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}",
                  "GITHUB_READ_ONLY": "1",
                  "GITHUB_TOOLSETS": "context,repos,issues,pull_requests"
                }
              },
              "safeoutputs": {
                "type": "http",
                "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT",
                "headers": {
                  "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}"
                }
              }
            },
            "gateway": {
              "port": $MCP_GATEWAY_PORT,
              "domain": "${MCP_GATEWAY_DOMAIN}",
              "apiKey": "${MCP_GATEWAY_API_KEY}",
              "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
            }
          }
          GH_AW_MCP_CONFIG_EOF
      - name: Download activation artifact
        uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8
        with:
          name: activation
          path: /tmp/gh-aw
      - name: Clean git credentials
        run: bash /opt/gh-aw/actions/clean_git_credentials.sh
      - name: Execute GitHub Copilot CLI
        id: agentic_execution
        # Copilot CLI tool arguments (sorted):
        timeout-minutes: 30
        run: |
          set -o pipefail
          touch /tmp/gh-aw/agent-step-summary.md
          # shellcheck disable=SC1003
          sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.23.0 --skip-pull --enable-api-proxy \
            -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-all-tools --allow-all-paths --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
        env:
          COPILOT_AGENT_RUNNER_TYPE: STANDALONE
          COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
          COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }}
          GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json
          GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
          GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
          GITHUB_API_URL: ${{ github.api_url }}
          GITHUB_HEAD_REF: ${{ github.head_ref }}
          GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
          GITHUB_REF_NAME: ${{ github.ref_name }}
          GITHUB_SERVER_URL: ${{ github.server_url }}
          GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md
          GITHUB_WORKSPACE: ${{ github.workspace }}
          XDG_CONFIG_HOME: /home/runner
      - name: Detect inference access error
        id: detect-inference-error
        if: always()
        continue-on-error: true
        run: bash /opt/gh-aw/actions/detect_inference_access_error.sh
      - name: Configure Git credentials
        env:
          REPO_NAME: ${{ github.repository }}
          SERVER_URL: ${{ github.server_url }}
        run: |
          git config --global user.email "github-actions[bot]@users.noreply.github.com"
          git config --global user.name "github-actions[bot]"
          git config --global am.keepcr true
          # Re-authenticate git with GitHub token
          SERVER_URL_STRIPPED="${SERVER_URL#https://}"
          git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
          echo "Git configured with standard GitHub Actions identity"
      - name: Copy Copilot session state files to logs
        if: always()
        continue-on-error: true
        run: |
          # Copy Copilot session state files to logs folder for artifact collection
          # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them
          SESSION_STATE_DIR="$HOME/.copilot/session-state"
          LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs"
          
          if [ -d "$SESSION_STATE_DIR" ]; then
            echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR"
            mkdir -p "$LOGS_DIR"
            cp -v "$SESSION_STATE_DIR"/*.jsonl "$LOGS_DIR/" 2>/dev/null || true
            echo "Session state files copied successfully"
          else
            echo "No session-state directory found at $SESSION_STATE_DIR"
          fi
      - name: Stop MCP Gateway
        if: always()
        continue-on-error: true
        env:
          MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
          MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
          GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }}
        run: |
          bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID"
      - name: Redact secrets in logs
        if: always()
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        with:
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/redact_secrets.cjs');
            await main();
        env:
          GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
          SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
          SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
          SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
          SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - name: Append agent step summary
        if: always()
        run: bash /opt/gh-aw/actions/append_agent_step_summary.sh
      - name: Upload Safe Outputs
        if: always()
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
        with:
          name: safe-output
          path: ${{ env.GH_AW_SAFE_OUTPUTS }}
          if-no-files-found: warn
      - name: Ingest agent output
        id: collect_output
        if: always()
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
          GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com"
          GITHUB_SERVER_URL: ${{ github.server_url }}
          GITHUB_API_URL: ${{ github.api_url }}
        with:
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/collect_ndjson_output.cjs');
            await main();
      - name: Upload sanitized agent output
        if: always() && env.GH_AW_AGENT_OUTPUT
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
        with:
          name: agent-output
          path: ${{ env.GH_AW_AGENT_OUTPUT }}
          if-no-files-found: warn
      - name: Upload engine output files
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
        with:
          name: agent_outputs
          path: |
            /tmp/gh-aw/sandbox/agent/logs/
            /tmp/gh-aw/redacted-urls.log
          if-no-files-found: ignore
      - name: Parse agent logs for step summary
        if: always()
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/
        with:
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/parse_copilot_log.cjs');
            await main();
      - name: Parse MCP Gateway logs for step summary
        if: always()
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        with:
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/parse_mcp_gateway_log.cjs');
            await main();
      - name: Print firewall logs
        if: always()
        continue-on-error: true
        env:
          AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs
        run: |
          # Fix permissions on firewall logs so they can be uploaded as artifacts
          # AWF runs with sudo, creating files owned by root
          sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true
          # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step)
          if command -v awf &> /dev/null; then
            awf logs summary | tee -a "$GITHUB_STEP_SUMMARY"
          else
            echo 'AWF binary not installed, skipping firewall log summary'
          fi
      - name: Upload agent artifacts
        if: always()
        continue-on-error: true
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
        with:
          name: agent-artifacts
          path: |
            /tmp/gh-aw/aw-prompts/prompt.txt
            /tmp/gh-aw/mcp-logs/
            /tmp/gh-aw/sandbox/firewall/logs/
            /tmp/gh-aw/agent-stdio.log
            /tmp/gh-aw/agent/
            /tmp/gh-aw/aw-*.patch
          if-no-files-found: ignore
      # --- Threat Detection (inline) ---
      - name: Check if detection needed
        id: detection_guard
        if: always()
        env:
          OUTPUT_TYPES: ${{ steps.collect_output.outputs.output_types }}
          HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }}
        run: |
          if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then
            echo "run_detection=true" >> "$GITHUB_OUTPUT"
            echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH"
          else
            echo "run_detection=false" >> "$GITHUB_OUTPUT"
            echo "Detection skipped: no agent outputs or patches to analyze"
          fi
      - name: Clear MCP configuration for detection
        if: always() && steps.detection_guard.outputs.run_detection == 'true'
        run: |
          rm -f /tmp/gh-aw/mcp-config/mcp-servers.json
          rm -f /home/runner/.copilot/mcp-config.json
          rm -f "$GITHUB_WORKSPACE/.gemini/settings.json"
      - name: Prepare threat detection files
        if: always() && steps.detection_guard.outputs.run_detection == 'true'
        run: |
          mkdir -p /tmp/gh-aw/threat-detection/aw-prompts
          cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true
          cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true
          for f in /tmp/gh-aw/aw-*.patch; do
            [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
          done
          echo "Prepared threat detection files:"
          ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true
      - name: Setup threat detection
        if: always() && steps.detection_guard.outputs.run_detection == 'true'
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          WORKFLOW_NAME: "Code Simplifier"
          WORKFLOW_DESCRIPTION: "Analyzes recently modified code and creates pull requests with simplifications that improve clarity, consistency, and maintainability while preserving functionality"
          HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }}
        with:
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/setup_threat_detection.cjs');
            await main();
      - name: Ensure threat-detection directory and log
        if: always() && steps.detection_guard.outputs.run_detection == 'true'
        run: |
          mkdir -p /tmp/gh-aw/threat-detection
          touch /tmp/gh-aw/threat-detection/detection.log
      - name: Execute GitHub Copilot CLI
        if: always() && steps.detection_guard.outputs.run_detection == 'true'
        id: detection_agentic_execution
        # Copilot CLI tool arguments (sorted):
        # --allow-tool shell(cat)
        # --allow-tool shell(grep)
        # --allow-tool shell(head)
        # --allow-tool shell(jq)
        # --allow-tool shell(ls)
        # --allow-tool shell(tail)
        # --allow-tool shell(wc)
        timeout-minutes: 20
        run: |
          set -o pipefail
          touch /tmp/gh-aw/agent-step-summary.md
          # shellcheck disable=SC1003
          sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,raw.githubusercontent.com,registry.npmjs.org,telemetry.enterprise.githubcopilot.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.23.0 --skip-pull --enable-api-proxy \
            -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(wc)'\'' --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
        env:
          COPILOT_AGENT_RUNNER_TYPE: STANDALONE
          COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
          COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }}
          GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
          GITHUB_API_URL: ${{ github.api_url }}
          GITHUB_HEAD_REF: ${{ github.head_ref }}
          GITHUB_REF_NAME: ${{ github.ref_name }}
          GITHUB_SERVER_URL: ${{ github.server_url }}
          GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md
          GITHUB_WORKSPACE: ${{ github.workspace }}
          XDG_CONFIG_HOME: /home/runner
      - name: Parse threat detection results
        id: parse_detection_results
        if: always() && steps.detection_guard.outputs.run_detection == 'true'
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        with:
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/parse_threat_detection_results.cjs');
            await main();
      - name: Upload threat detection log
        if: always() && steps.detection_guard.outputs.run_detection == 'true'
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
        with:
          name: threat-detection.log
          path: /tmp/gh-aw/threat-detection/detection.log
          if-no-files-found: ignore
      - name: Set detection conclusion
        id: detection_conclusion
        if: always()
        env:
          RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }}
          DETECTION_SUCCESS: ${{ steps.parse_detection_results.outputs.success }}
        run: |
          if [[ "$RUN_DETECTION" != "true" ]]; then
            echo "conclusion=skipped" >> "$GITHUB_OUTPUT"
            echo "success=true" >> "$GITHUB_OUTPUT"
            echo "Detection was not needed, marking as skipped"
          elif [[ "$DETECTION_SUCCESS" == "true" ]]; then
            echo "conclusion=success" >> "$GITHUB_OUTPUT"
            echo "success=true" >> "$GITHUB_OUTPUT"
            echo "Detection passed successfully"
          else
            echo "conclusion=failure" >> "$GITHUB_OUTPUT"
            echo "success=false" >> "$GITHUB_OUTPUT"
            echo "Detection found issues"
          fi

  conclusion:
    needs:
      - activation
      - agent
      - safe_outputs
    if: (always()) && (needs.agent.result != 'skipped')
    runs-on: ubuntu-slim
    permissions:
      contents: write
      issues: write
      pull-requests: write
    concurrency:
      group: "gh-aw-conclusion-code-simplifier"
      cancel-in-progress: false
    outputs:
      noop_message: ${{ steps.noop.outputs.noop_message }}
      tools_reported: ${{ steps.missing_tool.outputs.tools_reported }}
      total_count: ${{ steps.missing_tool.outputs.total_count }}
    steps:
      - name: Setup Scripts
        uses: github/gh-aw/actions/setup@e211c855a20aa6cf9297b411466e1c382a8686db # v0.55.0
        with:
          destination: /opt/gh-aw/actions
      - name: Download agent output artifact
        id: download-agent-output
        continue-on-error: true
        uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8
        with:
          name: agent-output
          path: /tmp/gh-aw/safeoutputs/
      - name: Setup agent output environment variable
        if: steps.download-agent-output.outcome == 'success'
        run: |
          mkdir -p /tmp/gh-aw/safeoutputs/
          find "/tmp/gh-aw/safeoutputs/" -type f -print
          echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
      - name: Process No-Op Messages
        id: noop
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
          GH_AW_NOOP_MAX: "1"
          GH_AW_WORKFLOW_NAME: "Code Simplifier"
          GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/code-simplifier.md@852cb06ad52958b402ed982b69957ffc57ca0619"
          GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/code-simplifier.md"
          GH_AW_TRACKER_ID: "code-simplifier"
        with:
          github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/noop.cjs');
            await main();
      - name: Record Missing Tool
        id: missing_tool
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
          GH_AW_WORKFLOW_NAME: "Code Simplifier"
          GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/code-simplifier.md@852cb06ad52958b402ed982b69957ffc57ca0619"
          GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/code-simplifier.md"
          GH_AW_TRACKER_ID: "code-simplifier"
        with:
          github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/missing_tool.cjs');
            await main();
      - name: Handle Agent Failure
        id: handle_agent_failure
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
          GH_AW_WORKFLOW_NAME: "Code Simplifier"
          GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/code-simplifier.md@852cb06ad52958b402ed982b69957ffc57ca0619"
          GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/code-simplifier.md"
          GH_AW_TRACKER_ID: "code-simplifier"
          GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
          GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
          GH_AW_WORKFLOW_ID: "code-simplifier"
          GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }}
          GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
          GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }}
          GH_AW_CODE_PUSH_FAILURE_ERRORS: ${{ needs.safe_outputs.outputs.code_push_failure_errors }}
          GH_AW_CODE_PUSH_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.code_push_failure_count }}
          GH_AW_GROUP_REPORTS: "false"
          GH_AW_TIMEOUT_MINUTES: "30"
        with:
          github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/handle_agent_failure.cjs');
            await main();
      - name: Handle No-Op Message
        id: handle_noop_message
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
          GH_AW_WORKFLOW_NAME: "Code Simplifier"
          GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/code-simplifier.md@852cb06ad52958b402ed982b69957ffc57ca0619"
          GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/code-simplifier.md"
          GH_AW_TRACKER_ID: "code-simplifier"
          GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
          GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
          GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }}
          GH_AW_NOOP_REPORT_AS_ISSUE: "true"
        with:
          github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/handle_noop_message.cjs');
            await main();
      - name: Handle Create Pull Request Error
        id: handle_create_pr_error
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
          GH_AW_WORKFLOW_NAME: "Code Simplifier"
          GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/code-simplifier.md@852cb06ad52958b402ed982b69957ffc57ca0619"
          GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/code-simplifier.md"
          GH_AW_TRACKER_ID: "code-simplifier"
          GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
        with:
          github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/handle_create_pr_error.cjs');
            await main();

  pre_activation:
    runs-on: ubuntu-slim
    outputs:
      activated: ${{ (steps.check_membership.outputs.is_team_member == 'true') && (steps.check_skip_if_match.outputs.skip_check_ok == 'true') }}
      matched_command: ''
    steps:
      - name: Setup Scripts
        uses: github/gh-aw/actions/setup@e211c855a20aa6cf9297b411466e1c382a8686db # v0.55.0
        with:
          destination: /opt/gh-aw/actions
      - name: Check team membership for workflow
        id: check_membership
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_REQUIRED_ROLES: admin,maintainer,write
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/check_membership.cjs');
            await main();
      - name: Check skip-if-match query
        id: check_skip_if_match
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_SKIP_QUERY: "is:pr is:open in:title \"[code-simplifier]\""
          GH_AW_WORKFLOW_NAME: "Code Simplifier"
          GH_AW_SKIP_MAX_MATCHES: "1"
        with:
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/check_skip_if_match.cjs');
            await main();

  safe_outputs:
    needs:
      - activation
      - agent
    if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (needs.agent.outputs.detection_success == 'true')
    runs-on: ubuntu-slim
    permissions:
      contents: write
      issues: write
      pull-requests: write
    timeout-minutes: 15
    env:
      GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/code-simplifier"
      GH_AW_ENGINE_ID: "copilot"
      GH_AW_TRACKER_ID: "code-simplifier"
      GH_AW_WORKFLOW_ID: "code-simplifier"
      GH_AW_WORKFLOW_NAME: "Code Simplifier"
      GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/code-simplifier.md@852cb06ad52958b402ed982b69957ffc57ca0619"
      GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/code-simplifier.md"
    outputs:
      code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }}
      code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }}
      create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}
      create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }}
      created_pr_number: ${{ steps.process_safe_outputs.outputs.created_pr_number }}
      created_pr_url: ${{ steps.process_safe_outputs.outputs.created_pr_url }}
      process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }}
      process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
    steps:
      - name: Setup Scripts
        uses: github/gh-aw/actions/setup@e211c855a20aa6cf9297b411466e1c382a8686db # v0.55.0
        with:
          destination: /opt/gh-aw/actions
      - name: Download agent output artifact
        id: download-agent-output
        continue-on-error: true
        uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8
        with:
          name: agent-output
          path: /tmp/gh-aw/safeoutputs/
      - name: Setup agent output environment variable
        if: steps.download-agent-output.outcome == 'success'
        run: |
          mkdir -p /tmp/gh-aw/safeoutputs/
          find "/tmp/gh-aw/safeoutputs/" -type f -print
          echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
      - name: Download patch artifact
        continue-on-error: true
        uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8
        with:
          name: agent-artifacts
          path: /tmp/gh-aw/
      - name: Checkout repository
        if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (contains(needs.agent.outputs.output_types, 'create_pull_request'))
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          ref: ${{ github.base_ref || github.event.pull_request.base.ref || github.ref_name || github.event.repository.default_branch }}
          token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
          persist-credentials: false
          fetch-depth: 1
      - name: Configure Git credentials
        if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (contains(needs.agent.outputs.output_types, 'create_pull_request'))
        env:
          REPO_NAME: ${{ github.repository }}
          SERVER_URL: ${{ github.server_url }}
          GIT_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
        run: |
          git config --global user.email "github-actions[bot]@users.noreply.github.com"
          git config --global user.name "github-actions[bot]"
          git config --global am.keepcr true
          # Re-authenticate git with GitHub token
          SERVER_URL_STRIPPED="${SERVER_URL#https://}"
          git remote set-url origin "https://x-access-token:${GIT_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
          echo "Git configured with standard GitHub Actions identity"
      - name: Process Safe Outputs
        id: process_safe_outputs
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
          GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com"
          GITHUB_SERVER_URL: ${{ github.server_url }}
          GITHUB_API_URL: ${{ github.api_url }}
          GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"expires\":24,\"labels\":[\"refactoring\",\"code-quality\",\"automation\"],\"max\":1,\"max_patch_size\":1024,\"reviewers\":[\"copilot\"],\"title_prefix\":\"[code-simplifier] \"},\"missing_data\":{},\"missing_tool\":{}}"
          GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }}
        with:
          github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/safe_output_handler_manager.cjs');
            await main();
      - name: Upload safe output items manifest
        if: always()
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
        with:
          name: safe-output-items
          path: /tmp/safe-output-items.jsonl
          if-no-files-found: warn

copilot-setup-steps .github/workflows/copilot-setup-steps.yml
Triggers
workflow_dispatch, push, pull_request
Runs on
ubuntu-latest
Jobs
copilot-setup-steps
Commands
  • yarn nx run-many --targets compile --no-cloud
View raw YAML
name: 'Copilot Setup Steps'

# Automatically run the setup steps when they are changed to allow for easy validation, and
# allow manual testing through the repository's "Actions" tab
on:
  workflow_dispatch:
  push:
    paths:
      - .github/workflows/copilot-setup-steps.yml
  pull_request:
    paths:
      - .github/workflows/copilot-setup-steps.yml

jobs:
  # The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot.
  copilot-setup-steps:
    runs-on: ubuntu-latest

    # Set the permissions to the lowest permissions possible needed for your steps.
    # Copilot will be given its own token for its operations.
    permissions:
      contents: read

    # You can define any steps you want, and they will run before the agent starts.
    # If you do not check out your code, Copilot will do this for you.
    steps:
      - name: Checkout code
        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0

      - name: Setup Node.js and Install Dependencies
        uses: ./.github/actions/setup-node-and-install
        with:
          install-code-deps: true

      - name: Compile
        run: yarn nx run-many --targets compile --no-cloud
cron-weekly perms .github/workflows/cron-weekly.yml
Triggers
schedule
Runs on
ubuntu-latest
Jobs
check-links
Actions
gaurav-nelson/github-action-markdown-link-check
Commands
  • curl -H "Content-Type: application/json" -X POST -d '{"content":"The Markdown Links Check workflow has failed in the repository: [storybook]"}' ${{ secrets.DISCORD_MONITORING_URL }}
View raw YAML
name: Markdown Links Check
# runs every monday at 9 am
on:
  schedule:
    - cron: "0 9 * * 1"

permissions:
  contents: read # to fetch repository files for markdown link checks

jobs:
  check-links:
    if: github.repository_owner == 'storybookjs'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: gaurav-nelson/github-action-markdown-link-check@v1
        # checks all markdown files from important folders including all subfolders
        with:
          # only show errors that occur instead of successful links + errors
          use-quiet-mode: "yes"
          # output full HTTP info for broken links
          use-verbose-mode: "yes"
          config-file: ".github/workflows/markdown-link-check-config.json"
      # Notify to Discord channel on failure
      - name: Send Discord Notification
        if: failure() # Only run this step if previous steps failed
        run: |
          curl -H "Content-Type: application/json" -X POST -d '{"content":"The Markdown Links Check workflow has failed in the repository: [storybook]"}' ${{ secrets.DISCORD_MONITORING_URL }}
danger-js .github/workflows/danger-js.yml
Triggers
pull_request
Runs on
ubuntu-latest
Jobs
dangerJS
Actions
docker://ghcr.io/danger/danger-js:13.0.5
View raw YAML
on:
  pull_request:
    types:
      - opened
      - synchronize
      - reopened
      - labeled
      - unlabeled
      - edited
    branches:
      - "**"

concurrency:
  group: ${{ github.workflow }}-${{ github.event.number }}
  cancel-in-progress: true

name: Danger JS
jobs:
  dangerJS:
    name: Danger JS
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Danger JS
        uses: docker://ghcr.io/danger/danger-js:13.0.5
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          args: --dangerfile scripts/dangerfile.js
duplicate-code-detector.lock perms .github/workflows/duplicate-code-detector.lock.yml
Triggers
schedule, workflow_dispatch
Runs on
ubuntu-slim, ubuntu-latest, ubuntu-slim, ubuntu-slim
Jobs
activation, agent, conclusion, safe_outputs
Actions
github/gh-aw/actions/setup, github/gh-aw/actions/setup, github/gh-aw/actions/setup, github/gh-aw/actions/setup
Commands
  • /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default
  • bash /opt/gh-aw/actions/create_prompt_first.sh { cat << 'GH_AW_PROMPT_EOF' <system> GH_AW_PROMPT_EOF cat "/opt/gh-aw/prompts/xpia.md" cat "/opt/gh-aw/prompts/temp_folder_prompt.md" cat "/opt/gh-aw/prompts/markdown.md" cat "/opt/gh-aw/prompts/safe_outputs_prompt.md" cat << 'GH_AW_PROMPT_EOF' <safe-output-tools> Tools: create_issue, missing_tool, missing_data, noop </safe-output-tools> <github-context> The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} - **actor**: __GH_AW_GITHUB_ACTOR__ {{/if}} {{#if __GH_AW_GITHUB_REPOSITORY__ }} - **repository**: __GH_AW_GITHUB_REPOSITORY__ {{/if}} {{#if __GH_AW_GITHUB_WORKSPACE__ }} - **workspace**: __GH_AW_GITHUB_WORKSPACE__ {{/if}} {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ {{/if}} {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ {{/if}} {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ {{/if}} {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ {{/if}} {{#if __GH_AW_GITHUB_RUN_ID__ }} - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ {{/if}} </github-context> GH_AW_PROMPT_EOF cat << 'GH_AW_PROMPT_EOF' </system> GH_AW_PROMPT_EOF cat << 'GH_AW_PROMPT_EOF' {{#runtime-import .github/workflows/shared/mood.md}} GH_AW_PROMPT_EOF cat << 'GH_AW_PROMPT_EOF' {{#runtime-import .github/workflows/duplicate-code-detector.md}} GH_AW_PROMPT_EOF } > "$GH_AW_PROMPT"
  • bash /opt/gh-aw/actions/validate_prompt_placeholders.sh
  • bash /opt/gh-aw/actions/print_prompt_summary.sh
  • bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh
  • git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" git config --global am.keepcr true # Re-authenticate git with GitHub token SERVER_URL_STRIPPED="${SERVER_URL#https://}" git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" echo "Git configured with standard GitHub Actions identity"
  • /opt/gh-aw/actions/install_copilot_cli.sh latest
  • bash /opt/gh-aw/actions/install_awf_binary.sh v0.23.0
View raw YAML
#
#    ___                   _   _      
#   / _ \                 | | (_)     
#  | |_| | __ _  ___ _ __ | |_ _  ___ 
#  |  _  |/ _` |/ _ \ '_ \| __| |/ __|
#  | | | | (_| |  __/ | | | |_| | (__ 
#  \_| |_/\__, |\___|_| |_|\__|_|\___|
#          __/ |
#  _    _ |___/ 
# | |  | |                / _| |
# | |  | | ___ _ __ _  __| |_| | _____      ____
# | |/\| |/ _ \ '__| |/ /|  _| |/ _ \ \ /\ / / ___|
# \  /\  / (_) | | | | ( | | | | (_) \ V  V /\__ \
#  \/  \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/
#
# This file was automatically generated by gh-aw (v0.55.0). DO NOT EDIT.
#
# To update this file, edit github/gh-aw/.github/workflows/duplicate-code-detector.md@852cb06ad52958b402ed982b69957ffc57ca0619 and run:
#   gh aw compile
# Not all edits will cause changes to this file.
#
# For more information: https://github.github.com/gh-aw/introduction/overview/
#
# Identifies duplicate code patterns across the codebase and suggests refactoring opportunities
#
# Source: github/gh-aw/.github/workflows/duplicate-code-detector.md@852cb06ad52958b402ed982b69957ffc57ca0619
#
# Resolved workflow manifest:
#   Imports:
#     - shared/mood.md
#
# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"8f718997fab9f4077b50cf09e67d1c93cb5d11105def34e97ec4d56929ca4323","compiler_version":"v0.55.0","strict":true}

name: "Duplicate Code Detector"
"on":
  schedule:
  - cron: "12 8 * * *"
    # Friendly format: daily (scattered)
  workflow_dispatch:

permissions: {}

concurrency:
  group: "gh-aw-${{ github.workflow }}"

run-name: "Duplicate Code Detector"

jobs:
  activation:
    runs-on: ubuntu-slim
    permissions:
      contents: read
    outputs:
      comment_id: ""
      comment_repo: ""
      model: ${{ steps.generate_aw_info.outputs.model }}
      secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }}
    steps:
      - name: Setup Scripts
        uses: github/gh-aw/actions/setup@e211c855a20aa6cf9297b411466e1c382a8686db # v0.55.0
        with:
          destination: /opt/gh-aw/actions
      - name: Generate agentic run info
        id: generate_aw_info
        env:
          GH_AW_INFO_ENGINE_ID: "copilot"
          GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI"
          GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }}
          GH_AW_INFO_VERSION: ""
          GH_AW_INFO_AGENT_VERSION: "latest"
          GH_AW_INFO_CLI_VERSION: "v0.55.0"
          GH_AW_INFO_WORKFLOW_NAME: "Duplicate Code Detector"
          GH_AW_INFO_EXPERIMENTAL: "false"
          GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true"
          GH_AW_INFO_STAGED: "false"
          GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]'
          GH_AW_INFO_FIREWALL_ENABLED: "true"
          GH_AW_INFO_AWF_VERSION: "v0.23.0"
          GH_AW_INFO_AWMG_VERSION: ""
          GH_AW_INFO_FIREWALL_TYPE: "squid"
          GH_AW_COMPILED_STRICT: "true"
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        with:
          script: |
            const { main } = require('/opt/gh-aw/actions/generate_aw_info.cjs');
            await main(core, context);
      - name: Validate COPILOT_GITHUB_TOKEN secret
        id: validate-secret
        run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default
        env:
          COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
      - name: Checkout .github and .agents folders
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          sparse-checkout: |
            .github
            .agents
          sparse-checkout-cone-mode: true
          fetch-depth: 1
          persist-credentials: false
      - name: Check workflow file timestamps
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_WORKFLOW_FILE: "duplicate-code-detector.lock.yml"
        with:
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/check_workflow_timestamp_api.cjs');
            await main();
      - name: Create prompt with built-in context
        env:
          GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
          GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
          GH_AW_GITHUB_ACTOR: ${{ github.actor }}
          GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
          GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
          GH_AW_GITHUB_EVENT_HEAD_COMMIT_ID: ${{ github.event.head_commit.id }}
          GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
          GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
          GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
          GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
          GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
        run: |
          bash /opt/gh-aw/actions/create_prompt_first.sh
          {
          cat << 'GH_AW_PROMPT_EOF'
          <system>
          GH_AW_PROMPT_EOF
          cat "/opt/gh-aw/prompts/xpia.md"
          cat "/opt/gh-aw/prompts/temp_folder_prompt.md"
          cat "/opt/gh-aw/prompts/markdown.md"
          cat "/opt/gh-aw/prompts/safe_outputs_prompt.md"
          cat << 'GH_AW_PROMPT_EOF'
          <safe-output-tools>
          Tools: create_issue, missing_tool, missing_data, noop
          </safe-output-tools>
          <github-context>
          The following GitHub context information is available for this workflow:
          {{#if __GH_AW_GITHUB_ACTOR__ }}
          - **actor**: __GH_AW_GITHUB_ACTOR__
          {{/if}}
          {{#if __GH_AW_GITHUB_REPOSITORY__ }}
          - **repository**: __GH_AW_GITHUB_REPOSITORY__
          {{/if}}
          {{#if __GH_AW_GITHUB_WORKSPACE__ }}
          - **workspace**: __GH_AW_GITHUB_WORKSPACE__
          {{/if}}
          {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }}
          - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__
          {{/if}}
          {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }}
          - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__
          {{/if}}
          {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }}
          - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__
          {{/if}}
          {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }}
          - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__
          {{/if}}
          {{#if __GH_AW_GITHUB_RUN_ID__ }}
          - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
          {{/if}}
          </github-context>
          
          GH_AW_PROMPT_EOF
          cat << 'GH_AW_PROMPT_EOF'
          </system>
          GH_AW_PROMPT_EOF
          cat << 'GH_AW_PROMPT_EOF'
          {{#runtime-import .github/workflows/shared/mood.md}}
          GH_AW_PROMPT_EOF
          cat << 'GH_AW_PROMPT_EOF'
          {{#runtime-import .github/workflows/duplicate-code-detector.md}}
          GH_AW_PROMPT_EOF
          } > "$GH_AW_PROMPT"
      - name: Interpolate variables and render templates
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
          GH_AW_GITHUB_ACTOR: ${{ github.actor }}
          GH_AW_GITHUB_EVENT_HEAD_COMMIT_ID: ${{ github.event.head_commit.id }}
          GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
          GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
        with:
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/interpolate_prompt.cjs');
            await main();
      - name: Substitute placeholders
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
          GH_AW_GITHUB_ACTOR: ${{ github.actor }}
          GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
          GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
          GH_AW_GITHUB_EVENT_HEAD_COMMIT_ID: ${{ github.event.head_commit.id }}
          GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
          GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
          GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
          GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
          GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
        with:
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            
            const substitutePlaceholders = require('/opt/gh-aw/actions/substitute_placeholders.cjs');
            
            // Call the substitution function
            return await substitutePlaceholders({
              file: process.env.GH_AW_PROMPT,
              substitutions: {
                GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR,
                GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID,
                GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER,
                GH_AW_GITHUB_EVENT_HEAD_COMMIT_ID: process.env.GH_AW_GITHUB_EVENT_HEAD_COMMIT_ID,
                GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER,
                GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER,
                GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY,
                GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID,
                GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE
              }
            });
      - name: Validate prompt placeholders
        env:
          GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
        run: bash /opt/gh-aw/actions/validate_prompt_placeholders.sh
      - name: Print prompt
        env:
          GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
        run: bash /opt/gh-aw/actions/print_prompt_summary.sh
      - name: Upload activation artifact
        if: success()
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
        with:
          name: activation
          path: |
            /tmp/gh-aw/aw_info.json
            /tmp/gh-aw/aw-prompts/prompt.txt
          retention-days: 1

  agent:
    needs: activation
    runs-on: ubuntu-latest
    permissions:
      contents: read
      issues: read
      pull-requests: read
    concurrency:
      group: "gh-aw-copilot-${{ github.workflow }}"
    env:
      DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
      GH_AW_ASSETS_ALLOWED_EXTS: ""
      GH_AW_ASSETS_BRANCH: ""
      GH_AW_ASSETS_MAX_SIZE_KB: 0
      GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
      GH_AW_SAFE_OUTPUTS: /opt/gh-aw/safeoutputs/outputs.jsonl
      GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json
      GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json
      GH_AW_WORKFLOW_ID_SANITIZED: duplicatecodedetector
    outputs:
      checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }}
      detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }}
      detection_success: ${{ steps.detection_conclusion.outputs.success }}
      has_patch: ${{ steps.collect_output.outputs.has_patch }}
      inference_access_error: ${{ steps.detect-inference-error.outputs.inference_access_error || 'false' }}
      model: ${{ needs.activation.outputs.model }}
      output: ${{ steps.collect_output.outputs.output }}
      output_types: ${{ steps.collect_output.outputs.output_types }}
    steps:
      - name: Setup Scripts
        uses: github/gh-aw/actions/setup@e211c855a20aa6cf9297b411466e1c382a8686db # v0.55.0
        with:
          destination: /opt/gh-aw/actions
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - name: Create gh-aw temp directory
        run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh
      - name: Configure Git credentials
        env:
          REPO_NAME: ${{ github.repository }}
          SERVER_URL: ${{ github.server_url }}
        run: |
          git config --global user.email "github-actions[bot]@users.noreply.github.com"
          git config --global user.name "github-actions[bot]"
          git config --global am.keepcr true
          # Re-authenticate git with GitHub token
          SERVER_URL_STRIPPED="${SERVER_URL#https://}"
          git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
          echo "Git configured with standard GitHub Actions identity"
      - name: Checkout PR branch
        id: checkout-pr
        if: |
          (github.event.pull_request) || (github.event.issue.pull_request)
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
        with:
          github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/checkout_pr_branch.cjs');
            await main();
      - name: Install GitHub Copilot CLI
        run: /opt/gh-aw/actions/install_copilot_cli.sh latest
      - name: Install awf binary
        run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.23.0
      - name: Determine automatic lockdown mode for GitHub MCP Server
        id: determine-automatic-lockdown
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
          GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
        with:
          script: |
            const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs');
            await determineAutomaticLockdown(github, context, core);
      - name: Download container images
        run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.23.0 ghcr.io/github/gh-aw-firewall/api-proxy:0.23.0 ghcr.io/github/gh-aw-firewall/squid:0.23.0 ghcr.io/github/gh-aw-mcpg:v0.1.8 ghcr.io/github/github-mcp-server:v0.31.0 ghcr.io/github/serena-mcp-server:latest node:lts-alpine
      - name: Write Safe Outputs Config
        run: |
          mkdir -p /opt/gh-aw/safeoutputs
          mkdir -p /tmp/gh-aw/safeoutputs
          mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
          cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF'
          {"create_issue":{"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}}
          GH_AW_SAFE_OUTPUTS_CONFIG_EOF
          cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF'
          [
            {
              "description": "Create a new GitHub issue for tracking bugs, feature requests, or tasks. Use this for actionable work items that need assignment, labeling, and status tracking. For reports, announcements, or status updates that don't require task tracking, use create_discussion instead. CONSTRAINTS: Maximum 1 issue(s) can be created. Assignees [\"copilot\"] will be automatically assigned.",
              "inputSchema": {
                "additionalProperties": false,
                "properties": {
                  "body": {
                    "description": "Detailed issue description in Markdown. Do NOT repeat the title as a heading since it already appears as the issue's h1. Include context, reproduction steps, or acceptance criteria as appropriate.",
                    "type": "string"
                  },
                  "integrity": {
                    "description": "Trustworthiness level of the message source (e.g., \"low\", \"medium\", \"high\").",
                    "type": "string"
                  },
                  "labels": {
                    "description": "Labels to categorize the issue (e.g., 'bug', 'enhancement'). Labels must exist in the repository.",
                    "items": {
                      "type": "string"
                    },
                    "type": "array"
                  },
                  "parent": {
                    "description": "Parent issue number for creating sub-issues. This is the numeric ID from the GitHub URL (e.g., 42 in github.com/owner/repo/issues/42). Can also be a temporary_id (e.g., 'aw_abc123', 'aw_Test123') from a previously created issue in the same workflow run.",
                    "type": [
                      "number",
                      "string"
                    ]
                  },
                  "secrecy": {
                    "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\").",
                    "type": "string"
                  },
                  "temporary_id": {
                    "description": "Unique temporary identifier for referencing this issue before it's created. Format: 'aw_' followed by 3 to 12 alphanumeric characters (e.g., 'aw_abc1', 'aw_Test123'). Use '#aw_ID' in body text to reference other issues by their temporary_id; these are replaced with actual issue numbers after creation.",
                    "pattern": "^aw_[A-Za-z0-9]{3,12}$",
                    "type": "string"
                  },
                  "title": {
                    "description": "Concise issue title summarizing the bug, feature, or task. The title appears as the main heading, so keep it brief and descriptive.",
                    "type": "string"
                  }
                },
                "required": [
                  "title",
                  "body"
                ],
                "type": "object"
              },
              "name": "create_issue"
            },
            {
              "description": "Report that a tool or capability needed to complete the task is not available, or share any information you deem important about missing functionality or limitations. Use this when you cannot accomplish what was requested because the required functionality is missing or access is restricted.",
              "inputSchema": {
                "additionalProperties": false,
                "properties": {
                  "alternatives": {
                    "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).",
                    "type": "string"
                  },
                  "integrity": {
                    "description": "Trustworthiness level of the message source (e.g., \"low\", \"medium\", \"high\").",
                    "type": "string"
                  },
                  "reason": {
                    "description": "Explanation of why this tool is needed or what information you want to share about the limitation (max 256 characters).",
                    "type": "string"
                  },
                  "secrecy": {
                    "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\").",
                    "type": "string"
                  },
                  "tool": {
                    "description": "Optional: Name or description of the missing tool or capability (max 128 characters). Be specific about what functionality is needed.",
                    "type": "string"
                  }
                },
                "required": [
                  "reason"
                ],
                "type": "object"
              },
              "name": "missing_tool"
            },
            {
              "description": "Log a transparency message when no significant actions are needed. Use this to confirm workflow completion and provide visibility when analysis is complete but no changes or outputs are required (e.g., 'No issues found', 'All checks passed'). This ensures the workflow produces human-visible output even when no other actions are taken.",
              "inputSchema": {
                "additionalProperties": false,
                "properties": {
                  "integrity": {
                    "description": "Trustworthiness level of the message source (e.g., \"low\", \"medium\", \"high\").",
                    "type": "string"
                  },
                  "message": {
                    "description": "Status or completion message to log. Should explain what was analyzed and the outcome (e.g., 'Code review complete - no issues found', 'Analysis complete - all tests passing').",
                    "type": "string"
                  },
                  "secrecy": {
                    "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\").",
                    "type": "string"
                  }
                },
                "required": [
                  "message"
                ],
                "type": "object"
              },
              "name": "noop"
            },
            {
              "description": "Report that data or information needed to complete the task is not available. Use this when you cannot accomplish what was requested because required data, context, or information is missing.",
              "inputSchema": {
                "additionalProperties": false,
                "properties": {
                  "alternatives": {
                    "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).",
                    "type": "string"
                  },
                  "context": {
                    "description": "Additional context about the missing data or where it should come from (max 256 characters).",
                    "type": "string"
                  },
                  "data_type": {
                    "description": "Type or description of the missing data or information (max 128 characters). Be specific about what data is needed.",
                    "type": "string"
                  },
                  "integrity": {
                    "description": "Trustworthiness level of the message source (e.g., \"low\", \"medium\", \"high\").",
                    "type": "string"
                  },
                  "reason": {
                    "description": "Explanation of why this data is needed to complete the task (max 256 characters).",
                    "type": "string"
                  },
                  "secrecy": {
                    "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\").",
                    "type": "string"
                  }
                },
                "required": [],
                "type": "object"
              },
              "name": "missing_data"
            }
          ]
          GH_AW_SAFE_OUTPUTS_TOOLS_EOF
          cat > /opt/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_EOF'
          {
            "create_issue": {
              "defaultMax": 1,
              "fields": {
                "body": {
                  "required": true,
                  "type": "string",
                  "sanitize": true,
                  "maxLength": 65000
                },
                "labels": {
                  "type": "array",
                  "itemType": "string",
                  "itemSanitize": true,
                  "itemMaxLength": 128
                },
                "parent": {
                  "issueOrPRNumber": true
                },
                "repo": {
                  "type": "string",
                  "maxLength": 256
                },
                "temporary_id": {
                  "type": "string"
                },
                "title": {
                  "required": true,
                  "type": "string",
                  "sanitize": true,
                  "maxLength": 128
                }
              }
            },
            "missing_data": {
              "defaultMax": 20,
              "fields": {
                "alternatives": {
                  "type": "string",
                  "sanitize": true,
                  "maxLength": 256
                },
                "context": {
                  "type": "string",
                  "sanitize": true,
                  "maxLength": 256
                },
                "data_type": {
                  "type": "string",
                  "sanitize": true,
                  "maxLength": 128
                },
                "reason": {
                  "type": "string",
                  "sanitize": true,
                  "maxLength": 256
                }
              }
            },
            "missing_tool": {
              "defaultMax": 20,
              "fields": {
                "alternatives": {
                  "type": "string",
                  "sanitize": true,
                  "maxLength": 512
                },
                "reason": {
                  "required": true,
                  "type": "string",
                  "sanitize": true,
                  "maxLength": 256
                },
                "tool": {
                  "type": "string",
                  "sanitize": true,
                  "maxLength": 128
                }
              }
            },
            "noop": {
              "defaultMax": 1,
              "fields": {
                "message": {
                  "required": true,
                  "type": "string",
                  "sanitize": true,
                  "maxLength": 65000
                }
              }
            }
          }
          GH_AW_SAFE_OUTPUTS_VALIDATION_EOF
      - name: Generate Safe Outputs MCP Server Config
        id: safe-outputs-config
        run: |
          # Generate a secure random API key (360 bits of entropy, 40+ chars)
          # Mask immediately to prevent timing vulnerabilities
          API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
          echo "::add-mask::${API_KEY}"
          
          PORT=3001
          
          # Set outputs for next steps
          {
            echo "safe_outputs_api_key=${API_KEY}"
            echo "safe_outputs_port=${PORT}"
          } >> "$GITHUB_OUTPUT"
          
          echo "Safe Outputs MCP server will run on port ${PORT}"
          
      - name: Start Safe Outputs MCP HTTP Server
        id: safe-outputs-start
        env:
          DEBUG: '*'
          GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }}
          GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }}
          GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json
          GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json
          GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
        run: |
          # Environment variables are set above to prevent template injection
          export DEBUG
          export GH_AW_SAFE_OUTPUTS_PORT
          export GH_AW_SAFE_OUTPUTS_API_KEY
          export GH_AW_SAFE_OUTPUTS_TOOLS_PATH
          export GH_AW_SAFE_OUTPUTS_CONFIG_PATH
          export GH_AW_MCP_LOG_DIR
          
          bash /opt/gh-aw/actions/start_safe_outputs_server.sh
          
      - name: Start MCP Gateway
        id: start-mcp-gateway
        env:
          GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
          GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }}
          GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }}
          GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }}
          GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
        run: |
          set -eo pipefail
          mkdir -p /tmp/gh-aw/mcp-config
          
          # Export gateway environment variables for MCP config and gateway script
          export MCP_GATEWAY_PORT="80"
          export MCP_GATEWAY_DOMAIN="host.docker.internal"
          MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
          echo "::add-mask::${MCP_GATEWAY_API_KEY}"
          export MCP_GATEWAY_API_KEY
          export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads"
          mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}"
          export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288"
          export DEBUG="*"
          
          export GH_AW_ENGINE="copilot"
          export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.1.8'
          
          mkdir -p /home/runner/.copilot
          cat << GH_AW_MCP_CONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh
          {
            "mcpServers": {
              "github": {
                "type": "stdio",
                "container": "ghcr.io/github/github-mcp-server:v0.31.0",
                "env": {
                  "GITHUB_LOCKDOWN_MODE": "$GITHUB_MCP_LOCKDOWN",
                  "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}",
                  "GITHUB_READ_ONLY": "1",
                  "GITHUB_TOOLSETS": "context,repos,issues,pull_requests"
                }
              },
              "safeoutputs": {
                "type": "http",
                "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT",
                "headers": {
                  "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}"
                }
              },
              "serena": {
                "type": "stdio",
                "container": "ghcr.io/github/serena-mcp-server:latest",
                "args": ["--network", "host"],
                "entrypoint": "serena",
                "entrypointArgs": ["start-mcp-server", "--context", "codex", "--project", "\${GITHUB_WORKSPACE}"],
                "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw"]
              }
            },
            "gateway": {
              "port": $MCP_GATEWAY_PORT,
              "domain": "${MCP_GATEWAY_DOMAIN}",
              "apiKey": "${MCP_GATEWAY_API_KEY}",
              "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
            }
          }
          GH_AW_MCP_CONFIG_EOF
      - name: Download activation artifact
        uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8
        with:
          name: activation
          path: /tmp/gh-aw
      - name: Clean git credentials
        run: bash /opt/gh-aw/actions/clean_git_credentials.sh
      - name: Execute GitHub Copilot CLI
        id: agentic_execution
        # Copilot CLI tool arguments (sorted):
        timeout-minutes: 15
        run: |
          set -o pipefail
          touch /tmp/gh-aw/agent-step-summary.md
          # shellcheck disable=SC1003
          sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.23.0 --skip-pull --enable-api-proxy \
            -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-all-tools --allow-all-paths --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
        env:
          COPILOT_AGENT_RUNNER_TYPE: STANDALONE
          COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
          COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }}
          GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json
          GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
          GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
          GITHUB_API_URL: ${{ github.api_url }}
          GITHUB_HEAD_REF: ${{ github.head_ref }}
          GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
          GITHUB_REF_NAME: ${{ github.ref_name }}
          GITHUB_SERVER_URL: ${{ github.server_url }}
          GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md
          GITHUB_WORKSPACE: ${{ github.workspace }}
          XDG_CONFIG_HOME: /home/runner
      - name: Detect inference access error
        id: detect-inference-error
        if: always()
        continue-on-error: true
        run: bash /opt/gh-aw/actions/detect_inference_access_error.sh
      - name: Configure Git credentials
        env:
          REPO_NAME: ${{ github.repository }}
          SERVER_URL: ${{ github.server_url }}
        run: |
          git config --global user.email "github-actions[bot]@users.noreply.github.com"
          git config --global user.name "github-actions[bot]"
          git config --global am.keepcr true
          # Re-authenticate git with GitHub token
          SERVER_URL_STRIPPED="${SERVER_URL#https://}"
          git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
          echo "Git configured with standard GitHub Actions identity"
      - name: Copy Copilot session state files to logs
        if: always()
        continue-on-error: true
        run: |
          # Copy Copilot session state files to logs folder for artifact collection
          # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them
          SESSION_STATE_DIR="$HOME/.copilot/session-state"
          LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs"
          
          if [ -d "$SESSION_STATE_DIR" ]; then
            echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR"
            mkdir -p "$LOGS_DIR"
            cp -v "$SESSION_STATE_DIR"/*.jsonl "$LOGS_DIR/" 2>/dev/null || true
            echo "Session state files copied successfully"
          else
            echo "No session-state directory found at $SESSION_STATE_DIR"
          fi
      - name: Stop MCP Gateway
        if: always()
        continue-on-error: true
        env:
          MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
          MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
          GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }}
        run: |
          bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID"
      - name: Redact secrets in logs
        if: always()
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        with:
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/redact_secrets.cjs');
            await main();
        env:
          GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
          SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
          SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
          SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
          SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - name: Append agent step summary
        if: always()
        run: bash /opt/gh-aw/actions/append_agent_step_summary.sh
      - name: Upload Safe Outputs
        if: always()
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
        with:
          name: safe-output
          path: ${{ env.GH_AW_SAFE_OUTPUTS }}
          if-no-files-found: warn
      - name: Ingest agent output
        id: collect_output
        if: always()
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
          GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com"
          GITHUB_SERVER_URL: ${{ github.server_url }}
          GITHUB_API_URL: ${{ github.api_url }}
        with:
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/collect_ndjson_output.cjs');
            await main();
      - name: Upload sanitized agent output
        if: always() && env.GH_AW_AGENT_OUTPUT
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
        with:
          name: agent-output
          path: ${{ env.GH_AW_AGENT_OUTPUT }}
          if-no-files-found: warn
      - name: Upload engine output files
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
        with:
          name: agent_outputs
          path: |
            /tmp/gh-aw/sandbox/agent/logs/
            /tmp/gh-aw/redacted-urls.log
          if-no-files-found: ignore
      - name: Parse agent logs for step summary
        if: always()
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/
        with:
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/parse_copilot_log.cjs');
            await main();
      - name: Parse MCP Gateway logs for step summary
        if: always()
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        with:
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/parse_mcp_gateway_log.cjs');
            await main();
      - name: Print firewall logs
        if: always()
        continue-on-error: true
        env:
          AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs
        run: |
          # Fix permissions on firewall logs so they can be uploaded as artifacts
          # AWF runs with sudo, creating files owned by root
          sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true
          # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step)
          if command -v awf &> /dev/null; then
            awf logs summary | tee -a "$GITHUB_STEP_SUMMARY"
          else
            echo 'AWF binary not installed, skipping firewall log summary'
          fi
      - name: Upload agent artifacts
        if: always()
        continue-on-error: true
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
        with:
          name: agent-artifacts
          path: |
            /tmp/gh-aw/aw-prompts/prompt.txt
            /tmp/gh-aw/mcp-logs/
            /tmp/gh-aw/sandbox/firewall/logs/
            /tmp/gh-aw/agent-stdio.log
            /tmp/gh-aw/agent/
          if-no-files-found: ignore
      # --- Threat Detection (inline) ---
      - name: Check if detection needed
        id: detection_guard
        if: always()
        env:
          OUTPUT_TYPES: ${{ steps.collect_output.outputs.output_types }}
          HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }}
        run: |
          if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then
            echo "run_detection=true" >> "$GITHUB_OUTPUT"
            echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH"
          else
            echo "run_detection=false" >> "$GITHUB_OUTPUT"
            echo "Detection skipped: no agent outputs or patches to analyze"
          fi
      - name: Clear MCP configuration for detection
        if: always() && steps.detection_guard.outputs.run_detection == 'true'
        run: |
          rm -f /tmp/gh-aw/mcp-config/mcp-servers.json
          rm -f /home/runner/.copilot/mcp-config.json
          rm -f "$GITHUB_WORKSPACE/.gemini/settings.json"
      - name: Prepare threat detection files
        if: always() && steps.detection_guard.outputs.run_detection == 'true'
        run: |
          mkdir -p /tmp/gh-aw/threat-detection/aw-prompts
          cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true
          cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true
          for f in /tmp/gh-aw/aw-*.patch; do
            [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
          done
          echo "Prepared threat detection files:"
          ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true
      - name: Setup threat detection
        if: always() && steps.detection_guard.outputs.run_detection == 'true'
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          WORKFLOW_NAME: "Duplicate Code Detector"
          WORKFLOW_DESCRIPTION: "Identifies duplicate code patterns across the codebase and suggests refactoring opportunities"
          HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }}
        with:
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/setup_threat_detection.cjs');
            await main();
      - name: Ensure threat-detection directory and log
        if: always() && steps.detection_guard.outputs.run_detection == 'true'
        run: |
          mkdir -p /tmp/gh-aw/threat-detection
          touch /tmp/gh-aw/threat-detection/detection.log
      - name: Execute GitHub Copilot CLI
        if: always() && steps.detection_guard.outputs.run_detection == 'true'
        id: detection_agentic_execution
        # Copilot CLI tool arguments (sorted):
        # --allow-tool shell(cat)
        # --allow-tool shell(grep)
        # --allow-tool shell(head)
        # --allow-tool shell(jq)
        # --allow-tool shell(ls)
        # --allow-tool shell(tail)
        # --allow-tool shell(wc)
        timeout-minutes: 20
        run: |
          set -o pipefail
          touch /tmp/gh-aw/agent-step-summary.md
          # shellcheck disable=SC1003
          sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,raw.githubusercontent.com,registry.npmjs.org,telemetry.enterprise.githubcopilot.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.23.0 --skip-pull --enable-api-proxy \
            -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(wc)'\'' --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
        env:
          COPILOT_AGENT_RUNNER_TYPE: STANDALONE
          COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
          COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }}
          GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
          GITHUB_API_URL: ${{ github.api_url }}
          GITHUB_HEAD_REF: ${{ github.head_ref }}
          GITHUB_REF_NAME: ${{ github.ref_name }}
          GITHUB_SERVER_URL: ${{ github.server_url }}
          GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md
          GITHUB_WORKSPACE: ${{ github.workspace }}
          XDG_CONFIG_HOME: /home/runner
      - name: Parse threat detection results
        id: parse_detection_results
        if: always() && steps.detection_guard.outputs.run_detection == 'true'
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        with:
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/parse_threat_detection_results.cjs');
            await main();
      - name: Upload threat detection log
        if: always() && steps.detection_guard.outputs.run_detection == 'true'
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
        with:
          name: threat-detection.log
          path: /tmp/gh-aw/threat-detection/detection.log
          if-no-files-found: ignore
      - name: Set detection conclusion
        id: detection_conclusion
        if: always()
        env:
          RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }}
          DETECTION_SUCCESS: ${{ steps.parse_detection_results.outputs.success }}
        run: |
          if [[ "$RUN_DETECTION" != "true" ]]; then
            echo "conclusion=skipped" >> "$GITHUB_OUTPUT"
            echo "success=true" >> "$GITHUB_OUTPUT"
            echo "Detection was not needed, marking as skipped"
          elif [[ "$DETECTION_SUCCESS" == "true" ]]; then
            echo "conclusion=success" >> "$GITHUB_OUTPUT"
            echo "success=true" >> "$GITHUB_OUTPUT"
            echo "Detection passed successfully"
          else
            echo "conclusion=failure" >> "$GITHUB_OUTPUT"
            echo "success=false" >> "$GITHUB_OUTPUT"
            echo "Detection found issues"
          fi

  conclusion:
    needs:
      - activation
      - agent
      - safe_outputs
    if: (always()) && (needs.agent.result != 'skipped')
    runs-on: ubuntu-slim
    permissions:
      contents: read
      issues: write
    concurrency:
      group: "gh-aw-conclusion-duplicate-code-detector"
      cancel-in-progress: false
    outputs:
      noop_message: ${{ steps.noop.outputs.noop_message }}
      tools_reported: ${{ steps.missing_tool.outputs.tools_reported }}
      total_count: ${{ steps.missing_tool.outputs.total_count }}
    steps:
      - name: Setup Scripts
        uses: github/gh-aw/actions/setup@e211c855a20aa6cf9297b411466e1c382a8686db # v0.55.0
        with:
          destination: /opt/gh-aw/actions
      - name: Download agent output artifact
        id: download-agent-output
        continue-on-error: true
        uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8
        with:
          name: agent-output
          path: /tmp/gh-aw/safeoutputs/
      - name: Setup agent output environment variable
        if: steps.download-agent-output.outcome == 'success'
        run: |
          mkdir -p /tmp/gh-aw/safeoutputs/
          find "/tmp/gh-aw/safeoutputs/" -type f -print
          echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
      - name: Process No-Op Messages
        id: noop
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
          GH_AW_NOOP_MAX: "1"
          GH_AW_WORKFLOW_NAME: "Duplicate Code Detector"
          GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/duplicate-code-detector.md@852cb06ad52958b402ed982b69957ffc57ca0619"
          GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/duplicate-code-detector.md"
        with:
          github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/noop.cjs');
            await main();
      - name: Record Missing Tool
        id: missing_tool
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
          GH_AW_WORKFLOW_NAME: "Duplicate Code Detector"
          GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/duplicate-code-detector.md@852cb06ad52958b402ed982b69957ffc57ca0619"
          GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/duplicate-code-detector.md"
        with:
          github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/missing_tool.cjs');
            await main();
      - name: Handle Agent Failure
        id: handle_agent_failure
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
          GH_AW_WORKFLOW_NAME: "Duplicate Code Detector"
          GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/duplicate-code-detector.md@852cb06ad52958b402ed982b69957ffc57ca0619"
          GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/duplicate-code-detector.md"
          GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
          GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
          GH_AW_WORKFLOW_ID: "duplicate-code-detector"
          GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }}
          GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
          GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }}
          GH_AW_GROUP_REPORTS: "false"
          GH_AW_TIMEOUT_MINUTES: "15"
        with:
          github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/handle_agent_failure.cjs');
            await main();
      - name: Handle No-Op Message
        id: handle_noop_message
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
          GH_AW_WORKFLOW_NAME: "Duplicate Code Detector"
          GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/duplicate-code-detector.md@852cb06ad52958b402ed982b69957ffc57ca0619"
          GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/duplicate-code-detector.md"
          GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
          GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
          GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }}
          GH_AW_NOOP_REPORT_AS_ISSUE: "true"
        with:
          github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/handle_noop_message.cjs');
            await main();

  safe_outputs:
    needs: agent
    if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (needs.agent.outputs.detection_success == 'true')
    runs-on: ubuntu-slim
    permissions:
      contents: read
      issues: write
    timeout-minutes: 15
    env:
      GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/duplicate-code-detector"
      GH_AW_ENGINE_ID: "copilot"
      GH_AW_WORKFLOW_ID: "duplicate-code-detector"
      GH_AW_WORKFLOW_NAME: "Duplicate Code Detector"
      GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/duplicate-code-detector.md@852cb06ad52958b402ed982b69957ffc57ca0619"
      GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/tree/852cb06ad52958b402ed982b69957ffc57ca0619/.github/workflows/duplicate-code-detector.md"
    outputs:
      code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }}
      code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }}
      create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}
      create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }}
      created_issue_number: ${{ steps.process_safe_outputs.outputs.created_issue_number }}
      created_issue_url: ${{ steps.process_safe_outputs.outputs.created_issue_url }}
      process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }}
      process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
    steps:
      - name: Setup Scripts
        uses: github/gh-aw/actions/setup@e211c855a20aa6cf9297b411466e1c382a8686db # v0.55.0
        with:
          destination: /opt/gh-aw/actions
      - name: Download agent output artifact
        id: download-agent-output
        continue-on-error: true
        uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8
        with:
          name: agent-output
          path: /tmp/gh-aw/safeoutputs/
      - name: Setup agent output environment variable
        if: steps.download-agent-output.outcome == 'success'
        run: |
          mkdir -p /tmp/gh-aw/safeoutputs/
          find "/tmp/gh-aw/safeoutputs/" -type f -print
          echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
      - name: Process Safe Outputs
        id: process_safe_outputs
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
          GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com"
          GITHUB_SERVER_URL: ${{ github.server_url }}
          GITHUB_API_URL: ${{ github.api_url }}
          GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"assignees\":[\"copilot\"],\"max\":1},\"missing_data\":{},\"missing_tool\":{}}"
          GH_AW_ASSIGN_COPILOT: "true"
        with:
          github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/safe_output_handler_manager.cjs');
            await main();
      - name: Assign Copilot to created issues
        if: steps.process_safe_outputs.outputs.issues_to_assign_copilot != ''
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
        env:
          GH_AW_ISSUES_TO_ASSIGN_COPILOT: ${{ steps.process_safe_outputs.outputs.issues_to_assign_copilot }}
        with:
          github-token: ${{ secrets.GH_AW_AGENT_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
          script: |
            const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io);
            const { main } = require('/opt/gh-aw/actions/assign_copilot_to_created_issues.cjs');
            await main();
      - name: Upload safe output items manifest
        if: always()
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
        with:
          name: safe-output-items
          path: /tmp/safe-output-items.jsonl
          if-no-files-found: warn

fork-checks matrix .github/workflows/fork-checks.yml
Triggers
push
Runs on
ubuntu-latest, ubuntu-latest, ${{ matrix.os }}
Jobs
check, formatting, test
Matrix
os→ ubuntu-latest, windows-latest
Commands
  • yarn task --task check
  • cd code && yarn lint:fmt
  • yarn task --task compile --start-from=compile
  • cd code && yarn exec playwright install chromium --with-deps
  • yarn test
View raw YAML
name: Fork checks

# This workflow is only for forks, so they can get basic checks in without a CircleCI API key
on:
  push:

env:
  NODE_OPTIONS: '--max_old_space_size=4096'

jobs:
  check:
    name: Core Type Checking
    if: github.repository_owner != 'storybookjs'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2

      - name: Setup Node.js and Install Dependencies
        uses: ./.github/actions/setup-node-and-install
        with:
          install-code-deps: true

      - name: check
        run: yarn task --task check

  formatting:
    name: Core Formatting
    if: github.repository_owner != 'storybookjs'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2

      - name: Setup Node.js and Install Dependencies
        uses: ./.github/actions/setup-node-and-install
        with:
          install-code-deps: true

      - name: oxfmt
        run: cd code && yarn lint:fmt

  test:
    strategy:
      matrix:
        os: [windows-latest, ubuntu-latest]
    runs-on: ${{ matrix.os }}
    name: Core Unit Tests, ${{ matrix.os }}
    if: github.repository_owner != 'storybookjs'
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2

      - name: Setup Node.js and Install Dependencies
        uses: ./.github/actions/setup-node-and-install
        with:
          install-code-deps: true

      - name: compile
        run: yarn task --task compile --start-from=compile

      - name: Install Playwright Dependencies
        run: cd code && yarn exec playwright install chromium --with-deps

      - name: test
        run: yarn test
generate-sandboxes .github/workflows/generate-sandboxes.yml
Triggers
schedule, workflow_dispatch
Runs on
ubuntu-latest, ubuntu-latest
Jobs
generate-next, generate-main
Actions
Ilshidur/action-discord, Ilshidur/action-discord
Commands
  • sudo rm -rf \ /opt/ghc \ /opt/google/chrome \ /opt/microsoft/msedge \ /opt/microsoft/powershell \ /usr/lib/mono \ /usr/local/julia* \ /usr/local/lib/android \ /usr/local/share/chromium \ /usr/local/share/powershell \ /usr/local/share/powershell \ /usr/share/dotnet \ /usr/share/swift
  • git config --global user.name "storybook-bot" git config --global user.email "32066757+storybook-bot@users.noreply.github.com"
  • node --experimental-modules ./check-dependencies.js
  • yarn task --task compile --start-from=auto --no-link
  • yarn local-registry --publish
  • yarn local-registry --open &
  • yarn wait-on tcp:127.0.0.1:6001
  • yarn generate-sandboxes --local-registry
View raw YAML
name: Generate and publish sandboxes

on:
  schedule:
    - cron: '2 2 */1 * *'
  workflow_dispatch:
  # To test fixes on push rather than wait for the scheduling, do the following:
  # 1. Uncomment the lines below and add your branch.
  # push:
  #   branches:
  #     - <your-branch-name>
  # 2. Change the "ref" value to <your-branch-name> in the actions/checkout step below.
  # 3. Comment out the whole "generate-main" job starting at line 77
  # 4. 👉 DON'T FORGET TO UNDO THE STEPS BEFORE YOU MERGE YOUR CHANGES!

env:
  YARN_ENABLE_IMMUTABLE_INSTALLS: 'false'
  CLEANUP_SANDBOX_NODE_MODULES: 'true'
  NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}

defaults:
  run:
    working-directory: ./code

jobs:
  generate-next:
    name: Generate to next
    if: github.repository_owner == 'storybookjs'
    runs-on: ubuntu-latest
    steps:
      - name: Remove unnecessary files to release disk space
        working-directory: ${{ github.workspace }}
        run: |
          sudo rm -rf \
            /opt/ghc \
            /opt/google/chrome \
            /opt/microsoft/msedge \
            /opt/microsoft/powershell \
            /usr/lib/mono \
            /usr/local/julia* \
            /usr/local/lib/android \
            /usr/local/share/chromium \
            /usr/local/share/powershell \
            /usr/local/share/powershell \
            /usr/share/dotnet \
            /usr/share/swift

      - uses: actions/checkout@v4
        with:
          ref: next

      - uses: actions/setup-node@v4
        with:
          node-version-file: '.nvmrc'

      - name: Setup git user
        run: |
          git config --global user.name "storybook-bot"
          git config --global user.email "32066757+storybook-bot@users.noreply.github.com"

      - name: Install dependencies
        working-directory: ./scripts
        run: node --experimental-modules ./check-dependencies.js

      - name: Compile Storybook libraries
        run: yarn task --task compile --start-from=auto --no-link

      - name: Publish to local registry
        run: yarn local-registry --publish

      - name: Run local registry
        run: yarn local-registry --open &

      - name: Wait for registry
        run: yarn wait-on tcp:127.0.0.1:6001

      - name: Generate
        id: generate
        run: yarn generate-sandboxes --local-registry

      - name: Publish
        # publish sandboxes even if the generation fails, as some sandboxes might have been generated successfully
        if: ${{ !cancelled() }}
        run: yarn publish-sandboxes --remote=https://storybook-bot:${{ secrets.PAT_STORYBOOK_BOT }}@github.com/storybookjs/sandboxes.git --push --branch=next

      - name: Report failure to Discord
        if: failure()
        env:
          DISCORD_WEBHOOK: ${{ secrets.DISCORD_MONITORING_URL }}
        uses: Ilshidur/action-discord@d2594079a10f1d6739ee50a2471f0ca57418b554
        with:
          args: |
            The generation of some or all sandboxes on the **next** branch has failed.
            [See the job summary for details](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})

  generate-main:
    name: Generate to main
    if: github.repository_owner == 'storybookjs'
    runs-on: ubuntu-latest
    steps:
      - name: Remove unnecessary files to release disk space
        working-directory: ${{ github.workspace }}
        run: |
          sudo rm -rf \
            /opt/ghc \
            /opt/google/chrome \
            /opt/microsoft/msedge \
            /opt/microsoft/powershell \
            /usr/lib/mono \
            /usr/local/julia* \
            /usr/local/lib/android \
            /usr/local/share/chromium \
            /usr/local/share/powershell \
            /usr/local/share/powershell \
            /usr/share/dotnet \
            /usr/share/swift
      - uses: actions/checkout@v4
        with:
          ref: main

      - uses: actions/setup-node@v4
        with:
          node-version-file: '.nvmrc'

      - name: Setup git user
        run: |
          git config --global user.name "storybook-bot"
          git config --global user.email "32066757+storybook-bot@users.noreply.github.com"

      - name: Install dependencies
        working-directory: ./scripts
        run: node --experimental-modules ./check-dependencies.js

      - name: Compile Storybook libraries
        run: yarn task --task compile --start-from=auto --no-link

      - name: Publish to local registry
        run: yarn local-registry --publish

      - name: Run local registry
        run: yarn local-registry --open &

      - name: Wait for registry
        run: yarn wait-on tcp:127.0.0.1:6001

      - name: Generate
        id: generate
        run: yarn generate-sandboxes --local-registry

      - name: Publish
        # publish sandboxes even if the generation fails, as some sandboxes might have been generated successfully
        if: ${{ !cancelled() }}
        run: yarn publish-sandboxes --remote=https://storybook-bot:${{ secrets.PAT_STORYBOOK_BOT }}@github.com/storybookjs/sandboxes.git --push --branch=main

      - name: Report failure to Discord
        if: failure()
        env:
          DISCORD_WEBHOOK: ${{ secrets.DISCORD_MONITORING_URL }}
        uses: Ilshidur/action-discord@d2594079a10f1d6739ee50a2471f0ca57418b554
        with:
          args: |
            The generation of some or all sandboxes on the **main** branch has failed.
            [See the job summary for details](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})
handle-release-branches .github/workflows/handle-release-branches.yml
Triggers
push
Runs on
ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest
Jobs
branch-checks, handle-latest, get-next-release-branch, create-next-release-branch, next-release-branch-check, request-create-frontpage-branch
Actions
notiz-dev/github-action-json-property
Commands
  • BRANCH=($(echo ${{ github.ref }} | sed -E 's/refs\/heads\///')) echo "branch=$BRANCH" >> $GITHUB_ENV
  • curl -X POST "https://api.netlify.com/build_hooks/${{ secrets.FRONTPAGE_HOOK }}"
  • NEXT_RELEASE_BRANCH=($(echo ${{ steps.next-version.outputs.prop }} | sed -E 's/([0-9]+)\.([0-9]+).*/release-\1-\2/')) echo "next-release-branch=$NEXT_RELEASE_BRANCH" >> $GITHUB_ENV
  • set +e REMOTE_BRANCH=$(git branch -r | grep origin/${{ needs.get-next-release-branch.outputs.branch }}) if [[ ! -z $REMOTE_BRANCH ]]; then git push origin --delete ${{ needs.get-next-release-branch.outputs.branch }}; fi echo 'Pushing branch ${{ needs.get-next-release-branch.outputs.branch }}...' git push -f origin ${{ needs.branch-checks.outputs.branch }}:${{ needs.get-next-release-branch.outputs.branch }}
  • IS_NEXT_RELEASE_BRANCH=${{ needs.branch-checks.outputs.branch == needs.get-next-release-branch.outputs.branch }} echo "is-next-release-branch=$IS_NEXT_RELEASE_BRANCH" >> $GITHUB_ENV
  • echo "relevant-base-branch=next" >> $GITHUB_ENV
  • echo 'WARNING: Do not push directly to the `${{ needs.branch-checks.outputs.branch }}` branch. This branch is created and force-pushed over after pushing to the `${{ env.relevant-base-branch }}` branch and the changes you just pushed will be lost.' exit 1
  • curl -X POST https://api.github.com/repos/storybookjs/frontpage/dispatches \ -H 'Accept: application/vnd.github.v3+json' \ -u ${{ secrets.FRONTPAGE_ACCESS_TOKEN }} \ --data '{"event_type": "request-create-frontpage-branch", "client_payload": { "branch": "${{ needs.create-next-release-branch.outputs.branch || needs.branch-checks.outputs.branch }}" }}'
View raw YAML
name: Handle Release Branches

on:
  push:

jobs:
  branch-checks:
    if: github.repository_owner == 'storybookjs'
    runs-on: ubuntu-latest
    steps:
      - id: get-branch
        run: |
          BRANCH=($(echo ${{ github.ref }} | sed -E 's/refs\/heads\///'))
          echo "branch=$BRANCH" >> $GITHUB_ENV
    outputs:
      branch: ${{ env.branch }}
      is-latest-branch: ${{ env.branch == 'main' }}
      is-next-branch: ${{ env.branch == 'next' }}
      is-release-branch: ${{ startsWith(env.branch, 'release-') }}
      is-actionable-branch: ${{ env.branch == 'main' || env.branch == 'next' || startsWith(env.branch, 'release-') }}

  handle-latest:
    needs: branch-checks
    if: ${{ needs.branch-checks.outputs.is-latest-branch == 'true' }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - run: curl -X POST "https://api.netlify.com/build_hooks/${{ secrets.FRONTPAGE_HOOK }}"

  get-next-release-branch:
    needs: branch-checks
    if: ${{ needs.branch-checks.outputs.is-next-branch == 'true' || needs.branch-checks.outputs.is-release-branch == 'true' }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: next
          path: next

      - id: next-version
        uses: notiz-dev/github-action-json-property@release
        with:
          path: ${{ github.workspace }}/next/code/package.json
          prop_path: version

      - run: |
          NEXT_RELEASE_BRANCH=($(echo ${{ steps.next-version.outputs.prop }} | sed -E 's/([0-9]+)\.([0-9]+).*/release-\1-\2/'))
          echo "next-release-branch=$NEXT_RELEASE_BRANCH" >> $GITHUB_ENV
    outputs:
      branch: ${{ env.next-release-branch }}

  create-next-release-branch:
    needs: [branch-checks, get-next-release-branch]
    if: ${{ needs.branch-checks.outputs.is-next-branch == 'true' }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - run: |
          set +e
          REMOTE_BRANCH=$(git branch -r | grep origin/${{ needs.get-next-release-branch.outputs.branch }})
          if [[ ! -z $REMOTE_BRANCH ]]; then git push origin --delete ${{ needs.get-next-release-branch.outputs.branch }}; fi
          echo 'Pushing branch ${{ needs.get-next-release-branch.outputs.branch }}...'
          git push -f origin ${{ needs.branch-checks.outputs.branch }}:${{ needs.get-next-release-branch.outputs.branch }}
    outputs:
      branch: ${{ needs.get-next-release-branch.outputs.branch }}

  next-release-branch-check:
    if: ${{ always() && github.repository_owner == 'storybookjs' }}
    needs: [branch-checks, get-next-release-branch]
    runs-on: ubuntu-latest
    steps:
      - run: |
          IS_NEXT_RELEASE_BRANCH=${{ needs.branch-checks.outputs.branch == needs.get-next-release-branch.outputs.branch }}
          echo "is-next-release-branch=$IS_NEXT_RELEASE_BRANCH" >> $GITHUB_ENV

      - if: ${{ env.is-next-release-branch == 'true' }}
        run: echo "relevant-base-branch=next" >> $GITHUB_ENV

      - if: ${{ env.is-next-release-branch == 'true' }}
        run: |
          echo 'WARNING: Do not push directly to the `${{ needs.branch-checks.outputs.branch }}` branch. This branch is created and force-pushed over after pushing to the `${{ env.relevant-base-branch }}` branch and the changes you just pushed will be lost.'
          exit 1
    outputs:
      check: ${{ env.is-next-release-branch }}

  request-create-frontpage-branch:
    if: ${{ always() && github.repository_owner == 'storybookjs' }}
    needs:
      [branch-checks, next-release-branch-check, create-next-release-branch]
    runs-on: ubuntu-latest
    steps:
      - if: ${{ needs.branch-checks.outputs.is-actionable-branch == 'true' && needs.branch-checks.outputs.is-latest-branch == 'false' && needs.next-release-branch-check.outputs.check == 'false' }}
        run: |
          curl -X POST https://api.github.com/repos/storybookjs/frontpage/dispatches \
          -H 'Accept: application/vnd.github.v3+json' \
          -u ${{ secrets.FRONTPAGE_ACCESS_TOKEN }} \
          --data '{"event_type": "request-create-frontpage-branch", "client_payload": { "branch": "${{ needs.create-next-release-branch.outputs.branch || needs.branch-checks.outputs.branch }}" }}'
nx perms .github/workflows/nx.yml
Triggers
push, pull_request, schedule
Runs on
ubuntu-latest
Jobs
nx
Actions
nrwl/nx-set-shas
Commands
  • tags="normal" if [[ "${{ github.event_name }}" == "pull_request" ]]; then if [[ "${{ contains(github.event.pull_request.labels.*.name, 'ci:merged') }}" == "true" ]]; then tags="merged" fi if [[ "${{ contains(github.event.pull_request.labels.*.name, 'ci:daily') }}" == "true" ]]; then tags="daily" fi fi if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/next" ]]; then tags="merged" fi if [[ "${{ github.event_name }}" == "schedule" ]]; then tags="daily" fi echo "tag=$tags" >> "$GITHUB_OUTPUT"
  • if [[ "${{ steps.tag.outputs.tag }}" == "daily" ]]; then echo "config=./.nx/workflows/distribution-config-daily.yaml" >> "$GITHUB_OUTPUT" else echo "config=./.nx/workflows/distribution-config.yaml" >> "$GITHUB_OUTPUT" fi
  • npx nx-cloud@latest start-ci-run --distribute-on="${{ steps.dist.outputs.config }}" --stop-agents-after="$ALL_TASKS"
  • yarn install --immutable
  • echo 'nx_output<<EOF' >> "$GITHUB_OUTPUT" yarn nx run-many -t $ALL_TASKS -c production -p="tag:library,tag:ci:${{ steps.tag.outputs.tag }}" | tee -a "$GITHUB_OUTPUT" status=${PIPESTATUS[0]} echo 'EOF' >> "$GITHUB_OUTPUT" exit $status
View raw YAML
name: nx

on:
  push:
    branches:
      - next
  # TODO use pull_request_target in the future to also run on forks
  pull_request:
    types: [opened, synchronize, labeled, reopened]
  schedule:
    - cron: '0 23 * * *'

permissions:
  actions: read
  contents: read
  statuses: write

env:
  NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}

jobs:
  nx:
    if: >
      github.repository == 'storybookjs/storybook' &&
        ((github.event_name == 'pull_request' &&
          github.event.pull_request.head.repo.full_name == github.repository &&
          (contains(github.event.pull_request.labels.*.name, 'ci:normal') ||
           contains(github.event.pull_request.labels.*.name, 'ci:merged') ||
           contains(github.event.pull_request.labels.*.name, 'ci:daily'))
        ) || (github.event_name == 'push' && github.ref == 'refs/heads/next') ||
        (github.event_name == 'schedule'))

    runs-on: ubuntu-latest
    env:
      ALL_TASKS: compile,check,knip,test,lint,fmt,sandbox,build,e2e-tests,e2e-tests-dev,test-runner,vitest-integration,check-sandbox,e2e-ui,jest,vitest,playwright-ct
    steps:
      - uses: actions/checkout@v4
        with:
          filter: tree:0
          fetch-depth: 0
      - name: Set Nx tag(s)
        id: tag
        run: |
          tags="normal"
          if [[ "${{ github.event_name }}" == "pull_request" ]]; then
            if [[ "${{ contains(github.event.pull_request.labels.*.name, 'ci:merged') }}" == "true" ]]; then
              tags="merged"
            fi
            if [[ "${{ contains(github.event.pull_request.labels.*.name, 'ci:daily') }}" == "true" ]]; then
              tags="daily"
            fi
          fi

          if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/next" ]]; then
            tags="merged"
          fi

          if [[ "${{ github.event_name }}" == "schedule" ]]; then
            tags="daily"
          fi

          echo "tag=$tags" >> "$GITHUB_OUTPUT"
      - name: Select distribution config
        id: dist
        run: |
          if [[ "${{ steps.tag.outputs.tag }}" == "daily" ]]; then
            echo "config=./.nx/workflows/distribution-config-daily.yaml" >> "$GITHUB_OUTPUT"
          else
            echo "config=./.nx/workflows/distribution-config.yaml" >> "$GITHUB_OUTPUT"
          fi
      - run: npx nx-cloud@latest start-ci-run --distribute-on="${{ steps.dist.outputs.config }}" --stop-agents-after="$ALL_TASKS"
      - name: Create Nx Cloud Status (pending)
        uses: actions/github-script@v7
        with:
          script: |
            const tag = ${{ toJson(steps.tag.outputs.tag) }} || 'normal';

            await github.rest.repos.createCommitStatus({
              owner: context.repo.owner,
              repo: context.repo.repo,
              sha: context.payload.pull_request?.head?.sha ?? context.sha,
              state: 'pending',
              target_url: `https://cloud.nx.app/orgs/606dcb5cdc2a2b00059cc0e9/workspaces/6929fbef73e98d8094d2a343/overview?branch=${
                context.payload.pull_request?.number ?? 'next'
              }`,
              description: 'NX Cloud is running your tests',
              context: `nx: ${tag}`,
            });
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'yarn'
      - run: yarn install --immutable
      - uses: nrwl/nx-set-shas@v4
      - id: nx
        name: 'Run nx'
        run: |
          echo 'nx_output<<EOF' >> "$GITHUB_OUTPUT"
          yarn nx run-many -t $ALL_TASKS -c production -p="tag:library,tag:ci:${{ steps.tag.outputs.tag }}" | tee -a "$GITHUB_OUTPUT"
          status=${PIPESTATUS[0]}
          echo 'EOF' >> "$GITHUB_OUTPUT"
          exit $status

      - name: Create per-task Nx statuses
        if: always()
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const raw = ${{ toJson(steps.nx.outputs.nx_output) }} || '';
            const tag = ${{ toJson(steps.tag.outputs.tag) }} || '';
            const lines = raw.split('\n');
            const failures = [];
            
            for (const [i, line] of lines.entries()) {
              if (!line.includes('✖')) continue;
            
              const task =
                line.match(/✖\s+([^│]+?)\s{2,}/)?.[1].trim() ||
                'Unknown Nx task';
            
              const url = lines
                .slice(i + 1, i + 6)
                .find(l => l.includes('Task logs:'))
                ?.match(/Task logs:\s*(https:\/\/cloud\.nx\.app\/logs\/\S+)/)?.[1];
            
              failures.push({ task, url });
            }
            
            const sha = context.payload.pull_request?.head?.sha ?? context.sha;
            
            // Per-task statuses (max 5)
            for (const { task, url } of failures.slice(0, 5)) {
              await github.rest.repos.createCommitStatus({
                owner: context.repo.owner,
                repo: context.repo.repo,
                sha,
                state: 'failure',
                target_url: url ?? undefined,
                context: `nx run ${task}`,
                description: 'Your test failed on NX Cloud',
              });
            }
            
            const runMatches = raw.match(/https:\/\/cloud\.nx\.app\/runs\/\S+/g);
            const nxCloudUrl = runMatches ? runMatches[runMatches.length - 1] : undefined;

            const failedCount = failures.length;

            await github.rest.repos.createCommitStatus({
              owner: context.repo.owner,
              repo: context.repo.repo,
              sha,
              state: failedCount ? 'failure' : 'success',
              target_url: nxCloudUrl,
              description: failedCount
                ? `Nx Cloud run failed (${failedCount} tasks failed)`
                : 'Nx Cloud run finished successfully',
              context: `nx: ${tag}`,
            });
prepare-non-patch-release .github/workflows/prepare-non-patch-release.yml
Triggers
push, workflow_dispatch
Runs on
ubuntu-latest
Jobs
prepare-non-patch-pull-request
Actions
Ilshidur/action-discord
Commands
  • yarn release:is-pr-frozen
  • gh run cancel ${{ github.run_id }} gh run watch ${{ github.run_id }}
  • git fetch --tags origin
  • yarn release:unreleased-changes-exists
  • gh run cancel ${{ github.run_id }} gh run watch ${{ github.run_id }}
  • yarn release:version --deferred --release-type ${{ inputs.release-type || 'prerelease' }} ${{ inputs.pre-id && format('{0} {1}', '--pre-id', inputs.pre-id) || '' }} --verbose
  • yarn release:write-changelog ${{ steps.bump-version.outputs.next-version }} --verbose
  • git config --global user.name 'storybook-bot' git config --global user.email '32066757+storybook-bot@users.noreply.github.com' git checkout -b version-non-patch-from-${{ steps.bump-version.outputs.current-version }} git add . git commit --allow-empty --no-verify -m "Write changelog for ${{ steps.bump-version.outputs.next-version }} [skip ci]" git push --force origin version-non-patch-from-${{ steps.bump-version.outputs.current-version }}
View raw YAML
name: Prepare non-patch PR
run-name: Prepare non-patch PR, triggered by ${{ github.triggering_actor }}

on:
  push:
    branches:
      - next
  workflow_dispatch:
    inputs:
      release-type:
        description: 'Which release type to use for bumping the version'
        required: true
        default: 'prerelease'
        type: choice
        options:
          - prerelease
          - prepatch
          - preminor
          - premajor
          - patch
          - minor
          - major
      pre-id:
        description: For prerelease versions, what prerelease identifier to use, eg. 'alpha', 'beta', 'rc'
        required: false
        type: string

env:
  PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
  PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1

concurrency:
  group: ${{ github.workflow }}
  cancel-in-progress: true

jobs:
  prepare-non-patch-pull-request:
    name: Prepare non-patch pull request
    if: github.repository_owner == 'storybookjs'
    runs-on: ubuntu-latest
    environment: Release
    defaults:
      run:
        working-directory: scripts
    steps:
      - name: Checkout next
        uses: actions/checkout@v4
        with:
          ref: next
          # this needs to be set to a high enough number that it will contain the last version tag
          # as of May 2023, the whole repo had 55K commits
          fetch-depth: 10000
          token: ${{ secrets.GH_TOKEN }}

      - name: Setup Node.js and Install Dependencies
        uses: ./.github/actions/setup-node-and-install

      - name: Check if pull request is frozen
        if: github.event_name != 'workflow_dispatch'
        id: check-frozen
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: yarn release:is-pr-frozen

      - name: Cancel when frozen
        if: steps.check-frozen.outputs.frozen == 'true' && github.event_name != 'workflow_dispatch'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        # From https://stackoverflow.com/a/75809743
        run: |
          gh run cancel ${{ github.run_id }}
          gh run watch ${{ github.run_id }}

      # tags are needed to get changes and changelog generation
      - name: Fetch git tags
        run: git fetch --tags origin

      - name: Check for unreleased changes
        if: github.event_name != 'workflow_dispatch'
        id: unreleased-changes
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: yarn release:unreleased-changes-exists

      - name: Cancel when no release necessary
        if: steps.unreleased-changes.outputs.has-changes-to-release == 'false' && github.event_name != 'workflow_dispatch'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        # From https://stackoverflow.com/a/75809743
        run: |
          gh run cancel ${{ github.run_id }}
          gh run watch ${{ github.run_id }}

      - name: Bump version deferred
        id: bump-version
        run: |
          yarn release:version --deferred --release-type ${{ inputs.release-type || 'prerelease' }} ${{ inputs.pre-id && format('{0} {1}', '--pre-id', inputs.pre-id) || '' }} --verbose

      - name: Write changelog
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          yarn release:write-changelog ${{ steps.bump-version.outputs.next-version }} --verbose

      - name: 'Commit changes to branch: version-non-patch-from-${{ steps.bump-version.outputs.current-version }}'
        working-directory: .
        run: |
          git config --global user.name 'storybook-bot'
          git config --global user.email '32066757+storybook-bot@users.noreply.github.com'
          git checkout -b version-non-patch-from-${{ steps.bump-version.outputs.current-version }}
          git add .
          git commit --allow-empty --no-verify -m "Write changelog for ${{ steps.bump-version.outputs.next-version }} [skip ci]"
          git push --force origin version-non-patch-from-${{ steps.bump-version.outputs.current-version }}

      - name: Generate PR description
        id: description
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: yarn release:generate-pr-description --current-version ${{ steps.bump-version.outputs.current-version }} --next-version ${{ steps.bump-version.outputs.next-version }} --verbose

      - name: Create or update pull request
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          RELEASE_TYPE=${{ inputs.release-type || 'prerelease' }}
          CAPITALIZED_RELEASE_TYPE=${RELEASE_TYPE^}
          if PR_STATE=$(gh pr view --json state --jq .state 2>/dev/null) && [[ -n "$PR_STATE" && "$PR_STATE" == *"OPEN"* ]]; then
            gh pr edit \
              --repo "${{github.repository }}" \
              --title "Release: $CAPITALIZED_RELEASE_TYPE ${{ inputs.pre-id && format('{0} ', inputs.pre-id) }}${{ steps.bump-version.outputs.next-version }}" \
              --body "${{ steps.description.outputs.description }}"
          else
            gh pr create \
              --repo "${{github.repository }}"\
              --title "Release: $CAPITALIZED_RELEASE_TYPE ${{ inputs.pre-id && format('{0} ', inputs.pre-id) }}${{ steps.bump-version.outputs.next-version }}" \
              --label "release" \
              --base next-release \
              --head version-non-patch-from-${{ steps.bump-version.outputs.current-version }} \
              --body "${{ steps.description.outputs.description }}"
          fi

      - name: Report job failure to Discord
        if: failure()
        env:
          DISCORD_WEBHOOK: ${{ secrets.DISCORD_MONITORING_URL }}
        uses: Ilshidur/action-discord@d2594079a10f1d6739ee50a2471f0ca57418b554
        with:
          args: 'The GitHub Action for preparing the release pull request bumping from v${{ steps.bump-version.outputs.current-version }} to v${{ steps.bump-version.outputs.next-version }} (triggered by ${{ github.triggering_actor }}) failed! See run at: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}'
prepare-patch-release .github/workflows/prepare-patch-release.yml
Triggers
push, workflow_dispatch
Runs on
ubuntu-latest
Jobs
prepare-patch-pull-request
Actions
Ilshidur/action-discord
Commands
  • yarn release:is-pr-frozen --patch
  • gh run cancel ${{ github.run_id }} gh run watch ${{ github.run_id }}
  • yarn release:unreleased-changes-exists --unpicked-patches
  • git fetch --depth=2000 origin next
  • git config --global user.name 'storybook-bot' git config --global user.email '32066757+storybook-bot@users.noreply.github.com' yarn release:pick-patches
  • gh run cancel ${{ github.run_id }} gh run watch ${{ github.run_id }}
  • yarn release:version --deferred --release-type patch --verbose
  • yarn release:get-current-version --verbose
View raw YAML
name: Prepare patch PR
run-name: Prepare patch PR, triggered by ${{ github.triggering_actor }}

on:
  push:
    branches:
      - next
  workflow_dispatch:

env:
  PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
  PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1

concurrency:
  group: ${{ github.workflow }}
  cancel-in-progress: true

jobs:
  prepare-patch-pull-request:
    name: Prepare patch pull request
    if: github.repository_owner == 'storybookjs'
    runs-on: ubuntu-latest
    environment: Release
    defaults:
      run:
        working-directory: scripts
    steps:
      - name: Checkout main
        uses: actions/checkout@v4
        with:
          ref: main
          token: ${{ secrets.GH_TOKEN }}

      - name: Setup Node.js and Install Dependencies
        uses: ./.github/actions/setup-node-and-install

      - name: Check if pull request is frozen
        if: github.event_name != 'workflow_dispatch'
        id: check-frozen
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: yarn release:is-pr-frozen --patch

      - name: Cancel when frozen
        if: steps.check-frozen.outputs.frozen == 'true' && github.event_name != 'workflow_dispatch'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        # From https://stackoverflow.com/a/75809743
        run: |
          gh run cancel ${{ github.run_id }}
          gh run watch ${{ github.run_id }}

      - name: Check for unreleased changes
        id: unreleased-changes
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: yarn release:unreleased-changes-exists --unpicked-patches

      - name: Fetch next branch
        run:
          # depth needs to be set to a high enough number that it will contain all the merge commits to cherry-pick
          # as of May 2023, the whole repo had 55K commits
          git fetch --depth=2000 origin next

      - name: Pick patches
        id: pick-patches
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          git config --global user.name 'storybook-bot'
          git config --global user.email '32066757+storybook-bot@users.noreply.github.com'
          yarn release:pick-patches

      - name: Cancel when no patches to pick
        if: steps.pick-patches.outputs.no-patch-prs == 'true'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        # From https://stackoverflow.com/a/75809743
        run: |
          gh run cancel ${{ github.run_id }}
          gh run watch ${{ github.run_id }}

      - name: Bump version deferred
        id: bump-version
        if: steps.unreleased-changes.outputs.has-changes-to-release == 'true'
        run: |
          yarn release:version --deferred --release-type patch --verbose

      # We need the current version to set the branch name, even when not bumping the version
      - name: Get current version
        id: current-version
        if: steps.unreleased-changes.outputs.has-changes-to-release == 'false'
        run: |
          yarn release:get-current-version --verbose

      - name: Set version output
        id: versions
        run: |
          echo "current=${{ steps.bump-version.outputs.current-version || steps.current-version.outputs.current-version }}" >> "$GITHUB_OUTPUT"
          echo "next=${{ steps.bump-version.outputs.next-version || steps.current-version.outputs.current-version }}" >> "$GITHUB_OUTPUT"

      - name: Write changelog
        if: steps.unreleased-changes.outputs.has-changes-to-release == 'true'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          yarn release:write-changelog ${{ steps.versions.outputs.next }} --unpicked-patches --verbose

      - name: 'Commit changes to branch: version-patch-from-${{ steps.versions.outputs.current }}'
        working-directory: .
        run: |
          git config --global user.name 'storybook-bot'
          git config --global user.email '32066757+storybook-bot@users.noreply.github.com'
          git checkout -b version-patch-from-${{ steps.versions.outputs.current }}
          git add .
          git commit --allow-empty --no-verify -m "Write changelog for ${{ steps.versions.outputs.next }} [skip ci]"
          git push --force origin version-patch-from-${{ steps.versions.outputs.current }}

      - name: Generate PR description
        id: description
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: yarn release:generate-pr-description --unpicked-patches --manual-cherry-picks='${{ steps.pick-patches.outputs.failed-cherry-picks }}' ${{ steps.unreleased-changes.outputs.has-changes-to-release == 'true' && format('{0}={1} {2}={3}', '--current-version', steps.versions.outputs.current, '--next-version', steps.versions.outputs.next) || '' }} --verbose

      - name: Create or update pull request with release
        if: steps.unreleased-changes.outputs.has-changes-to-release == 'true'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          if PR_STATE=$(gh pr view --json state --jq .state 2>/dev/null) && [[ -n "$PR_STATE" && "$PR_STATE" == *"OPEN"* ]]; then
            gh pr edit \
              --repo "${{github.repository }}" \
              --title "Release: Patch ${{ steps.versions.outputs.next }}" \
              --body "${{ steps.description.outputs.description }}"
          else
            gh pr create \
              --repo "${{github.repository }}" \
              --title "Release: Patch ${{ steps.versions.outputs.next }}" \
              --label "release" \
              --base latest-release \
              --head version-patch-from-${{ steps.versions.outputs.current }} \
              --body "${{ steps.description.outputs.description }}"
          fi

      - name: Create or update pull request without release
        if: steps.unreleased-changes.outputs.has-changes-to-release == 'false'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          if PR_STATE=$(gh pr view --json state --jq .state 2>/dev/null) && [[ -n "$PR_STATE" && "$PR_STATE" == *"OPEN"* ]]; then
            gh pr edit \
              --repo "${{github.repository }}"\
              --title "Release: Merge patches to \`main\` (without version bump)" \
              --body "${{ steps.description.outputs.description }}"
          else
            gh pr create \
              --repo "${{github.repository }}"\
              --title "Release: Merge patches to \`main\` (without version bump)" \
              --label "release" \
              --base latest-release \
              --head version-patch-from-${{ steps.versions.outputs.current }} \
              --body "${{ steps.description.outputs.description }}"
          fi

      - name: Report job failure to Discord
        if: failure()
        env:
          DISCORD_WEBHOOK: ${{ secrets.DISCORD_MONITORING_URL }}
        uses: Ilshidur/action-discord@d2594079a10f1d6739ee50a2471f0ca57418b554
        with:
          args: 'The GitHub Action for preparing the release pull request bumping from v${{ steps.versions.outputs.current }} to v${{ steps.versions.outputs.next }} (triggered by ${{ github.triggering_actor }}) failed! See run at: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}'
publish perms .github/workflows/publish.yml
Triggers
push, workflow_dispatch, pull_request
Runs on
ubuntu-latest, ubuntu-latest
Jobs
publish-normal, publish-canary
Actions
getsentry/action-release, Ilshidur/action-discord, prince-chrismc/check-actor-permissions-action, ivangabriele/find-and-replace-pull-request-body
Commands
  • yarn release:cancel-preparation-runs
  • CURRENT_VERSION=$(cat ./code/package.json | jq '.version') DEFERRED_NEXT_VERSION=$(cat ./code/package.json | jq '.deferredNextVersion') if [[ "$DEFERRED_NEXT_VERSION" == "null" ]]; then echo "No deferred version set, not bumping versions" exit 0 fi cd scripts yarn release:version --apply --verbose cd .. git config --global user.name "storybook-bot" git config --global user.email "32066757+storybook-bot@users.noreply.github.com" git add . git commit -m "Bump version from $CURRENT_VERSION to $DEFERRED_NEXT_VERSION [skip ci]" --no-verify --allow-empty git push origin "$REF_NAME"
  • yarn release:get-current-version
  • yarn release:is-version-published "$CURRENT_VERSION"
  • yarn release:is-prerelease "$CURRENT_VERSION" --verbose
  • yarn release:publish --tag ${{ steps.is-prerelease.outputs.prerelease == 'true' && 'next' || 'latest' }} --verbose
  • echo "target=${{ github.ref_name == 'next-release' && 'next' || 'main' }}" >> $GITHUB_OUTPUT
  • yarn release:get-changelog-from-file "$CURRENT_VERSION"
View raw YAML
name: Publish
run-name: "${{ github.event_name == 'workflow_dispatch' && format('Publish Canary on PR #{0}, triggered by {1}', inputs.pr, github.triggering_actor) || format('Publish new version on {0}, triggered by {1}', github.ref_name, github.triggering_actor) }}"

on:
  push:
    # Normal releases, major/minor/patch/prerelease
    branches:
      - latest-release
      - next-release
  workflow_dispatch:
    # Manual canary releases on PRs
    inputs:
      pr:
        description: "⚠️ CANARY RELEASES ONLY - Enter the pull request number to create a canary release for"
        required: true
        type: number
  pull_request:
    # Automated canary releases on PRs with the "with-canary-release"-suffix in the branch name
    types: [opened, synchronize, reopened]

env:
  PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
  PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1

permissions:
  id-token: write
  contents: write
  pull-requests: write

concurrency:
  # Group concurrent runs based on the event type:
  # - For workflow_dispatch and pull_request: group by PR number to allow only one canary release per PR
  # - For push events: group by branch name to prevent multiple releases on the same branch
  group: ${{ github.event_name == 'workflow_dispatch' && format('{0}-{1}', github.workflow, inputs.pr) || github.event_name == 'pull_request' && format('{0}-{1}', github.workflow, github.event.pull_request.number) || format('{0}-{1}', github.workflow, github.ref_name) }}
  cancel-in-progress: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request' }}

jobs:
  publish-normal:
    name: Publish normal version
    runs-on: ubuntu-latest
    if: |
      github.repository_owner == 'storybookjs' &&
      github.event_name == 'push' &&
      (github.ref_name == 'latest-release' || github.ref_name == 'next-release') &&
      contains(github.event.head_commit.message, '[skip ci]') != true
    environment: Release
    defaults:
      run:
        working-directory: scripts
    steps:
      - name: Checkout ${{ github.ref_name }}
        uses: actions/checkout@v4
        with:
          fetch-depth: 100
          token: ${{ secrets.GH_TOKEN }}

      - name: Setup Node.js and Install Dependencies
        uses: ./.github/actions/setup-node-and-install
        with:
          install-code-deps: true

      - name: Cancel all release preparation runs
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: yarn release:cancel-preparation-runs

      - name: Apply deferred version bump and commit
        working-directory: .
        env:
          REF_NAME: ${{ github.ref_name }}
        run: |
          CURRENT_VERSION=$(cat ./code/package.json | jq '.version')
          DEFERRED_NEXT_VERSION=$(cat ./code/package.json | jq '.deferredNextVersion')

          if [[ "$DEFERRED_NEXT_VERSION" == "null" ]]; then
              echo "No deferred version set, not bumping versions"
              exit 0
          fi
          cd scripts
          yarn release:version --apply --verbose
          cd ..

          git config --global user.name "storybook-bot"
          git config --global user.email "32066757+storybook-bot@users.noreply.github.com"
          git add .
          git commit -m "Bump version from $CURRENT_VERSION to $DEFERRED_NEXT_VERSION [skip ci]" --no-verify --allow-empty
          git push origin "$REF_NAME"

      - name: Get current version
        id: version
        run: yarn release:get-current-version

      - name: Check if publish is needed
        id: publish-needed
        env:
          CURRENT_VERSION: ${{ steps.version.outputs.current-version }}
        run: yarn release:is-version-published "$CURRENT_VERSION"

      - name: Check release vs prerelease
        if: steps.publish-needed.outputs.published == 'false'
        id: is-prerelease
        env:
          CURRENT_VERSION: ${{ steps.version.outputs.current-version }}
        run: yarn release:is-prerelease "$CURRENT_VERSION" --verbose

      - name: Publish
        env:
          NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
        if: steps.publish-needed.outputs.published == 'false'
        run: yarn release:publish --tag ${{ steps.is-prerelease.outputs.prerelease == 'true' && 'next' || 'latest' }} --verbose

      - name: Get target branch
        id: target
        run: echo "target=${{ github.ref_name == 'next-release' && 'next' || 'main' }}" >> $GITHUB_OUTPUT

      - name: Get changelog for ${{ steps.version.outputs.current-version }}
        if: steps.publish-needed.outputs.published == 'false'
        id: changelog
        env:
          CURRENT_VERSION: ${{ steps.version.outputs.current-version }}
        run: yarn release:get-changelog-from-file "$CURRENT_VERSION"

      # tags are needed to get list of patches to label as picked
      - name: Fetch git tags
        if: github.ref_name == 'latest-release'
        run: git fetch --tags origin

      # when this is a patch release from main, label any patch PRs included in the release
      - name: Label patch PRs as picked
        if: github.ref_name == 'latest-release'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: yarn release:label-patches

      - name: Create GitHub Release
        if: steps.publish-needed.outputs.published == 'false'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          CURRENT_VERSION: ${{ steps.version.outputs.current-version }}
          REPOSITORY: ${{ github.repository }}
          REF_NAME: ${{ github.ref_name }}
          CHANGELOG: ${{ steps.changelog.outputs.changelog }}
          IS_PRERELEASE: ${{ steps.is-prerelease.outputs.prerelease == 'true' && '--prerelease' || '' }}
        run: |
          gh release create \
          "v$CURRENT_VERSION" \
            --repo "$REPOSITORY" \
            --target "$REF_NAME" \
            --title "v$CURRENT_VERSION" \
            --notes "$CHANGELOG" \
            $IS_PRERELEASE

      - name: Merge ${{ github.ref_name }} into ${{ steps.target.outputs.target }}
        env:
          REF_NAME: ${{ github.ref_name }}
          TARGET_BRANCH: ${{ steps.target.outputs.target }}
        run: |
          git config --global user.name "storybook-bot"
          git config --global user.email "32066757+storybook-bot@users.noreply.github.com"
          git fetch origin "$TARGET_BRANCH"
          git checkout "$TARGET_BRANCH"
          git merge "$REF_NAME"
          git push origin "$TARGET_BRANCH"

      - name: Force push from 'next' to 'latest-release' and 'main' on minor/major releases
        if: github.ref_name == 'next-release' && steps.is-prerelease.outputs.prerelease == 'false'
        run: |
          git checkout next
          git pull
          git push origin --force next:latest-release
          git push origin --force next:main

      - name: Sync CHANGELOG.md from `main` to `next`
        if: steps.target.outputs.target == 'main'
        working-directory: .
        env:
          CURRENT_VERSION: ${{ steps.version.outputs.current-version }}
        run: |
          git fetch origin next
          git checkout next
          git pull
          git checkout origin/main ./CHANGELOG.md
          git add ./CHANGELOG.md
          git commit -m "Update CHANGELOG.md for v$CURRENT_VERSION [skip ci]" --no-verify --allow-empty
          git push origin next

      # Sync the next.json version file to the main branch so it gets deployed to the docs site
      # but only if this is a prerelease, because in minor/major releases we're already force pushing next-release onto main, so it's already there
      - name: Sync version JSONs from `next-release` to `main`
        if: github.ref_name == 'next-release' && steps.is-prerelease.outputs.prerelease == 'true'
        working-directory: .
        env:
          CURRENT_VERSION: ${{ steps.version.outputs.current-version }}
        run: |
          VERSION_FILE="./docs/versions/next.json"
          git fetch origin main
          git checkout main
          git pull
          git checkout origin/next-release $VERSION_FILE
          git add $VERSION_FILE
          git commit -m "Update $VERSION_FILE for v$CURRENT_VERSION"
          git push origin main

      - name: Create Sentry release
        if: steps.publish-needed.outputs.published == 'false'
        uses: getsentry/action-release@v3
        env:
          SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
          SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
          SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
        with:
          release: ${{ steps.version.outputs.current-version }}
          environment: ${{ steps.is-prerelease.outputs.prerelease == 'true' && 'prerelease' || 'latest' }}

      - name: Report job failure to Discord
        if: failure()
        env:
          DISCORD_WEBHOOK: ${{ secrets.DISCORD_MONITORING_URL }}
        uses: Ilshidur/action-discord@d2594079a10f1d6739ee50a2471f0ca57418b554
        with:
          args: "The GitHub Action for publishing version ${{ steps.version.outputs.current-version }} (triggered by ${{ github.triggering_actor }}) failed! See run at: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"

  publish-canary:
    name: Publish canary version
    runs-on: ubuntu-latest
    if: |
      github.repository_owner == 'storybookjs' &&
      (
        github.event_name == 'workflow_dispatch' ||
        (github.event_name == 'pull_request' && endsWith(github.head_ref, 'with-canary-release'))
      ) &&
      contains(github.event.head_commit.message, '[skip ci]') != true
    environment: Release
    steps:
      - name: Fail if triggering actor is not administrator
        uses: prince-chrismc/check-actor-permissions-action@87c6d9b36c730377858fd9719fbbac1b58fa678d
        with:
          permission: admin

      - name: Get pull request information
        id: info
        env:
          GH_TOKEN: ${{ secrets.GH_TOKEN }}
          PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr || github.event.pull_request.number }}
          REPOSITORY: ${{ github.repository }}
        run: |
          PR_INFO=$(gh pr view "$PR_NUMBER" --repo "$REPOSITORY" --json isCrossRepository,headRefOid,headRefName,headRepository,headRepositoryOwner --jq '{isFork: .isCrossRepository, owner: .headRepositoryOwner.login, repoName: .headRepository.name, branch: .headRefName, sha: .headRefOid}')
          echo $PR_INFO
          # Loop through each key-value pair in PR_INFO and set as step output
          for key in $(echo "$PR_INFO" | jq -r 'keys[]'); do
            value=$(echo "$PR_INFO" | jq -r ".$key")
            echo "$key=$value" >> "$GITHUB_OUTPUT"
          done
          echo "repository=$(echo "$PR_INFO" | jq -r ".owner")/$(echo "$PR_INFO" | jq -r ".repoName")" >> $GITHUB_OUTPUT
          echo "shortSha=$(echo "$PR_INFO" | jq -r ".sha" | cut -c 1-8)" >> $GITHUB_OUTPUT
          echo "date=$(date)" >> $GITHUB_OUTPUT
          echo "timestamp=$(date +%s)" >> $GITHUB_OUTPUT

      - name: Checkout
        uses: actions/checkout@v4
        with:
          repository: ${{ steps.info.outputs.isFork == 'true' && steps.info.outputs.repository || null }}
          ref: ${{ steps.info.outputs.sha }}
          token: ${{ secrets.GH_TOKEN }}

      - name: Setup Node.js and Install Dependencies
        uses: ./.github/actions/setup-node-and-install
        with:
          install-code-deps: true

      - name: Set version
        id: version
        working-directory: scripts
        env:
          PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr || github.event.pull_request.number }}
          SHORT_SHA: ${{ steps.info.outputs.shortSha }}
        run: |
          yarn release:version --exact "0.0.0-pr-$PR_NUMBER-sha-$SHORT_SHA" --verbose

      - name: Publish v${{ steps.version.outputs.next-version }}
        env:
          NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
        working-directory: scripts
        run: yarn release:publish --tag canary --verbose

      - name: Replace Pull Request Body
        uses: ivangabriele/find-and-replace-pull-request-body@042438c6cbfbacf6a4701d6042f59b1f73db2fd8
        with:
          githubToken: ${{ secrets.GH_TOKEN }}
          prNumber: ${{ github.event_name == 'workflow_dispatch' && inputs.pr || '' }}
          find: "CANARY_RELEASE_SECTION"
          isHtmlCommentTag: true
          replace: |
            This pull request has been released as version `${{ steps.version.outputs.next-version }}`. Try it out in a new sandbox by running `npx storybook@${{ steps.version.outputs.next-version }} sandbox` or in an existing project with `npx storybook@${{ steps.version.outputs.next-version }} upgrade`.
            <details>
            <summary>More information</summary>

            | | |
            | --- | --- |
            | **Published version** | [`${{ steps.version.outputs.next-version }}`](https://npmjs.com/package/storybook/v/${{ steps.version.outputs.next-version }}) |
            | **Triggered by** | @${{ github.triggering_actor }} |
            | **Repository** | [${{ steps.info.outputs.repository }}](https://github.com/${{ steps.info.outputs.repository }}) |
            | **Branch** | [`${{ steps.info.outputs.branch }}`](https://github.com/${{ steps.info.outputs.repository }}/tree/${{ steps.info.outputs.branch }}) |
            | **Commit** | [`${{ steps.info.outputs.shortSha }}`](https://github.com/${{ steps.info.outputs.repository }}/commit/${{ steps.info.outputs.sha }}) |
            | **Datetime** | ${{ steps.info.outputs.date }} (`${{ steps.info.outputs.timestamp }}`) |
            | **Workflow run** | [${{ github.run_id }}](https://github.com/storybookjs/storybook/actions/runs/${{ github.run_id }}) |

            To request a new release of this pull request, mention the `@storybookjs/core` team.

            _core team members can create a new canary release [here](https://github.com/storybookjs/storybook/actions/workflows/publish.yml) or locally with `gh workflow run --repo storybookjs/storybook publish.yml --field pr=${{ github.event_name == 'workflow_dispatch' && inputs.pr || github.event.pull_request.number }}`_
            </details>

      - name: Create failing comment on PR
        if: failure()
        env:
          GH_TOKEN: ${{ secrets.GH_TOKEN }}
          PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr || github.event.pull_request.number }}
          REPOSITORY: ${{ github.repository }}
          TRIGGERING_ACTOR: ${{ github.triggering_actor }}
          RUN_ID: ${{ github.run_id }}
        run: |
          gh pr comment "$PR_NUMBER"\
            --repo "$REPOSITORY"\
            --body "Failed to publish canary version of this pull request, triggered by @$TRIGGERING_ACTOR. See the failed workflow run at: https://github.com/$REPOSITORY/actions/runs/$RUN_ID"
stale perms .github/workflows/stale.yml
Triggers
schedule
Runs on
ubuntu-latest
Jobs
stale
Actions
actions/stale
View raw YAML
name: "Close stale issues that need reproduction or more info from OP"
on:
  schedule:
    - cron: "30 1 * * *"

permissions:
  issues: write # to close and label issues (actions/stale)
  pull-requests: write # to mark stale pull requests (actions/stale)

jobs:
  stale:
    runs-on: ubuntu-latest
    if: github.repository_owner == 'storybookjs'
    steps:
      - uses: actions/stale@v9
        with:
          stale-issue-message: "Hi there! Thank you for opening this issue, but it has been marked as `stale` because we need more information to move forward. Could you please provide us with the requested reproduction or additional information that could help us better understand the problem? We'd love to resolve this issue, but we can't do it without your help!"
          close-issue-message: "I'm afraid we need to close this issue for now, since we can't take any action without the requested reproduction or additional information. But please don't hesitate to open a new issue if the problem persists – we're always happy to help. Thanks so much for your understanding."
          any-of-issue-labels: "needs reproduction,needs more info"
          exempt-issue-labels: "needs triage"
          labels-to-add-when-unstale: "needs triage"
          days-before-issue-close: 7
          days-before-issue-stale: 21
          days-before-pr-close: -1
          days-before-pr-stale: 10
          exempt-draft-pr: true
triage perms .github/workflows/triage.yml
Triggers
issues, issue_comment
Runs on
ubuntu-latest
Jobs
triage
Actions
balazsorban44/nissuer
View raw YAML
name: Triage issues

on:
  issues:
    types: [opened, labeled]
  issue_comment:
    types: [created]

env:
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

permissions:
  issues: write

jobs:
  triage:
    name: Nissuer
    if: github.repository_owner == 'storybookjs'
    runs-on: ubuntu-latest
    steps:
      - uses: balazsorban44/nissuer@1.10.0
        with:
          label-comments: |
            {
              "good first issue": ".github/comments/good-first-issue.md"
            }
          reproduction-comment: ".github/comments/invalid-link.md"
          reproduction-hosts: "github.com,codesandbox.io,stackblitz.com"
          reproduction-link-section: "### Reproduction link(.*)### Reproduction steps"
          reproduction-invalid-label: "needs reproduction"
          reproduction-issue-labels: "bug,needs triage"
trigger-circle-ci-workflow .github/workflows/trigger-circle-ci-workflow.yml
Triggers
pull_request_target, push
Runs on
ubuntu-latest, ubuntu-latest, ubuntu-latest
Jobs
get-branch, get-parameters, trigger-circle-ci-workflow
Actions
fjogeleit/http-request-action
Commands
  • if [ "${{ github.event.pull_request.head.repo.fork }}" = "true" ]; then export BRANCH=pull/${{ github.event.pull_request.number }}/head elif [ "${{ github.event_name }}" = "push" ]; then export BRANCH="$REF_NAME" else export BRANCH="$PR_REF_NAME" fi echo "$BRANCH" echo "branch=$BRANCH" >> $GITHUB_ENV
  • echo "workflow=normal" >> $GITHUB_ENV
  • echo "workflow=docs" >> $GITHUB_ENV
  • echo "workflow=merged" >> $GITHUB_ENV
  • echo "workflow=daily" >> $GITHUB_ENV
View raw YAML
name: Trigger CircleCI workflow

on:
  # Use pull_request_target, as we don't need to check out the actual code of the fork in this script.
  # And this is the only way to trigger the Circle CI API on forks as well.
  pull_request_target:
    types: [opened, synchronize, labeled, reopened]
  push:
    branches:
      - next
      - main

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  get-branch:
    if: github.repository_owner == 'storybookjs'
    runs-on: ubuntu-latest
    steps:
      - id: get-branch
        env:
          # Stored as environment variable to prevent script injection
          REF_NAME: ${{ github.ref_name }}
          PR_REF_NAME: ${{ github.event.pull_request.head.ref }}
        run: |
          if  [ "${{ github.event.pull_request.head.repo.fork }}" = "true" ]; then
            export BRANCH=pull/${{ github.event.pull_request.number }}/head
            elif [ "${{ github.event_name }}" = "push" ]; then
            export BRANCH="$REF_NAME"
            else
            export BRANCH="$PR_REF_NAME"
          fi
          echo "$BRANCH"
          echo "branch=$BRANCH" >> $GITHUB_ENV
    outputs:
      branch: ${{ env.branch }}

  get-parameters:
    if: github.repository_owner == 'storybookjs'
    runs-on: ubuntu-latest
    steps:
      - if: github.event_name == 'pull_request_target' && (contains(github.event.pull_request.labels.*.name, 'ci:normal'))
        run: echo "workflow=normal" >> $GITHUB_ENV
      - if: github.event_name == 'pull_request_target' && (contains(github.event.pull_request.labels.*.name, 'ci:docs'))
        run: echo "workflow=docs" >> $GITHUB_ENV
      - if: github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'ci:merged')
        run: echo "workflow=merged" >> $GITHUB_ENV
      - if: github.event_name == 'pull_request_target' && (contains(github.event.pull_request.labels.*.name, 'ci:daily'))
        run: echo "workflow=daily" >> $GITHUB_ENV
    outputs:
      workflow: ${{ env.workflow }}
      ghBaseBranch: ${{ github.event.pull_request.base.ref }}
      ghPrNumber: ${{ github.event.pull_request.number }}

  trigger-circle-ci-workflow:
    runs-on: ubuntu-latest
    needs: [get-branch, get-parameters]
    if: github.repository_owner == 'storybookjs' && needs.get-parameters.outputs.workflow != ''
    steps:
      - name: Trigger Normal tests
        uses: fjogeleit/http-request-action@v1
        with:
          url: 'https://circleci.com/api/v2/project/gh/storybookjs/storybook/pipeline'
          method: 'POST'
          customHeaders: '{"Content-Type": "application/json", "Circle-Token": "${{ secrets.CIRCLE_CI_TOKEN }}"}'
          data: '{ "branch": "${{needs.get-branch.outputs.branch}}", "parameters": ${{toJson(needs.get-parameters.outputs)}} }'