From e5d065ddc8940ece06baf3cd8cb4b3cf96a10a3a Mon Sep 17 00:00:00 2001 From: Damien Urruty Date: Mon, 1 Jun 2026 14:53:29 +0200 Subject: [PATCH 1/3] Experiment with a new agentic workflow --- .../ci-failure-triage-agent.lock.yml | 193 +- .github/workflows/ci-failure-triage-agent.md | 265 ++- .github/workflows/night-owl.lock.yml | 1564 +++++++++++++++++ .github/workflows/night-owl.md | 213 +++ CLAUDE.md | 6 + 5 files changed, 2007 insertions(+), 234 deletions(-) create mode 100644 .github/workflows/night-owl.lock.yml create mode 100644 .github/workflows/night-owl.md diff --git a/.github/workflows/ci-failure-triage-agent.lock.yml b/.github/workflows/ci-failure-triage-agent.lock.yml index cc1950191..64a57282b 100644 --- a/.github/workflows/ci-failure-triage-agent.lock.yml +++ b/.github/workflows/ci-failure-triage-agent.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"e2cedb21c66ecda26b22275f82334a7ec3a98d38b20a3d8b6a337f27291f64c3","compiler_version":"v0.76.1","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"fccf4857a7b6fcff5ad5f7d961efe7ebee0edfa829ee241182736c9f259d1253","compiler_version":"v0.76.1","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"SonarSource/vault-action-wrapper","sha":"c154b4a417b51cb98dd71137f49bf20e77c56820","version":"c154b4a417b51cb98dd71137f49bf20e77c56820"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"46d564922b082d0db93244972e8005ea6904ee5f","version":"v0.76.1"},{"repo":"slackapi/slack-github-action","sha":"70cd7be8e40a46e8b0eced40b0de447bdb42f68e","version":"70cd7be8e40a46e8b0eced40b0de447bdb42f68e"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.55"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.55"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.19"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4","digest":"sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.4@sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} # ___ _ _ # / _ \ | | (_) @@ -24,8 +24,11 @@ # # # Frontmatter env variables: -# - HEAD_BRANCH: (main workflow) -# - WORKFLOW_CONCLUSION: (main workflow) +# - NIGHT_OWL_BASE_BRANCH: (main workflow) +# - NIGHT_OWL_CANDIDATE_JQL: (main workflow) +# - NIGHT_OWL_JIRA_LABEL: (main workflow) +# - NIGHT_OWL_JIRA_PROJECT: (main workflow) +# - NIGHT_OWL_SLACK_CHANNEL_ID: (main workflow) # # Secrets used: # - COPILOT_GITHUB_TOKEN @@ -55,6 +58,8 @@ name: "CI Failure Triage Agent" on: + schedule: + - cron: "0 0 * * *" workflow_dispatch: inputs: aw_context: @@ -62,38 +67,22 @@ on: description: "Agent caller context (used internally by Agentic Workflows)." required: false type: string - head_branch: - description: Head branch of the failed workflow run - workflow_conclusion: - default: failure - description: Conclusion of the failed workflow run - workflow_run: - # zizmor: ignore[dangerous-triggers] - workflow_run trigger is secured with role and fork validation - branches: - - task/dam/enable-ci-failure-triager - - master - types: - - completed - workflows: - - "*" permissions: {} -concurrency: ci-triage-${{ github.run_id }} +concurrency: night-owl run-name: "CI Failure Triage Agent" env: - HEAD_BRANCH: ${{ inputs.head_branch || github.event.workflow_run.head_branch }} - WORKFLOW_CONCLUSION: ${{ inputs.workflow_conclusion || github.event.workflow_run.conclusion }} + NIGHT_OWL_BASE_BRANCH: master + NIGHT_OWL_CANDIDATE_JQL: project = CLI AND labels = "for-agent" AND statusCategory = "To Do" ORDER BY created ASC + NIGHT_OWL_JIRA_LABEL: for-agent + NIGHT_OWL_JIRA_PROJECT: CLI + NIGHT_OWL_SLACK_CHANNEL_ID: C0B7GN16473 jobs: activation: - needs: pre_activation - # zizmor: ignore[dangerous-triggers] - workflow_run trigger is secured with role and fork validation - if: > - (needs.pre_activation.outputs.activated == 'true') && (github.event_name != 'workflow_run' || github.event.workflow_run.repository.id == github.repository_id && - (!(github.event.workflow_run.repository.fork))) runs-on: ubuntu-slim permissions: actions: read @@ -116,8 +105,6 @@ jobs: with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} - trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} - parent-span-id: ${{ needs.pre_activation.outputs.setup-parent-span-id || needs.pre_activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "CI Failure Triage Agent" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/ci-failure-triage-agent.lock.yml@${{ github.ref }} @@ -137,7 +124,7 @@ jobs: 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_ALLOWED_DOMAINS: '["defaults","mcp.atlassian.com"]' GH_AW_INFO_FIREWALL_ENABLED: "true" GH_AW_INFO_AWF_VERSION: "v0.25.55" GH_AW_INFO_AWMG_VERSION: "" @@ -215,23 +202,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_71205a221871e33c_EOF' + cat << 'GH_AW_PROMPT_8b50d7b8fbf6689a_EOF' - GH_AW_PROMPT_71205a221871e33c_EOF + GH_AW_PROMPT_8b50d7b8fbf6689a_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_71205a221871e33c_EOF' + cat << 'GH_AW_PROMPT_8b50d7b8fbf6689a_EOF' Tools: create_pull_request, missing_tool, missing_data, slack_notify - GH_AW_PROMPT_71205a221871e33c_EOF + GH_AW_PROMPT_8b50d7b8fbf6689a_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_71205a221871e33c_EOF' + cat << 'GH_AW_PROMPT_8b50d7b8fbf6689a_EOF' - GH_AW_PROMPT_71205a221871e33c_EOF + GH_AW_PROMPT_8b50d7b8fbf6689a_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_71205a221871e33c_EOF' + cat << 'GH_AW_PROMPT_8b50d7b8fbf6689a_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -263,12 +250,12 @@ jobs: - **Note**: If a branch you need is not in the list above and is not listed as an additional fetched ref, it has NOT been checked out. For private repositories you cannot fetch it without proper authentication. If the branch is required and not available, exit with an error and ask the user to add it to the `fetch:` option of the `checkout:` configuration (e.g., `fetch: ["refs/pulls/open/*"]` for all open PR refs, or `fetch: ["main", "feature/my-branch"]` for specific branches). - GH_AW_PROMPT_71205a221871e33c_EOF + GH_AW_PROMPT_8b50d7b8fbf6689a_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_71205a221871e33c_EOF' + cat << 'GH_AW_PROMPT_8b50d7b8fbf6689a_EOF' {{#runtime-import .github/workflows/ci-failure-triage-agent.md}} - GH_AW_PROMPT_71205a221871e33c_EOF + GH_AW_PROMPT_8b50d7b8fbf6689a_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -294,7 +281,6 @@ jobs: GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' - GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -314,8 +300,7 @@ jobs: 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_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, - GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST } }); - name: Validate prompt placeholders @@ -347,10 +332,11 @@ jobs: retention-days: 1 agent: - needs: activation + needs: + - activation + - atlassian_mcp_auth runs-on: ubuntu-latest permissions: - actions: read contents: read issues: read pull-requests: read @@ -482,25 +468,25 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_6c0d4912a9f47461_EOF' - {"create_pull_request":{"base_branch":"${{ env.HEAD_BRANCH }}","draft":true,"labels":["ci-fix","automated"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"allowed","title_prefix":"[ci-fix] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"report_incomplete":{},"slack-notify":{"description":"Send a CI failure triage message to Slack","inputs":{"message":{"default":null,"description":"The triage message to send","required":true,"type":"string"}}}} - GH_AW_SAFE_OUTPUTS_CONFIG_6c0d4912a9f47461_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_3d3eb64fbd6cbdfd_EOF' + {"create_pull_request":{"base_branch":"${{ env.NIGHT_OWL_BASE_BRANCH }}","draft":true,"labels":["night-owl","automated"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"allowed","title_prefix":"[night-owl] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"report_incomplete":{},"slack-notify":{"description":"Send a night-owl message to Slack","inputs":{"message":{"default":null,"description":"The night-owl message to send","required":true,"type":"string"}}}} + GH_AW_SAFE_OUTPUTS_CONFIG_3d3eb64fbd6cbdfd_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | { "description_suffixes": { - "create_pull_request": " CONSTRAINTS: Maximum 1 pull request(s) can be created. Title will be prefixed with \"[ci-fix] \". Labels [\"ci-fix\" \"automated\"] will be automatically added. PRs will be created as drafts." + "create_pull_request": " CONSTRAINTS: Maximum 1 pull request(s) can be created. Title will be prefixed with \"[night-owl] \". Labels [\"night-owl\" \"automated\"] will be automatically added. PRs will be created as drafts." }, "repo_params": {}, "dynamic_tools": [ { - "description": "Send a CI failure triage message to Slack", + "description": "Send a night-owl message to Slack", "inputSchema": { "additionalProperties": false, "properties": { "message": { - "description": "The triage message to send", + "description": "The night-owl message to send", "type": "string" } }, @@ -704,9 +690,29 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_f5c54b8c57ac837c_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_628f86365c716de9_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { + "atlassian": { + "type": "http", + "url": "https://mcp.atlassian.com/v1/mcp", + "headers": { + "Authorization": "${{ needs.atlassian_mcp_auth.outputs.authorization_header }}" + }, + "tools": [ + "getAccessibleAtlassianResources", + "searchJiraIssuesUsingJql", + "getJiraIssue", + "getJiraIssueRemoteIssueLinks" + ], + "guard-policies": { + "write-sink": { + "accept": [ + "*" + ] + } + } + }, "github": { "type": "stdio", "container": "ghcr.io/github/github-mcp-server:v1.0.4", @@ -714,7 +720,7 @@ jobs: "GITHUB_HOST": "\${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", - "GITHUB_TOOLSETS": "context,repos,issues,pull_requests,actions" + "GITHUB_TOOLSETS": "context,repos,pull_requests" }, "guard-policies": { "allow-only": { @@ -745,7 +751,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_f5c54b8c57ac837c_EOF + GH_AW_MCP_CONFIG_628f86365c716de9_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -779,7 +785,7 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.55/awf-config.schema.json","network":{"allowDomains":["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","www.googleapis.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5.3","gemini-pro","any"],"antigravity":["copilot/antigravity*","google/antigravity*","gemini/antigravity*"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"claude":["agent"],"codex":["agent"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"computer-use":["copilot/*computer-use*","google/*computer-use*","gemini/*computer-use*","openai/*computer-use*"],"copilot":["agent"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent"],"gemini-3-flash":["copilot/gemini-3*flash*","google/gemini-3*flash*","gemini/gemini-3*flash*"],"gemini-3-pro":["copilot/gemini-3*pro*","google/gemini-3*pro*","gemini/gemini-3*pro*"],"gemini-3.1-flash":["copilot/gemini-3.1*flash*","google/gemini-3.1*flash*","gemini/gemini-3.1*flash*"],"gemini-3.1-pro":["copilot/gemini-3.1*pro*","google/gemini-3.1*pro*","gemini/gemini-3.1*pro*"],"gemini-3.5-flash":["copilot/gemini-3.5*flash*","google/gemini-3.5*flash*","gemini/gemini-3.5*flash*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"gpt-5.2":["copilot/gpt-5.2*","openai/gpt-5.2*"],"gpt-5.3":["copilot/gpt-5.3*","openai/gpt-5.3*"],"gpt-5.4":["copilot/gpt-5.4*","openai/gpt-5.4*"],"gpt-5.5":["copilot/gpt-5.5*","openai/gpt-5.5*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"opus":["copilot/*opus*","anthropic/*opus*"],"opusplan":["opus?effort=high"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"robotics":["copilot/*robotics*","google/*robotics*","gemini/*robotics*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4-5-*","anthropic/*sonnet-4-5-*","copilot/*sonnet-4-6*","anthropic/*sonnet-4-6*"],"summarization":["haiku","gpt-5-mini","gemini-flash-lite","mini"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.25.55"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.55/awf-config.schema.json","network":{"allowDomains":["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","mcp.atlassian.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","www.googleapis.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5.3","gemini-pro","any"],"antigravity":["copilot/antigravity*","google/antigravity*","gemini/antigravity*"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"claude":["agent"],"codex":["agent"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"computer-use":["copilot/*computer-use*","google/*computer-use*","gemini/*computer-use*","openai/*computer-use*"],"copilot":["agent"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent"],"gemini-3-flash":["copilot/gemini-3*flash*","google/gemini-3*flash*","gemini/gemini-3*flash*"],"gemini-3-pro":["copilot/gemini-3*pro*","google/gemini-3*pro*","gemini/gemini-3*pro*"],"gemini-3.1-flash":["copilot/gemini-3.1*flash*","google/gemini-3.1*flash*","gemini/gemini-3.1*flash*"],"gemini-3.1-pro":["copilot/gemini-3.1*pro*","google/gemini-3.1*pro*","gemini/gemini-3.1*pro*"],"gemini-3.5-flash":["copilot/gemini-3.5*flash*","google/gemini-3.5*flash*","gemini/gemini-3.5*flash*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"gpt-5.2":["copilot/gpt-5.2*","openai/gpt-5.2*"],"gpt-5.3":["copilot/gpt-5.3*","openai/gpt-5.3*"],"gpt-5.4":["copilot/gpt-5.4*","openai/gpt-5.4*"],"gpt-5.5":["copilot/gpt-5.5*","openai/gpt-5.5*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"opus":["copilot/*opus*","anthropic/*opus*"],"opusplan":["opus?effort=high"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"robotics":["copilot/*robotics*","google/*robotics*","gemini/*robotics*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4-5-*","anthropic/*sonnet-4-5-*","copilot/*sonnet-4-6*","anthropic/*sonnet-4-6*"],"summarization":["haiku","gpt-5-mini","gemini-flash-lite","mini"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.25.55"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then @@ -875,7 +881,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.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,www.googleapis.com" + 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,mcp.atlassian.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,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} with: @@ -972,10 +978,47 @@ jobs: /tmp/gh-aw/sandbox/firewall/awf-reflect.json if-no-files-found: ignore + atlassian_mcp_auth: + needs: activation + runs-on: ubuntu-latest + permissions: + id-token: write + + outputs: + authorization_header: ${{ steps.build_auth_header.outputs.authorization_header }} + steps: + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Get Jira credentials from Vault + id: secrets + uses: SonarSource/vault-action-wrapper@c154b4a417b51cb98dd71137f49bf20e77c56820 # c154b4a417b51cb98dd71137f49bf20e77c56820 + with: + secrets: | + development/kv/data/jira user | JIRA_USER; + development/kv/data/jira token | JIRA_TOKEN; + - name: Build Atlassian MCP authorization header + id: build_auth_header + run: | + encoded=$(printf '%s' "${JIRA_USER}:${JIRA_TOKEN}" | base64 | tr -d '\n') + echo "::add-mask::${encoded}" + echo "authorization_header=Basic ${encoded}" >> "$GITHUB_OUTPUT" + env: + JIRA_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_TOKEN }} + JIRA_USER: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_USER }} + shell: bash + conclusion: needs: - activation - agent + - atlassian_mcp_auth - detection - safe_outputs - slack_notify @@ -1313,40 +1356,6 @@ jobs: } } - pre_activation: - runs-on: ubuntu-slim - outputs: - activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} - matched_command: '' - setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} - setup-span-id: ${{ steps.setup.outputs.span-id }} - setup-trace-id: ${{ steps.setup.outputs.trace-id }} - steps: - - name: Setup Scripts - id: setup - uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1 - with: - destination: ${{ runner.temp }}/gh-aw/actions - job-name: ${{ github.job }} - env: - GH_AW_SETUP_WORKFLOW_NAME: "CI Failure Triage Agent" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/ci-failure-triage-agent.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.52" - GH_AW_INFO_AWF_VERSION: "v0.25.55" - GH_AW_INFO_ENGINE_ID: "copilot" - - name: Check team membership for workflow - id: check_membership - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_REQUIRED_ROLES: "admin,maintainer,write" - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); - await main(); - safe_outputs: needs: - activation @@ -1449,7 +1458,7 @@ jobs: if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') && github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - ref: ${{ env.HEAD_BRANCH }} + ref: ${{ env.NIGHT_OWL_BASE_BRANCH }} token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} persist-credentials: false fetch-depth: 0 @@ -1482,11 +1491,11 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} - 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,www.googleapis.com" + 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,mcp.atlassian.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,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} GH_AW_SAFE_OUTPUT_JOBS: "{\"slack_notify\":\"\"}" - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"base_branch\":\"${{ env.HEAD_BRANCH }}\",\"draft\":true,\"labels\":[\"ci-fix\",\"automated\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"allowed\",\"title_prefix\":\"[ci-fix] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"base_branch\":\"${{ env.NIGHT_OWL_BASE_BRANCH }}\",\"draft\":true,\"labels\":[\"night-owl\",\"automated\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"allowed\",\"title_prefix\":\"[night-owl] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"report_incomplete\":{}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -1550,6 +1559,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json SLACK_BOT_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).SLACK_BOT_TOKEN }} with: - channel-id: squad-integration-on-call + channel-id: ${{ env.NIGHT_OWL_SLACK_CHANNEL_ID }} slack-message: ${{ steps.extract.outputs.message }} diff --git a/.github/workflows/ci-failure-triage-agent.md b/.github/workflows/ci-failure-triage-agent.md index 9697825f3..4bd60704f 100644 --- a/.github/workflows/ci-failure-triage-agent.md +++ b/.github/workflows/ci-failure-triage-agent.md @@ -1,56 +1,91 @@ --- on: + schedule: + - cron: '0 0 * * *' workflow_dispatch: - inputs: - workflow_conclusion: - description: 'Conclusion of the failed workflow run' - default: 'failure' - head_branch: - description: 'Head branch of the failed workflow run' - workflow_run: - workflows: ["*"] - types: [completed] - branches: - - 'task/dam/enable-ci-failure-triager' - - 'master' - -concurrency: ci-triage-${{ github.run_id }} + +concurrency: night-owl permissions: contents: read - actions: read issues: read pull-requests: read checkout: - fetch-depth: 0 +network: + allowed: + - defaults + - mcp.atlassian.com + tools: github: - toolsets: [context, repos, issues, pull_requests, actions] + toolsets: [context, repos, pull_requests] + +mcp-servers: + atlassian: + type: http + url: https://mcp.atlassian.com/v1/mcp + headers: + Authorization: "${{ needs.atlassian_mcp_auth.outputs.authorization_header }}" + allowed: + - getAccessibleAtlassianResources + - searchJiraIssuesUsingJql + - getJiraIssue + - getJiraIssueRemoteIssueLinks env: - WORKFLOW_CONCLUSION: ${{ inputs.workflow_conclusion || github.event.workflow_run.conclusion }} - HEAD_BRANCH: ${{ inputs.head_branch || github.event.workflow_run.head_branch }} + NIGHT_OWL_BASE_BRANCH: master + NIGHT_OWL_CANDIDATE_JQL: 'project = CLI AND labels = "for-agent" AND statusCategory = "To Do" ORDER BY created ASC' + NIGHT_OWL_JIRA_LABEL: for-agent + NIGHT_OWL_JIRA_PROJECT: CLI + NIGHT_OWL_SLACK_CHANNEL_ID: C0B7GN16473 + +jobs: + atlassian_mcp_auth: + runs-on: ubuntu-latest + permissions: + id-token: write + outputs: + authorization_header: ${{ steps.build_auth_header.outputs.authorization_header }} + steps: + - name: Get Jira credentials from Vault + id: secrets + uses: SonarSource/vault-action-wrapper@c154b4a417b51cb98dd71137f49bf20e77c56820 + with: + secrets: | + development/kv/data/jira user | JIRA_USER; + development/kv/data/jira token | JIRA_TOKEN; + - name: Build Atlassian MCP authorization header + id: build_auth_header + shell: bash + run: | + encoded=$(printf '%s' "${JIRA_USER}:${JIRA_TOKEN}" | base64 | tr -d '\n') + echo "::add-mask::${encoded}" + echo "authorization_header=Basic ${encoded}" >> "$GITHUB_OUTPUT" + env: + JIRA_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_TOKEN }} + JIRA_USER: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_USER }} safe-outputs: noop: false create-pull-request: draft: true - title-prefix: "[ci-fix] " - labels: [ci-fix, automated] + title-prefix: "[night-owl] " + labels: [night-owl, automated] protected-files: allowed - base-branch: ${{ env.HEAD_BRANCH }} + base-branch: ${{ env.NIGHT_OWL_BASE_BRANCH }} jobs: slack-notify: needs: safe_outputs - description: "Send a CI failure triage message to Slack" + description: "Send a night-owl message to Slack" runs-on: ubuntu-latest permissions: id-token: write inputs: message: - description: "The triage message to send" + description: "The night-owl message to send" required: true type: string steps: @@ -80,153 +115,99 @@ safe-outputs: env: SLACK_BOT_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).SLACK_BOT_TOKEN }} with: - channel-id: squad-integration-on-call + channel-id: ${{ env.NIGHT_OWL_SLACK_CHANNEL_ID }} slack-message: ${{ steps.extract.outputs.message }} --- # CI Failure Triage Agent -## Instructions - -1. Check the `$WORKFLOW_CONCLUSION` environment variable. If it is not `failure`, stop immediately and do nothing. -2. Follow the triage skill instructions below. - ---- - -# CI Failure Triage Skill - -You are a CI failure triage agent. When a workflow run fails on `master` -(or a `task/dam/enable-ci-failure-triager` development branch), you diagnose the failure, -post a proposed remediation to Slack, and — when you have high confidence -in a mechanical fix — open a PR with the fix automatically. - ---- - -## 1. Extract context +This workflow is temporarily repurposed to run the Night Owl implementation flow so it can be triggered from the existing workflow slot on `master`. -Use the GitHub MCP to read the workflow run that triggered this agent. Extract: +## Instructions -- **Run ID** -- **Workflow name** and a direct URL to the run -- **Branch** (head branch) -- **Commit SHA** (head SHA) -- **Commit author** and **short commit message** (first line) +1. Read `AGENTS.md` and `CLAUDE.md` before changing code so you follow the repository-specific rules. +2. Use Atlassian MCP for all Jira discovery and ticket reading. Do not rely on any separate hand-written brief. +3. Use GitHub MCP plus the local checkout for repository context, implementation work, and PR checks. +4. Emit exactly one `slack_notify` safe output for every terminal outcome: + - `starving`: no eligible Jira ticket remains after filtering and open-PR deduplication; + - `blocked`: the selected ticket or its parent/linked context is missing key product or implementation decisions; + - `draft-pr-opened`: the implementation is done and a draft PR was created. ---- +## 1. Discover candidate Jira tickets -## 2. Loop guard +1. Use `getAccessibleAtlassianResources` to find the SonarSource Jira Cloud site. +2. Use `searchJiraIssuesUsingJql` with `$NIGHT_OWL_CANDIDATE_JQL`. +3. Process candidates in the order returned by Jira. Do not randomize. +4. Keep only issues whose current status name is exactly `Open`, `TODO`, or `To Do`. +5. Work only on issues in project `$NIGHT_OWL_JIRA_PROJECT` with label `$NIGHT_OWL_JIRA_LABEL`. -Use the GitHub MCP to list previous runs of the `ci-failure-triage-agent` -workflow that share the same commit SHA. +## 2. Skip tickets already in flight -- If any previous run **completed successfully**, exit silently — a Slack - message has already been posted for this commit. -- If no successful prior run exists, continue. +For each candidate issue: ---- +1. Use GitHub MCP to inspect open pull requests in this repository. +2. If an open PR title, body, branch name, or obvious linked reference already targets the Jira key, skip that issue and continue with the next one. +3. If no eligible candidate remains after this deduplication, emit a `slack_notify` message that Night Owl is starving and stop successfully. -## 3. Fetch failure logs +## 3. Gather Jira context -Use the GitHub MCP to get the failed jobs for the run and their log output. +For the first eligible issue that is not already in flight: -Parse the output to identify: -- Which job(s) failed -- Which step(s) failed -- The exact error messages and stack traces +1. Read the Jira issue details, including summary, description, issue type, status, labels, comments, subtasks, and issue links. +2. If the issue has a parent or epic, read that issue too before making decisions. +3. Read additional linked Jira tickets only when they are directly relevant to implementation or acceptance criteria. +4. Treat Jira as the source of truth for the ticket context. ---- +## 4. Decide whether the ticket is actionable -## 4. Diagnose the failure +Stop and emit a `slack_notify` message without creating a PR if any required behavior is missing or ambiguous, including: -Categorise the root cause: +- acceptance criteria, +- scope boundaries, +- rollout expectations, +- API or CLI contract decisions, +- conflicting instructions between the ticket and its parent context. -| Category | Signals | -|----------|---------| -| **Flaky / transient** | "connection refused", "timeout", "rate limit", "502", intermittent network errors, `SIGKILL` with no code context | -| **Build error** | Compilation errors, missing imports, type errors | -| **Test failure** | Assertion errors, test assertion mismatches | -| **Lint / format** | Style violations, formatter diffs | -| **Code quality** | SonarCloud issues on changed files | -| **Dependency** | Lock file out of sync, missing package, version conflict | -| **Infrastructure** | Runner OOM, disk full, missing secret/env var | +The blocked Slack message must include: -Use the GitHub MCP to read the relevant source files referenced in the error -output to confirm the root cause before formulating a remediation. +- the Jira key and summary, +- the parent issue key and summary when you used one, +- a short explanation of why you stopped, +- the concrete missing decisions or questions that a human needs to answer. ---- +## 5. Implement the ticket -## 5. Formulate a proposed remediation +If the issue is actionable: -Write a concrete next step — what a human (or a future write-enabled agent) -should do. Tailor it to the category: +1. Read the relevant repository files and follow `AGENTS.md` / `CLAUDE.md`. +2. Create a branch before committing, using the Jira key in the branch name, for example `night-owl/CLI-123-short-slug`. +3. Implement the change. +4. Run the smallest set of meaningful checks that match the files you changed. If you edit TypeScript, follow the repository formatting requirements before you finish. +5. If you cannot finish a coherent implementation or cannot validate it well enough to justify a draft PR, stop and emit a blocked Slack message explaining what remains. -| Category | Proposed remediation | -|----------|----------------------| -| Flaky / transient | Re-run the failed jobs | -| Build / test / lint | "Investigate `` — the error suggests ``" | -| Code quality | "Submit `sonar remediate --project --issues ,`" | -| Dependency | "Regenerate the lockfile via ``" | -| Infrastructure | "Check runner capacity / verify secret `` is set" | -| Uncertain | "Manual investigation needed — error at `` does not match a known pattern" | +## 6. Open a draft PR ---- +When the implementation is complete: -## 5b. Auto-fix (conditional) - -After diagnosing and formulating a remediation, assess your confidence in the fix: - -- **High confidence**: the fix is mechanical or deterministic — formatting, missing import, - lockfile regeneration, simple typo, straightforward test assertion update. -- **Low confidence**: the fix requires design decisions, affects multiple systems, the root - cause is uncertain, or the change is non-trivial. - -**If confidence is high:** - -1. Use the GitHub MCP to search for open PRs with `[ci-fix]` in the title that touch the - same file(s). If one already exists, skip PR creation. -2. Apply the fix. You **must** create a branch before committing — the framework - bundles your changes by branch ref, and without a `refs/heads/*` entry the - bundle will fail to apply. - ```bash - # 1. Create a branch (any ci-fix/ name works) - git checkout -b ci-fix/ - # 2. Make the code changes to the file(s) - # 3. Stage ALL changed files explicitly - git add ... - # 4. Verify there are staged changes - git status - # 5. Commit - git commit -m ": " - ``` -3. Open a PR via the `create-pull-request` safe output. In the PR description include: - - The diagnosis category and root cause summary - - A link to the failed workflow run - - The exact error that was fixed -4. Note the PR URL for inclusion in the Slack message. - -**If confidence is low:** skip PR creation entirely and proceed to Slack notification. +1. Use the `create-pull-request` safe output. The PR must remain a draft. +2. Use a PR title that starts with the Jira key, for example `CLI-123 Implement ...`. +3. In the PR description include: + - the Jira ticket link and summary, + - the parent ticket link if you used parent context, + - a short implementation summary, + - the checks you ran, + - any open questions or follow-ups that remain. ---- - -## 6. Notify Slack - -Emit a `slack_notify` safe output with: +## 7. Notify Slack -- `message`: a structured message containing: - - **Workflow**: `{workflow-name}` — link to the run - - **Branch**: `{head-branch}` - - **Commit**: `{short-sha}` by `{author}` — `{commit-subject}` - - **Diagnosis**: `{category}` — 1–2 sentences explaining the root cause - - **Proposed remediation**: the concrete next step from §5 - - **Fix PR**: if a PR was created in §5b, include the PR URL - ---- +Emit a `slack_notify` message for every terminal outcome. -## Rules +The message should be structured and concise: -- Post **at most one** Slack message per failed commit (loop guard in §2). -- Only create a PR when confidence in the fix is high and no existing `[ci-fix]` PR addresses the same issue. -- Only modify files when creating a high-confidence fix PR. Never open issues. -- If the failure category is uncertain, still post to Slack — include the raw error - excerpt and flag the diagnosis as "uncertain". +- `Night Owl`: starving, blocked, or draft PR opened +- `Ticket`: Jira key, link, and summary when applicable +- `Parent`: parent Jira key and summary when applicable +- `Outcome`: one or two sentences +- `PR`: include the PR URL when one was created +- `Checks`: include the checks you ran when a PR was created diff --git a/.github/workflows/night-owl.lock.yml b/.github/workflows/night-owl.lock.yml new file mode 100644 index 000000000..f70f17ef4 --- /dev/null +++ b/.github/workflows/night-owl.lock.yml @@ -0,0 +1,1564 @@ +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"fccf4857a7b6fcff5ad5f7d961efe7ebee0edfa829ee241182736c9f259d1253","compiler_version":"v0.76.1","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"SonarSource/vault-action-wrapper","sha":"c154b4a417b51cb98dd71137f49bf20e77c56820","version":"c154b4a417b51cb98dd71137f49bf20e77c56820"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"46d564922b082d0db93244972e8005ea6904ee5f","version":"v0.76.1"},{"repo":"slackapi/slack-github-action","sha":"70cd7be8e40a46e8b0eced40b0de447bdb42f68e","version":"70cd7be8e40a46e8b0eced40b0de447bdb42f68e"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.55"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.55"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.19"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4","digest":"sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.4@sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by gh-aw (v0.76.1). DO NOT EDIT. +# +# To update this file, edit the corresponding .md file 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/ +# +# +# Frontmatter env variables: +# - NIGHT_OWL_BASE_BRANCH: (main workflow) +# - NIGHT_OWL_CANDIDATE_JQL: (main workflow) +# - NIGHT_OWL_JIRA_LABEL: (main workflow) +# - NIGHT_OWL_JIRA_PROJECT: (main workflow) +# - NIGHT_OWL_SLACK_CHANNEL_ID: (main workflow) +# +# Secrets used: +# - COPILOT_GITHUB_TOKEN +# - GH_AW_CI_TRIGGER_TOKEN +# - GH_AW_GITHUB_MCP_SERVER_TOKEN +# - GH_AW_GITHUB_TOKEN +# - GITHUB_TOKEN +# +# Custom actions used: +# - SonarSource/vault-action-wrapper@c154b4a417b51cb98dd71137f49bf20e77c56820 # c154b4a417b51cb98dd71137f49bf20e77c56820 +# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 +# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) +# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 +# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 +# - github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1 +# - slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # 70cd7be8e40a46e8b0eced40b0de447bdb42f68e +# +# Container images used: +# - ghcr.io/github/gh-aw-firewall/agent:0.25.55 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55 +# - ghcr.io/github/gh-aw-firewall/squid:0.25.55 +# - ghcr.io/github/gh-aw-mcpg:v0.3.19 +# - ghcr.io/github/github-mcp-server:v1.0.4@sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4 +# - node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14 + +name: "Night Owl" +on: + schedule: + - cron: "0 0 * * *" + workflow_dispatch: + inputs: + aw_context: + default: "" + description: "Agent caller context (used internally by Agentic Workflows)." + required: false + type: string + +permissions: {} + +concurrency: night-owl + +run-name: "Night Owl" + +env: + NIGHT_OWL_BASE_BRANCH: master + NIGHT_OWL_CANDIDATE_JQL: project = CLI AND labels = "for-agent" AND statusCategory = "To Do" ORDER BY created ASC + NIGHT_OWL_JIRA_LABEL: for-agent + NIGHT_OWL_JIRA_PROJECT: CLI + NIGHT_OWL_SLACK_CHANNEL_ID: C0B7GN16473 + +jobs: + activation: + runs-on: ubuntu-slim + permissions: + actions: read + contents: read + outputs: + comment_id: "" + comment_repo: "" + engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} + lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} + model: ${{ steps.generate_aw_info.outputs.model }} + secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Night Owl" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/night-owl.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.52" + GH_AW_INFO_AWF_VERSION: "v0.25.55" + GH_AW_INFO_ENGINE_ID: "copilot" + - 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 || 'claude-sonnet-4.6' }} + GH_AW_INFO_VERSION: "1.0.52" + GH_AW_INFO_AGENT_VERSION: "1.0.52" + GH_AW_INFO_CLI_VERSION: "v0.76.1" + GH_AW_INFO_WORKFLOW_NAME: "Night Owl" + GH_AW_INFO_EXPERIMENTAL: "false" + GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" + GH_AW_INFO_STAGED: "false" + GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","mcp.atlassian.com"]' + GH_AW_INFO_FIREWALL_ENABLED: "true" + GH_AW_INFO_AWF_VERSION: "v0.25.55" + GH_AW_INFO_AWMG_VERSION: "" + GH_AW_INFO_FIREWALL_TYPE: "squid" + GH_AW_COMPILED_STRICT: "true" + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); + await main(core, context); + - name: Validate COPILOT_GITHUB_TOKEN secret + id: validate-secret + run: bash "${RUNNER_TEMP}/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: + persist-credentials: false + sparse-checkout: | + .github + .agents + .antigravity + .claude + .codex + .crush + .gemini + .opencode + .pi + sparse-checkout-cone-mode: true + fetch-depth: 1 + - name: Save agent config folders for base branch restoration + env: + GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" + - name: Check workflow lock file + id: check-lock-file + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_WORKFLOW_FILE: "night-owl.lock.yml" + GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); + await main(); + - name: Check compile-agentic version + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_COMPILED_VERSION: "v0.76.1" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.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: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + # poutine:ignore untrusted_checkout_exec + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" + { + cat << 'GH_AW_PROMPT_8b50d7b8fbf6689a_EOF' + + GH_AW_PROMPT_8b50d7b8fbf6689a_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" + cat << 'GH_AW_PROMPT_8b50d7b8fbf6689a_EOF' + + Tools: create_pull_request, missing_tool, missing_data, slack_notify + GH_AW_PROMPT_8b50d7b8fbf6689a_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" + cat << 'GH_AW_PROMPT_8b50d7b8fbf6689a_EOF' + + GH_AW_PROMPT_8b50d7b8fbf6689a_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" + cat << 'GH_AW_PROMPT_8b50d7b8fbf6689a_EOF' + + The following GitHub context information is available for this workflow: + {{#if github.actor}} + - **actor**: __GH_AW_GITHUB_ACTOR__ + {{/if}} + {{#if github.repository}} + - **repository**: __GH_AW_GITHUB_REPOSITORY__ + {{/if}} + {{#if github.workspace}} + - **workspace**: __GH_AW_GITHUB_WORKSPACE__ + {{/if}} + {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}} + - **issue-number**: #__GH_AW_EXPR_802A9F6A__ + {{/if}} + {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}} + - **discussion-number**: #__GH_AW_EXPR_1A3A194A__ + {{/if}} + {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}} + - **pull-request-number**: #__GH_AW_EXPR_463A214A__ + {{/if}} + {{#if github.event.comment.id || github.aw.context.comment_id}} + - **comment-id**: __GH_AW_EXPR_FF1D34CE__ + {{/if}} + {{#if github.run_id}} + - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ + {{/if}} + - **checkouts**: The following repositories have been checked out and are available in the workspace: + - `$GITHUB_WORKSPACE` → `__GH_AW_GITHUB_REPOSITORY__` (cwd) [full history, all branches available as remote-tracking refs] + - **Note**: If a branch you need is not in the list above and is not listed as an additional fetched ref, it has NOT been checked out. For private repositories you cannot fetch it without proper authentication. If the branch is required and not available, exit with an error and ask the user to add it to the `fetch:` option of the `checkout:` configuration (e.g., `fetch: ["refs/pulls/open/*"]` for all open PR refs, or `fetch: ["main", "feature/my-branch"]` for specific branches). + + + GH_AW_PROMPT_8b50d7b8fbf6689a_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" + cat << 'GH_AW_PROMPT_8b50d7b8fbf6689a_EOF' + + {{#runtime-import .github/workflows/night-owl.md}} + GH_AW_PROMPT_8b50d7b8fbf6689a_EOF + } > "$GH_AW_PROMPT" + - name: Interpolate variables and render templates + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_ENGINE_ID: "copilot" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); + await main(); + - name: Substitute placeholders + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + + const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); + + // Call the substitution function + return await substitutePlaceholders({ + file: process.env.GH_AW_PROMPT, + substitutions: { + GH_AW_EXPR_1A3A194A: process.env.GH_AW_EXPR_1A3A194A, + GH_AW_EXPR_463A214A: process.env.GH_AW_EXPR_463A214A, + GH_AW_EXPR_802A9F6A: process.env.GH_AW_EXPR_802A9F6A, + GH_AW_EXPR_FF1D34CE: process.env.GH_AW_EXPR_FF1D34CE, + GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, + 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_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST + } + }); + - name: Validate prompt placeholders + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" + - name: Print prompt + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" + - name: Upload activation artifact + if: success() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: activation + include-hidden-files: true + path: | + /tmp/gh-aw/aw_info.json + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/aw-prompts/prompt-template.txt + /tmp/gh-aw/aw-prompts/prompt-import-tree.json + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/base + /tmp/gh-aw/.github/agents + /tmp/gh-aw/.github/skills + if-no-files-found: ignore + retention-days: 1 + + agent: + needs: + - activation + - atlassian_mcp_auth + 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_WORKFLOW_ID_SANITIZED: nightowl + outputs: + agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} + checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} + effective_tokens_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.effective_tokens_rate_limit_error || 'false' }} + has_patch: ${{ steps.collect_output.outputs.has_patch }} + inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} + model: ${{ needs.activation.outputs.model }} + model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Night Owl" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/night-owl.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.52" + GH_AW_INFO_AWF_VERSION: "v0.25.55" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Set runtime paths + id: set-runtime-paths + run: | + { + echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" + echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" + echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" + } >> "$GITHUB_OUTPUT" + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + fetch-depth: 0 + - name: Create gh-aw temp directory + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" + - name: Configure gh CLI for GitHub Enterprise + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" + env: + GH_TOKEN: ${{ github.token }} + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ 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:${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@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + 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('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); + await main(); + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.52 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.55 + - name: Determine automatic lockdown mode for GitHub MCP Server + id: determine-automatic-lockdown + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) + 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('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); + await determineAutomaticLockdown(github, context, core); + - name: Download activation artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: activation + path: /tmp/gh-aw + - name: Restore agent config folders from base branch + if: steps.checkout-pr.outcome == 'success' + env: + GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" + - name: Restore inline sub-agents from activation artifact + env: + GH_AW_SUB_AGENT_DIR: ".github/agents" + GH_AW_SUB_AGENT_EXT: ".agent.md" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" + - name: Restore inline skills from activation artifact + env: + GH_AW_SKILL_DIR: ".github/skills" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.55 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55 ghcr.io/github/gh-aw-firewall/squid:0.25.55 ghcr.io/github/gh-aw-mcpg:v0.3.19 ghcr.io/github/github-mcp-server:v1.0.4@sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4 node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14 + - name: Generate Safe Outputs Config + run: | + mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" + mkdir -p /tmp/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_3d3eb64fbd6cbdfd_EOF' + {"create_pull_request":{"base_branch":"${{ env.NIGHT_OWL_BASE_BRANCH }}","draft":true,"labels":["night-owl","automated"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"allowed","title_prefix":"[night-owl] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"report_incomplete":{},"slack-notify":{"description":"Send a night-owl message to Slack","inputs":{"message":{"default":null,"description":"The night-owl message to send","required":true,"type":"string"}}}} + GH_AW_SAFE_OUTPUTS_CONFIG_3d3eb64fbd6cbdfd_EOF + - name: Generate Safe Outputs Tools + env: + GH_AW_TOOLS_META_JSON: | + { + "description_suffixes": { + "create_pull_request": " CONSTRAINTS: Maximum 1 pull request(s) can be created. Title will be prefixed with \"[night-owl] \". Labels [\"night-owl\" \"automated\"] will be automatically added. PRs will be created as drafts." + }, + "repo_params": {}, + "dynamic_tools": [ + { + "description": "Send a night-owl message to Slack", + "inputSchema": { + "additionalProperties": false, + "properties": { + "message": { + "description": "The night-owl message to send", + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "name": "slack_notify" + } + ] + } + GH_AW_VALIDATION_JSON: | + { + "create_pull_request": { + "defaultMax": 1, + "fields": { + "base": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "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 + } + } + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 1024 + } + } + } + } + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); + await main(); + - 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: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + 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: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/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 + 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 "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" + + - name: Start MCP Gateway + id: start-mcp-gateway + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.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_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }} + GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }} + 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 "${RUNNER_TEMP}/gh-aw/mcp-config" + + # Export gateway environment variables for MCP config and gateway script + export MCP_GATEWAY_PORT="8080" + export MCP_GATEWAY_DOMAIN="host.docker.internal" + export MCP_GATEWAY_HOST_DOMAIN="localhost" + 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" + MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') + MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') + case "${DOCKER_HOST:-}" in + unix://* ) DOCKER_SOCK_PATH="${DOCKER_HOST#unix://}" ;; + /* ) DOCKER_SOCK_PATH="$DOCKER_HOST" ;; + * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; + esac + DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/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 DOCKER_HOST=unix:///var/run/docker.sock -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_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -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.3.19' + + mkdir -p /home/runner/.copilot + GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) + cat << GH_AW_MCP_CONFIG_628f86365c716de9_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + { + "mcpServers": { + "atlassian": { + "type": "http", + "url": "https://mcp.atlassian.com/v1/mcp", + "headers": { + "Authorization": "${{ needs.atlassian_mcp_auth.outputs.authorization_header }}" + }, + "tools": [ + "getAccessibleAtlassianResources", + "searchJiraIssuesUsingJql", + "getJiraIssue", + "getJiraIssueRemoteIssueLinks" + ], + "guard-policies": { + "write-sink": { + "accept": [ + "*" + ] + } + } + }, + "github": { + "type": "stdio", + "container": "ghcr.io/github/github-mcp-server:v1.0.4", + "env": { + "GITHUB_HOST": "\${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_READ_ONLY": "1", + "GITHUB_TOOLSETS": "context,repos,pull_requests" + }, + "guard-policies": { + "allow-only": { + "min-integrity": "$GITHUB_MCP_GUARD_MIN_INTEGRITY", + "repos": "$GITHUB_MCP_GUARD_REPOS" + } + } + }, + "safeoutputs": { + "type": "http", + "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", + "headers": { + "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + }, + "guard-policies": { + "write-sink": { + "accept": [ + "*" + ] + } + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + } + } + GH_AW_MCP_CONFIG_628f86365c716de9_EOF + - name: Mount MCP servers as CLIs + id: mount-mcp-clis + continue-on-error: true + env: + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); + await main(); + - name: Clean credentials + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" + - name: Audit pre-agent workspace + id: pre_agent_audit + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/audit_pre_agent_workspace.sh" + - name: Execute GitHub Copilot CLI + id: agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" + (umask 177 && touch /tmp/gh-aw/agent-stdio.log) + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.55/awf-config.schema.json","network":{"allowDomains":["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","mcp.atlassian.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","www.googleapis.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5.3","gemini-pro","any"],"antigravity":["copilot/antigravity*","google/antigravity*","gemini/antigravity*"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"claude":["agent"],"codex":["agent"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"computer-use":["copilot/*computer-use*","google/*computer-use*","gemini/*computer-use*","openai/*computer-use*"],"copilot":["agent"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent"],"gemini-3-flash":["copilot/gemini-3*flash*","google/gemini-3*flash*","gemini/gemini-3*flash*"],"gemini-3-pro":["copilot/gemini-3*pro*","google/gemini-3*pro*","gemini/gemini-3*pro*"],"gemini-3.1-flash":["copilot/gemini-3.1*flash*","google/gemini-3.1*flash*","gemini/gemini-3.1*flash*"],"gemini-3.1-pro":["copilot/gemini-3.1*pro*","google/gemini-3.1*pro*","gemini/gemini-3.1*pro*"],"gemini-3.5-flash":["copilot/gemini-3.5*flash*","google/gemini-3.5*flash*","gemini/gemini-3.5*flash*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"gpt-5.2":["copilot/gpt-5.2*","openai/gpt-5.2*"],"gpt-5.3":["copilot/gpt-5.3*","openai/gpt-5.3*"],"gpt-5.4":["copilot/gpt-5.4*","openai/gpt-5.4*"],"gpt-5.5":["copilot/gpt-5.5*","openai/gpt-5.5*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"opus":["copilot/*opus*","anthropic/*opus*"],"opusplan":["opus?effort=high"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"robotics":["copilot/*robotics*","google/*robotics*","gemini/*robotics*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4-5-*","anthropic/*sonnet-4-5-*","copilot/*sonnet-4-6*","anthropic/*sonnet-4-6*"],"summarization":["haiku","gpt-5-mini","gemini-flash-lite","mini"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.25.55"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + fi + # shellcheck disable=SC1003 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + env: + AWF_REFLECT_ENABLED: 1 + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + GH_AW_PHASE: agent + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_VERSION: v0.76.1 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows + 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 }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + XDG_CONFIG_HOME: /home/runner + - name: Detect agent errors + if: always() + id: detect-agent-errors + continue-on-error: true + run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ 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:${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: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" + - 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 "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" + - name: Redact secrets in logs + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/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 "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" + - name: Copy Safe Outputs + if: always() + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + run: | + mkdir -p /tmp/gh-aw + cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true + - name: Ingest agent output + id: collect_output + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.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,mcp.atlassian.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,www.googleapis.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs'); + await main(); + - name: Parse agent logs for step summary + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs'); + await main(); + - name: Parse MCP Gateway logs for step summary + if: always() + id: parse-mcp-gateway + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/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/audit dirs so they can be uploaded as artifacts + # AWF runs with sudo, creating files owned by root + sudo chmod -R a+rX /tmp/gh-aw/sandbox/firewall 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: Parse token usage for step summary + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); + - name: Print AWF reflect summary + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs'); + await main(); + - name: Write agent output placeholder if missing + if: always() + run: | + if [ ! -f /tmp/gh-aw/agent_output.json ]; then + echo '{"items":[]}' > /tmp/gh-aw/agent_output.json + fi + - name: Upload agent artifacts + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: agent + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log + /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/agent_usage.json + /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/pre-agent-audit.txt + /tmp/gh-aw/agent/ + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/safeoutputs.jsonl + /tmp/gh-aw/agent_output.json + /tmp/gh-aw/aw-*.patch + /tmp/gh-aw/aw-*.bundle + /tmp/gh-aw/awf-config.json + /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/sandbox/firewall/audit/ + /tmp/gh-aw/sandbox/firewall/awf-reflect.json + if-no-files-found: ignore + + atlassian_mcp_auth: + needs: activation + runs-on: ubuntu-latest + permissions: + id-token: write + + outputs: + authorization_header: ${{ steps.build_auth_header.outputs.authorization_header }} + steps: + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Get Jira credentials from Vault + id: secrets + uses: SonarSource/vault-action-wrapper@c154b4a417b51cb98dd71137f49bf20e77c56820 # c154b4a417b51cb98dd71137f49bf20e77c56820 + with: + secrets: | + development/kv/data/jira user | JIRA_USER; + development/kv/data/jira token | JIRA_TOKEN; + - name: Build Atlassian MCP authorization header + id: build_auth_header + run: | + encoded=$(printf '%s' "${JIRA_USER}:${JIRA_TOKEN}" | base64 | tr -d '\n') + echo "::add-mask::${encoded}" + echo "authorization_header=Basic ${encoded}" >> "$GITHUB_OUTPUT" + env: + JIRA_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_TOKEN }} + JIRA_USER: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_USER }} + shell: bash + + conclusion: + needs: + - activation + - agent + - atlassian_mcp_auth + - detection + - safe_outputs + - slack_notify + if: > + always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || + needs.activation.outputs.stale_lock_file_failed == 'true') + runs-on: ubuntu-slim + permissions: + contents: write + issues: write + pull-requests: write + concurrency: + group: "gh-aw-conclusion-night-owl" + cancel-in-progress: false + queue: max + outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Night Owl" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/night-owl.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.52" + GH_AW_INFO_AWF_VERSION: "v0.25.55" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Log detection run + id: detection_runs + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Night Owl" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/night-owl.md" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs'); + await main(); + - name: Record missing tool + id: missing_tool + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Night Owl" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/night-owl.md" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Night Owl" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/night-owl.md" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); + - name: Handle agent failure + id: handle_agent_failure + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Night Owl" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/night-owl.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: "night-owl" + GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} + GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} + GH_AW_EFFECTIVE_TOKENS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.effective_tokens_rate_limit_error || 'false' }} + GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} + GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} + GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} + GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" + 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_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} + GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} + GH_AW_GROUP_REPORTS: "false" + GH_AW_FAILURE_REPORT_AS_ISSUE: "true" + GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" + GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" + GH_AW_TIMEOUT_MINUTES: "20" + GH_AW_MAX_EFFECTIVE_TOKENS: "25000000" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); + await main(); + + detection: + needs: + - activation + - agent + if: > + always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} + detection_reason: ${{ steps.detection_conclusion.outputs.reason }} + detection_success: ${{ steps.detection_conclusion.outputs.success }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Night Owl" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/night-owl.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.52" + GH_AW_INFO_AWF_VERSION: "v0.25.55" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Checkout repository for patch context + if: needs.agent.outputs.has_patch == 'true' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + # --- Threat Detection --- + - name: Clean stale firewall files from agent artifact + run: | + rm -rf /tmp/gh-aw/sandbox/firewall/logs + rm -rf /tmp/gh-aw/sandbox/firewall/audit + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.55 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55 ghcr.io/github/gh-aw-firewall/squid:0.25.55 + - name: Check if detection needed + id: detection_guard + if: always() + env: + OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }} + HAS_PATCH: ${{ needs.agent.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 Config for detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + rm -f "${RUNNER_TEMP}/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 + for f in /tmp/gh-aw/aw-*.bundle; 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@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + WORKFLOW_NAME: "Night Owl" + WORKFLOW_DESCRIPTION: "No description provided" + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/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: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '24' + package-manager-cache: false + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.52 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.55 + - name: Execute GitHub Copilot CLI + if: always() && steps.detection_guard.outputs.run_detection == 'true' + continue-on-error: true + id: detection_agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" + (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.55/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000},"container":{"imageTag":"0.25.55"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + fi + # shellcheck disable=SC1003 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + env: + AWF_REFLECT_ENABLED: 1 + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_PHASE: detection + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_VERSION: v0.76.1 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows + 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 }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + XDG_CONFIG_HOME: /home/runner + - name: Upload threat detection log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: detection + path: /tmp/gh-aw/threat-detection/detection.log + if-no-files-found: ignore + - name: Parse and conclude threat detection + id: detection_conclusion + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }} + GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" + with: + script: | + try { + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + } catch (loadErr) { + const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; + const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure'; + const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); + core.error(msg); + core.setOutput('reason', 'parse_error'); + if (continueOnError && !detectionExecutionFailed) { + core.warning('\u26A0\uFE0F ' + msg); + core.setOutput('conclusion', 'warning'); + core.setOutput('success', 'false'); + } else { + core.setOutput('conclusion', 'failure'); + core.setOutput('success', 'false'); + core.setFailed(msg); + } + } + + safe_outputs: + needs: + - activation + - agent + - detection + if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' + runs-on: ubuntu-slim + permissions: + contents: write + issues: write + pull-requests: write + timeout-minutes: 15 + env: + GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/night-owl" + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} + GH_AW_ENGINE_ID: "copilot" + GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} + GH_AW_ENGINE_VERSION: "1.0.52" + GH_AW_WORKFLOW_ID: "night-owl" + GH_AW_WORKFLOW_NAME: "Night Owl" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/night-owl.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 + id: setup + uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Night Owl" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/night-owl.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.52" + GH_AW_INFO_AWF_VERSION: "v0.25.55" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Download patch artifact + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Extract base branch from agent output + id: extract-base-branch + if: steps.download-agent-output.outcome == 'success' + shell: bash + run: | + if [ -f "/tmp/gh-aw/agent_output.json" ]; then + GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) + BASE_BRANCH=$("$GH_AW_NODE" -e " + try { + const data = JSON.parse(require('fs').readFileSync('/tmp/gh-aw/agent_output.json', 'utf8')); + const item = (data.items || []).find(i => + (i.type === 'create_pull_request' || i.type === 'push_to_pull_request_branch') && + i.base_branch + ); + if (item) process.stdout.write(item.base_branch); + } catch(e) {} + " 2>/dev/null || true) + # Validate: only allow safe git branch name characters + if [[ "$BASE_BRANCH" =~ ^[a-zA-Z0-9/_.-]+$ ]] && [ ${#BASE_BRANCH} -le 255 ]; then + printf 'base-branch=%s\n' "$BASE_BRANCH" >> "$GITHUB_OUTPUT" + echo "Extracted base branch from safe output: $BASE_BRANCH" + fi + fi + - name: Checkout repository (trusted default branch for comment events) + if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') && (github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.event.repository.default_branch }} + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + persist-credentials: false + fetch-depth: 0 + - name: Checkout repository + if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') && github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ env.NIGHT_OWL_BASE_BRANCH }} + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + persist-credentials: false + fetch-depth: 0 + - 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: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Process Safe Outputs + id: process_safe_outputs + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + 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,mcp.atlassian.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,www.googleapis.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_AW_SAFE_OUTPUT_JOBS: "{\"slack_notify\":\"\"}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"base_branch\":\"${{ env.NIGHT_OWL_BASE_BRANCH }}\",\"draft\":true,\"labels\":[\"night-owl\",\"automated\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"allowed\",\"title_prefix\":\"[night-owl] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"report_incomplete\":{}}" + 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('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); + await main(); + - name: Upload Safe Outputs Items + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: safe-outputs-items + path: | + /tmp/gh-aw/safe-output-items.jsonl + /tmp/gh-aw/temporary-id-map.json + if-no-files-found: ignore + + slack_notify: + needs: + - agent + - detection + - safe_outputs + if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'slack_notify') + runs-on: ubuntu-latest + permissions: + id-token: write + steps: + - name: Download agent output artifact + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: ${{ runner.temp }}/gh-aw/safe-jobs/ + - name: Extract message from agent output + id: extract + run: | + if [ -f "$GH_AW_AGENT_OUTPUT" ]; then + MESSAGE=$(cat "$GH_AW_AGENT_OUTPUT" | jq -r '.items[] | select(.type == "slack_notify") | .message') + DELIMITER=$(openssl rand -hex 16) + echo "message<<$DELIMITER" >> "$GITHUB_OUTPUT" + echo "$MESSAGE" >> "$GITHUB_OUTPUT" + echo "$DELIMITER" >> "$GITHUB_OUTPUT" + else + echo "::error::No agent output found at $GH_AW_AGENT_OUTPUT" + exit 1 + fi + env: + GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json + - name: Get Slack token from Vault + id: secrets + uses: SonarSource/vault-action-wrapper@c154b4a417b51cb98dd71137f49bf20e77c56820 # c154b4a417b51cb98dd71137f49bf20e77c56820 + env: + GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json + with: + secrets: | + development/kv/data/slack token | SLACK_BOT_TOKEN; + - name: Post to Slack + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # 70cd7be8e40a46e8b0eced40b0de447bdb42f68e + env: + GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json + SLACK_BOT_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).SLACK_BOT_TOKEN }} + with: + channel-id: ${{ env.NIGHT_OWL_SLACK_CHANNEL_ID }} + slack-message: ${{ steps.extract.outputs.message }} + diff --git a/.github/workflows/night-owl.md b/.github/workflows/night-owl.md new file mode 100644 index 000000000..a4cc173d9 --- /dev/null +++ b/.github/workflows/night-owl.md @@ -0,0 +1,213 @@ +--- +on: + schedule: + - cron: '0 0 * * *' + workflow_dispatch: + +concurrency: night-owl + +permissions: + contents: read + issues: read + pull-requests: read + +checkout: + - fetch-depth: 0 + +network: + allowed: + - defaults + - mcp.atlassian.com + +tools: + github: + toolsets: [context, repos, pull_requests] + +mcp-servers: + atlassian: + type: http + url: https://mcp.atlassian.com/v1/mcp + headers: + Authorization: "${{ needs.atlassian_mcp_auth.outputs.authorization_header }}" + allowed: + - getAccessibleAtlassianResources + - searchJiraIssuesUsingJql + - getJiraIssue + - getJiraIssueRemoteIssueLinks + +env: + NIGHT_OWL_BASE_BRANCH: master + NIGHT_OWL_CANDIDATE_JQL: 'project = CLI AND labels = "for-agent" AND statusCategory = "To Do" ORDER BY created ASC' + NIGHT_OWL_JIRA_LABEL: for-agent + NIGHT_OWL_JIRA_PROJECT: CLI + NIGHT_OWL_SLACK_CHANNEL_ID: C0B7GN16473 + +jobs: + atlassian_mcp_auth: + runs-on: ubuntu-latest + permissions: + id-token: write + outputs: + authorization_header: ${{ steps.build_auth_header.outputs.authorization_header }} + steps: + - name: Get Jira credentials from Vault + id: secrets + uses: SonarSource/vault-action-wrapper@c154b4a417b51cb98dd71137f49bf20e77c56820 + with: + secrets: | + development/kv/data/jira user | JIRA_USER; + development/kv/data/jira token | JIRA_TOKEN; + - name: Build Atlassian MCP authorization header + id: build_auth_header + shell: bash + run: | + encoded=$(printf '%s' "${JIRA_USER}:${JIRA_TOKEN}" | base64 | tr -d '\n') + echo "::add-mask::${encoded}" + echo "authorization_header=Basic ${encoded}" >> "$GITHUB_OUTPUT" + env: + JIRA_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_TOKEN }} + JIRA_USER: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_USER }} + +safe-outputs: + noop: false + create-pull-request: + draft: true + title-prefix: "[night-owl] " + labels: [night-owl, automated] + protected-files: allowed + base-branch: ${{ env.NIGHT_OWL_BASE_BRANCH }} + jobs: + slack-notify: + needs: safe_outputs + description: "Send a night-owl message to Slack" + runs-on: ubuntu-latest + permissions: + id-token: write + inputs: + message: + description: "The night-owl message to send" + required: true + type: string + steps: + - name: Extract message from agent output + id: extract + run: | + if [ -f "$GH_AW_AGENT_OUTPUT" ]; then + MESSAGE=$(cat "$GH_AW_AGENT_OUTPUT" | jq -r '.items[] | select(.type == "slack_notify") | .message') + DELIMITER=$(openssl rand -hex 16) + echo "message<<$DELIMITER" >> "$GITHUB_OUTPUT" + echo "$MESSAGE" >> "$GITHUB_OUTPUT" + echo "$DELIMITER" >> "$GITHUB_OUTPUT" + else + echo "::error::No agent output found at $GH_AW_AGENT_OUTPUT" + exit 1 + fi + env: + GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json + - name: Get Slack token from Vault + id: secrets + uses: SonarSource/vault-action-wrapper@c154b4a417b51cb98dd71137f49bf20e77c56820 + with: + secrets: | + development/kv/data/slack token | SLACK_BOT_TOKEN; + - name: Post to Slack + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e + env: + SLACK_BOT_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).SLACK_BOT_TOKEN }} + with: + channel-id: ${{ env.NIGHT_OWL_SLACK_CHANNEL_ID }} + slack-message: ${{ steps.extract.outputs.message }} + +--- + +# Night Owl + +You are the nightly implementation agent for this repository. + +## Instructions + +1. Read `AGENTS.md` and `CLAUDE.md` before changing code so you follow the repository-specific rules. +2. Use Atlassian MCP for all Jira discovery and ticket reading. Do not rely on any separate hand-written brief. +3. Use GitHub MCP plus the local checkout for repository context, implementation work, and PR checks. +4. Emit exactly one `slack_notify` safe output for every terminal outcome: + - `starving`: no eligible Jira ticket remains after filtering and open-PR deduplication; + - `blocked`: the selected ticket or its parent/linked context is missing key product or implementation decisions; + - `draft-pr-opened`: the implementation is done and a draft PR was created. + +## 1. Discover candidate Jira tickets + +1. Use `getAccessibleAtlassianResources` to find the SonarSource Jira Cloud site. +2. Use `searchJiraIssuesUsingJql` with `$NIGHT_OWL_CANDIDATE_JQL`. +3. Process candidates in the order returned by Jira. Do not randomize. +4. Keep only issues whose current status name is exactly `Open`, `TODO`, or `To Do`. +5. Work only on issues in project `$NIGHT_OWL_JIRA_PROJECT` with label `$NIGHT_OWL_JIRA_LABEL`. + +## 2. Skip tickets already in flight + +For each candidate issue: + +1. Use GitHub MCP to inspect open pull requests in this repository. +2. If an open PR title, body, branch name, or obvious linked reference already targets the Jira key, skip that issue and continue with the next one. +3. If no eligible candidate remains after this deduplication, emit a `slack_notify` message that Night Owl is starving and stop successfully. + +## 3. Gather Jira context + +For the first eligible issue that is not already in flight: + +1. Read the Jira issue details, including summary, description, issue type, status, labels, comments, subtasks, and issue links. +2. If the issue has a parent or epic, read that issue too before making decisions. +3. Read additional linked Jira tickets only when they are directly relevant to implementation or acceptance criteria. +4. Treat Jira as the source of truth for the ticket context. + +## 4. Decide whether the ticket is actionable + +Stop and emit a `slack_notify` message without creating a PR if any required behavior is missing or ambiguous, including: + +- acceptance criteria, +- scope boundaries, +- rollout expectations, +- API or CLI contract decisions, +- conflicting instructions between the ticket and its parent context. + +The blocked Slack message must include: + +- the Jira key and summary, +- the parent issue key and summary when you used one, +- a short explanation of why you stopped, +- the concrete missing decisions or questions that a human needs to answer. + +## 5. Implement the ticket + +If the issue is actionable: + +1. Read the relevant repository files and follow `AGENTS.md` / `CLAUDE.md`. +2. Create a branch before committing, using the Jira key in the branch name, for example `night-owl/CLI-123-short-slug`. +3. Implement the change. +4. Run the smallest set of meaningful checks that match the files you changed. If you edit TypeScript, follow the repository formatting requirements before you finish. +5. If you cannot finish a coherent implementation or cannot validate it well enough to justify a draft PR, stop and emit a blocked Slack message explaining what remains. + +## 6. Open a draft PR + +When the implementation is complete: + +1. Use the `create-pull-request` safe output. The PR must remain a draft. +2. Use a PR title that starts with the Jira key, for example `CLI-123 Implement ...`. +3. In the PR description include: + - the Jira ticket link and summary, + - the parent ticket link if you used parent context, + - a short implementation summary, + - the checks you ran, + - any open questions or follow-ups that remain. + +## 7. Notify Slack + +Emit a `slack_notify` message for every terminal outcome. + +The message should be structured and concise: + +- `Night Owl`: starving, blocked, or draft PR opened +- `Ticket`: Jira key, link, and summary when applicable +- `Parent`: parent Jira key and summary when applicable +- `Outcome`: one or two sentences +- `PR`: include the PR URL when one was created +- `Checks`: include the checks you ran when a PR was created diff --git a/CLAUDE.md b/CLAUDE.md index c74eb2245..0dccecca4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -108,6 +108,12 @@ Do **not** use `bun test --coverage` directly — Bun's native LCOV reporter emi When adding, removing, or changing commands, scripts, or project structure, update `CLAUDE.md`, and `AGENTS.md` to reflect the change before finishing. +## GitHub Agentic Workflows + +- Agentic workflow source files live in `.github/workflows/*.md`. Generated `.lock.yml` files are compiler output; update them with `gh aw compile ` instead of editing them directly. +- `ci-failure-triage-agent.md` watches failed workflow runs and can open draft remediation PRs. +- `night-owl.md` runs nightly at `00:00 UTC`, uses GitHub MCP plus Atlassian MCP to select one `CLI` Jira ticket labeled `for-agent` in a to-do status, reads the ticket and parent context from Jira, opens draft PRs when the work is implementable, and posts Slack updates for starvation, blocked tickets, and draft PR creation. + ## Docs site (`docs/`) The docs site is generated from the CLI source — do not edit `commands.json`, `llms.txt`, or `sitemap.xml` by hand. This is done by automation post-release. From 2fea86c5bf95b6c76b4bb9d828687d261a7a4016 Mon Sep 17 00:00:00 2001 From: Damien Urruty Date: Wed, 3 Jun 2026 19:01:47 +0200 Subject: [PATCH 2/3] Try different API --- .../ci-failure-triage-agent.lock.yml | 32 +++++++++---------- .github/workflows/ci-failure-triage-agent.md | 2 +- .github/workflows/night-owl.lock.yml | 32 +++++++++---------- .github/workflows/night-owl.md | 2 +- 4 files changed, 34 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci-failure-triage-agent.lock.yml b/.github/workflows/ci-failure-triage-agent.lock.yml index 64a57282b..155a24b19 100644 --- a/.github/workflows/ci-failure-triage-agent.lock.yml +++ b/.github/workflows/ci-failure-triage-agent.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"fccf4857a7b6fcff5ad5f7d961efe7ebee0edfa829ee241182736c9f259d1253","compiler_version":"v0.76.1","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"b3ed5a23b246495a02613fe57f0e27e96cada4897433840fdaa0d10d8e409fcc","compiler_version":"v0.76.1","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"SonarSource/vault-action-wrapper","sha":"c154b4a417b51cb98dd71137f49bf20e77c56820","version":"c154b4a417b51cb98dd71137f49bf20e77c56820"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"46d564922b082d0db93244972e8005ea6904ee5f","version":"v0.76.1"},{"repo":"slackapi/slack-github-action","sha":"70cd7be8e40a46e8b0eced40b0de447bdb42f68e","version":"70cd7be8e40a46e8b0eced40b0de447bdb42f68e"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.55"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.55"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.19"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4","digest":"sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.4@sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} # ___ _ _ # / _ \ | | (_) @@ -202,23 +202,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_8b50d7b8fbf6689a_EOF' + cat << 'GH_AW_PROMPT_7c74984cf505da3e_EOF' - GH_AW_PROMPT_8b50d7b8fbf6689a_EOF + GH_AW_PROMPT_7c74984cf505da3e_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_8b50d7b8fbf6689a_EOF' + cat << 'GH_AW_PROMPT_7c74984cf505da3e_EOF' Tools: create_pull_request, missing_tool, missing_data, slack_notify - GH_AW_PROMPT_8b50d7b8fbf6689a_EOF + GH_AW_PROMPT_7c74984cf505da3e_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_8b50d7b8fbf6689a_EOF' + cat << 'GH_AW_PROMPT_7c74984cf505da3e_EOF' - GH_AW_PROMPT_8b50d7b8fbf6689a_EOF + GH_AW_PROMPT_7c74984cf505da3e_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_8b50d7b8fbf6689a_EOF' + cat << 'GH_AW_PROMPT_7c74984cf505da3e_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -250,12 +250,12 @@ jobs: - **Note**: If a branch you need is not in the list above and is not listed as an additional fetched ref, it has NOT been checked out. For private repositories you cannot fetch it without proper authentication. If the branch is required and not available, exit with an error and ask the user to add it to the `fetch:` option of the `checkout:` configuration (e.g., `fetch: ["refs/pulls/open/*"]` for all open PR refs, or `fetch: ["main", "feature/my-branch"]` for specific branches). - GH_AW_PROMPT_8b50d7b8fbf6689a_EOF + GH_AW_PROMPT_7c74984cf505da3e_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_8b50d7b8fbf6689a_EOF' + cat << 'GH_AW_PROMPT_7c74984cf505da3e_EOF' {{#runtime-import .github/workflows/ci-failure-triage-agent.md}} - GH_AW_PROMPT_8b50d7b8fbf6689a_EOF + GH_AW_PROMPT_7c74984cf505da3e_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -468,9 +468,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_3d3eb64fbd6cbdfd_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_bec8dc372f5517fb_EOF' {"create_pull_request":{"base_branch":"${{ env.NIGHT_OWL_BASE_BRANCH }}","draft":true,"labels":["night-owl","automated"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"allowed","title_prefix":"[night-owl] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"report_incomplete":{},"slack-notify":{"description":"Send a night-owl message to Slack","inputs":{"message":{"default":null,"description":"The night-owl message to send","required":true,"type":"string"}}}} - GH_AW_SAFE_OUTPUTS_CONFIG_3d3eb64fbd6cbdfd_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_bec8dc372f5517fb_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -690,12 +690,12 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_628f86365c716de9_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_23b0a2526e25efe8_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "atlassian": { "type": "http", - "url": "https://mcp.atlassian.com/v1/mcp", + "url": "https://mcp.atlassian.com/v1/mcp/authv2", "headers": { "Authorization": "${{ needs.atlassian_mcp_auth.outputs.authorization_header }}" }, @@ -751,7 +751,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_628f86365c716de9_EOF + GH_AW_MCP_CONFIG_23b0a2526e25efe8_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true diff --git a/.github/workflows/ci-failure-triage-agent.md b/.github/workflows/ci-failure-triage-agent.md index 4bd60704f..b1695f462 100644 --- a/.github/workflows/ci-failure-triage-agent.md +++ b/.github/workflows/ci-failure-triage-agent.md @@ -26,7 +26,7 @@ tools: mcp-servers: atlassian: type: http - url: https://mcp.atlassian.com/v1/mcp + url: https://mcp.atlassian.com/v1/mcp/authv2 headers: Authorization: "${{ needs.atlassian_mcp_auth.outputs.authorization_header }}" allowed: diff --git a/.github/workflows/night-owl.lock.yml b/.github/workflows/night-owl.lock.yml index f70f17ef4..9af837969 100644 --- a/.github/workflows/night-owl.lock.yml +++ b/.github/workflows/night-owl.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"fccf4857a7b6fcff5ad5f7d961efe7ebee0edfa829ee241182736c9f259d1253","compiler_version":"v0.76.1","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"b3ed5a23b246495a02613fe57f0e27e96cada4897433840fdaa0d10d8e409fcc","compiler_version":"v0.76.1","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"SonarSource/vault-action-wrapper","sha":"c154b4a417b51cb98dd71137f49bf20e77c56820","version":"c154b4a417b51cb98dd71137f49bf20e77c56820"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"46d564922b082d0db93244972e8005ea6904ee5f","version":"v0.76.1"},{"repo":"slackapi/slack-github-action","sha":"70cd7be8e40a46e8b0eced40b0de447bdb42f68e","version":"70cd7be8e40a46e8b0eced40b0de447bdb42f68e"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.55"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.55"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.19"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4","digest":"sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.4@sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} # ___ _ _ # / _ \ | | (_) @@ -202,23 +202,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_8b50d7b8fbf6689a_EOF' + cat << 'GH_AW_PROMPT_7c74984cf505da3e_EOF' - GH_AW_PROMPT_8b50d7b8fbf6689a_EOF + GH_AW_PROMPT_7c74984cf505da3e_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_8b50d7b8fbf6689a_EOF' + cat << 'GH_AW_PROMPT_7c74984cf505da3e_EOF' Tools: create_pull_request, missing_tool, missing_data, slack_notify - GH_AW_PROMPT_8b50d7b8fbf6689a_EOF + GH_AW_PROMPT_7c74984cf505da3e_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_8b50d7b8fbf6689a_EOF' + cat << 'GH_AW_PROMPT_7c74984cf505da3e_EOF' - GH_AW_PROMPT_8b50d7b8fbf6689a_EOF + GH_AW_PROMPT_7c74984cf505da3e_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_8b50d7b8fbf6689a_EOF' + cat << 'GH_AW_PROMPT_7c74984cf505da3e_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -250,12 +250,12 @@ jobs: - **Note**: If a branch you need is not in the list above and is not listed as an additional fetched ref, it has NOT been checked out. For private repositories you cannot fetch it without proper authentication. If the branch is required and not available, exit with an error and ask the user to add it to the `fetch:` option of the `checkout:` configuration (e.g., `fetch: ["refs/pulls/open/*"]` for all open PR refs, or `fetch: ["main", "feature/my-branch"]` for specific branches). - GH_AW_PROMPT_8b50d7b8fbf6689a_EOF + GH_AW_PROMPT_7c74984cf505da3e_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_8b50d7b8fbf6689a_EOF' + cat << 'GH_AW_PROMPT_7c74984cf505da3e_EOF' {{#runtime-import .github/workflows/night-owl.md}} - GH_AW_PROMPT_8b50d7b8fbf6689a_EOF + GH_AW_PROMPT_7c74984cf505da3e_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -468,9 +468,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_3d3eb64fbd6cbdfd_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_bec8dc372f5517fb_EOF' {"create_pull_request":{"base_branch":"${{ env.NIGHT_OWL_BASE_BRANCH }}","draft":true,"labels":["night-owl","automated"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"allowed","title_prefix":"[night-owl] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"report_incomplete":{},"slack-notify":{"description":"Send a night-owl message to Slack","inputs":{"message":{"default":null,"description":"The night-owl message to send","required":true,"type":"string"}}}} - GH_AW_SAFE_OUTPUTS_CONFIG_3d3eb64fbd6cbdfd_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_bec8dc372f5517fb_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -690,12 +690,12 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_628f86365c716de9_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_23b0a2526e25efe8_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "atlassian": { "type": "http", - "url": "https://mcp.atlassian.com/v1/mcp", + "url": "https://mcp.atlassian.com/v1/mcp/authv2", "headers": { "Authorization": "${{ needs.atlassian_mcp_auth.outputs.authorization_header }}" }, @@ -751,7 +751,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_628f86365c716de9_EOF + GH_AW_MCP_CONFIG_23b0a2526e25efe8_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true diff --git a/.github/workflows/night-owl.md b/.github/workflows/night-owl.md index a4cc173d9..a3ebf2a7f 100644 --- a/.github/workflows/night-owl.md +++ b/.github/workflows/night-owl.md @@ -26,7 +26,7 @@ tools: mcp-servers: atlassian: type: http - url: https://mcp.atlassian.com/v1/mcp + url: https://mcp.atlassian.com/v1/mcp/authv2 headers: Authorization: "${{ needs.atlassian_mcp_auth.outputs.authorization_header }}" allowed: From 9ca58e9f904977ab17f38b0871eb01b1cfe738ba Mon Sep 17 00:00:00 2001 From: Damien Urruty Date: Wed, 3 Jun 2026 19:40:56 +0200 Subject: [PATCH 3/3] Try CLI --- .../scripts/night-owl/prepare-jira-context.sh | 456 ++++++++++++++++++ .../ci-failure-triage-agent.lock.yml | 235 +++++---- .github/workflows/ci-failure-triage-agent.md | 145 ++++-- .github/workflows/night-owl.lock.yml | 235 +++++---- .github/workflows/night-owl.md | 145 ++++-- CLAUDE.md | 4 +- 6 files changed, 950 insertions(+), 270 deletions(-) create mode 100644 .github/scripts/night-owl/prepare-jira-context.sh diff --git a/.github/scripts/night-owl/prepare-jira-context.sh b/.github/scripts/night-owl/prepare-jira-context.sh new file mode 100644 index 000000000..f20e65af2 --- /dev/null +++ b/.github/scripts/night-owl/prepare-jira-context.sh @@ -0,0 +1,456 @@ +#!/usr/bin/env bash + +set -euo pipefail + +: "${GH_TOKEN:?GH_TOKEN is required}" +: "${GITHUB_OUTPUT:?GITHUB_OUTPUT is required}" +: "${GITHUB_REPOSITORY:?GITHUB_REPOSITORY is required}" +: "${NIGHT_OWL_CANDIDATE_JQL:?NIGHT_OWL_CANDIDATE_JQL is required}" +: "${NIGHT_OWL_JIRA_LABEL:?NIGHT_OWL_JIRA_LABEL is required}" +: "${NIGHT_OWL_JIRA_PROJECT:?NIGHT_OWL_JIRA_PROJECT is required}" +: "${NIGHT_OWL_JIRA_SITE:?NIGHT_OWL_JIRA_SITE is required}" + +emit_output() { + printf '%s=%s\n' "$1" "$2" >>"$GITHUB_OUTPUT" +} + +emit_multiline_output() { + local name="$1" + local value="$2" + local delimiter="EOF_$(openssl rand -hex 12)" + + { + printf '%s<<%s\n' "$name" "$delimiter" + printf '%s\n' "$value" + printf '%s\n' "$delimiter" + } >>"$GITHUB_OUTPUT" +} + +normalize_collection() { + jq -c ' + if type == "array" then . + elif .issues? then .issues + elif .values? then .values + elif .results? then .results + elif .items? then .items + elif .comments? then .comments + else [] + end + ' +} + +normalize_single() { + jq -c ' + if type == "array" then .[0] + elif .issues? then .issues[0] + elif .values? then .values[0] + elif .results? then .results[0] + elif .items? then .items[0] + else . + end + ' +} + +adf_to_text() { + jq -r ' + def textify: + if . == null then "" + elif type == "string" then . + elif type == "array" then + map(textify) + | map(select(length > 0)) + | join("\n") + elif type == "object" then + ([.text? // empty, (.content? | textify)] | map(select(length > 0)) | join("\n")) + else + tostring + end; + textify | gsub("\r"; "") + ' +} + +compact_text() { + printf '%s' "$1" | tr '\n' ' ' | sed -E 's/[[:space:]]+/ /g; s/^ //; s/ $//' +} + +truncate_text() { + local text + local limit + + text="$(compact_text "$1")" + limit="${2:-400}" + + if [ "${#text}" -le "$limit" ]; then + printf '%s' "$text" + return + fi + + printf '%s…' "${text:0:$((limit - 1))}" +} + +issue_key() { + jq -r '.key // .issueKey // empty' +} + +issue_summary() { + jq -r '.fields.summary // .summary // empty' +} + +issue_status() { + jq -r '.fields.status.name // .status.name // .status // empty' +} + +issue_type() { + jq -r '.fields.issuetype.name // .issuetype.name // .issueType.name // empty' +} + +issue_project() { + jq -r '.fields.project.key // .project.key // empty' +} + +issue_parent_key() { + jq -r '.fields.parent.key // .parent.key // empty' +} + +issue_labels_csv() { + jq -r '[.fields.labels[]?, .labels[]?] | unique | join(", ")' +} + +issue_has_label() { + local label="$1" + jq -e --arg label "$label" '[.fields.labels[]?, .labels[]?] | index($label) != null' >/dev/null +} + +issue_description_text() { + local issue_json="$1" + local description_text + + description_text="$( + jq '.fields.description // .description // null' <<<"$issue_json" | adf_to_text + )" + + if [ -n "$(compact_text "$description_text")" ]; then + printf '%s\n' "$description_text" + else + printf '_None provided._\n' + fi +} + +has_open_pr_for_key() { + local key="$1" + local pattern="(^|[^A-Z0-9])${key}([^A-Z0-9]|$)" + + jq -e --arg pattern "$pattern" ' + any( + .[]?; + ([.title // "", .body // "", .headRefName // ""] | join("\n")) | test($pattern; "i") + ) + ' <<<"$OPEN_PULL_REQUESTS_JSON" >/dev/null +} + +format_comments_markdown() { + local comments_json="$1" + local result='' + local comment='' + + while IFS= read -r comment; do + local author="" + local created="" + local body="" + local body_text="" + local excerpt="" + + author="$(jq -r '.author.displayName // .author.name // "unknown author"' <<<"$comment")" + created="$(jq -r '.updated // .created // "unknown date"' <<<"$comment")" + body="$(jq '.body // .comment // .text // null' <<<"$comment")" + body_text="$(adf_to_text <<<"$body")" + excerpt="$(truncate_text "$body_text" 400)" + + if [ -z "$excerpt" ]; then + excerpt='(no comment body)' + fi + + result+="- ${author} (${created}): ${excerpt}"$'\n' + done < <(normalize_collection <<<"$comments_json" | jq -c '.[]?') + + if [ -z "$result" ]; then + printf '_No comments loaded._\n' + else + printf '%s' "$result" + fi +} + +format_links_markdown() { + local links_json="$1" + local result='' + local link='' + + while IFS= read -r link; do + local relationship="" + local linked_key="" + local linked_status="" + local linked_summary="" + local line="" + + relationship="$(jq -r '.type.name // .linkType.name // .name // "linked"' <<<"$link")" + linked_key="$(jq -r '.outwardIssue.key // .inwardIssue.key // .workItem.key // .linkedWorkItem.key // .issue.key // .key // empty' <<<"$link")" + linked_status="$(jq -r '.outwardIssue.fields.status.name // .outwardIssue.status.name // .outwardIssue.status // .inwardIssue.fields.status.name // .inwardIssue.status.name // .inwardIssue.status // .workItem.fields.status.name // .workItem.status.name // .workItem.status // empty' <<<"$link")" + linked_summary="$(jq -r '.outwardIssue.fields.summary // .outwardIssue.summary // .inwardIssue.fields.summary // .inwardIssue.summary // .workItem.fields.summary // .workItem.summary // .linkedWorkItem.fields.summary // .linkedWorkItem.summary // .issue.fields.summary // .issue.summary // empty' <<<"$link")" + + line="- ${relationship}" + if [ -n "$linked_key" ]; then + line+=" ${linked_key}" + fi + if [ -n "$linked_status" ]; then + line+=" (${linked_status})" + fi + if [ -n "$linked_summary" ]; then + line+=": ${linked_summary}" + fi + + result+="${line}"$'\n' + done < <(normalize_collection <<<"$links_json" | jq -c '.[]?') + + if [ -z "$result" ]; then + printf '_No direct issue links loaded._\n' + else + printf '%s' "$result" + fi +} + +set_starving_outputs() { + local outcome="$1" + + emit_output prep_status starving + emit_output issue_key "" + emit_output issue_url "" + emit_output issue_summary "" + emit_output parent_key "" + emit_output parent_url "" + emit_output parent_summary "" + emit_multiline_output context_markdown "" + emit_multiline_output slack_message "$outcome" +} + +search_workitems() { + if acli jira workitem search --jql "$NIGHT_OWL_CANDIDATE_JQL" --paginate --json 2>/dev/null; then + return + fi + + acli jira workitem search --jql "$NIGHT_OWL_CANDIDATE_JQL" --limit 50 --json +} + +view_workitem() { + local key="$1" + local fields="${2:-*all}" + + if acli jira workitem view "$key" --fields "$fields" --json 2>/dev/null; then + return + fi + + acli jira workitem view --key "$key" --fields "$fields" --json +} + +list_comments() { + local key="$1" + + if acli jira workitem comment list --key "$key" --order -updated --limit 10 --json 2>/dev/null; then + return + fi + + acli jira workitem comment list "$key" --order -updated --limit 10 --json +} + +list_links() { + local key="$1" + + if acli jira workitem link list --key "$key" --json 2>/dev/null; then + return + fi + + acli jira workitem link list "$key" --json +} + +OPEN_PULL_REQUESTS_JSON="$( + gh pr list \ + --repo "$GITHUB_REPOSITORY" \ + --state open \ + --limit 100 \ + --json number,title,body,headRefName,url +)" + +SEARCH_RESULTS_JSON="$(search_workitems)" + +selected_issue_json='' +selected_issue_key='' +skipped_in_flight=0 +candidate_count=0 + +while IFS= read -r candidate; do + current_key="$(issue_key <<<"$candidate")" + current_status="$(issue_status <<<"$candidate")" + + if [ -z "$current_key" ]; then + continue + fi + + candidate_count=$((candidate_count + 1)) + + case "$current_status" in + Open|TODO|"To Do") ;; + *) + continue + ;; + esac + + if has_open_pr_for_key "$current_key"; then + skipped_in_flight=$((skipped_in_flight + 1)) + continue + fi + + full_issue_json_raw="$(view_workitem "$current_key" "*all")" + full_issue_json="$(normalize_single <<<"$full_issue_json_raw")" + + if [ "$(issue_project <<<"$full_issue_json")" != "$NIGHT_OWL_JIRA_PROJECT" ]; then + continue + fi + + if ! issue_has_label "$NIGHT_OWL_JIRA_LABEL" <<<"$full_issue_json"; then + continue + fi + + case "$(issue_status <<<"$full_issue_json")" in + Open|TODO|"To Do") ;; + *) + continue + ;; + esac + + selected_issue_key="$current_key" + selected_issue_json="$full_issue_json" + break +done < <(normalize_collection <<<"$SEARCH_RESULTS_JSON" | jq -c '.[]?') + +if [ -z "$selected_issue_key" ]; then + if [ "$candidate_count" -eq 0 ]; then + set_starving_outputs "Night Owl: :sleeping: starving +Outcome: No Jira ticket matched the Night Owl query for project ${NIGHT_OWL_JIRA_PROJECT}, label ${NIGHT_OWL_JIRA_LABEL}, and statuses Open/TODO/To Do. +Action needed: Add a qualifying Jira ticket for the agent." + else + set_starving_outputs "Night Owl: :sleeping: starving +Outcome: No Jira ticket remained after skipping tickets already covered by an open pull request and revalidating the Night Owl filters. +Context: ${skipped_in_flight} matching ticket(s) were already in flight. +Action needed: Add another qualifying Jira ticket or finish the open PRs already covering the current queue." + fi + exit 0 +fi + +selected_issue_summary="$(issue_summary <<<"$selected_issue_json")" +selected_issue_status="$(issue_status <<<"$selected_issue_json")" +selected_issue_type="$(issue_type <<<"$selected_issue_json")" +selected_issue_labels="$(issue_labels_csv <<<"$selected_issue_json")" +selected_issue_url="https://${NIGHT_OWL_JIRA_SITE}/browse/${selected_issue_key}" + +parent_key="$(issue_parent_key <<<"$selected_issue_json")" +parent_json='' +parent_summary='' +parent_url='' + +if [ -n "$parent_key" ]; then + parent_json_raw="$(view_workitem "$parent_key" "*all")" + parent_json="$(normalize_single <<<"$parent_json_raw")" + parent_summary="$(issue_summary <<<"$parent_json")" + parent_url="https://${NIGHT_OWL_JIRA_SITE}/browse/${parent_key}" +fi + +comments_json='[]' +comments_note='' +if ! comments_json="$(list_comments "$selected_issue_key")"; then + comments_json='[]' + comments_note='_Comments could not be loaded by the prep job._' +fi + +links_json='[]' +links_note='' +if ! links_json="$(list_links "$selected_issue_key")"; then + links_json='[]' + links_note='_Direct issue links could not be loaded by the prep job._' +fi + +selected_issue_description="$(issue_description_text "$selected_issue_json")" +parent_description='_No parent ticket._' +if [ -n "$parent_json" ]; then + parent_description="$(issue_description_text "$parent_json")" +fi + +comments_section="$(format_comments_markdown "$comments_json")" +links_section="$(format_links_markdown "$links_json")" + +context_markdown="$( + cat < - GH_AW_PROMPT_7c74984cf505da3e_EOF + GH_AW_PROMPT_d3dcfaa93bd10850_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_7c74984cf505da3e_EOF' + cat << 'GH_AW_PROMPT_d3dcfaa93bd10850_EOF' Tools: create_pull_request, missing_tool, missing_data, slack_notify - GH_AW_PROMPT_7c74984cf505da3e_EOF + GH_AW_PROMPT_d3dcfaa93bd10850_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_7c74984cf505da3e_EOF' + cat << 'GH_AW_PROMPT_d3dcfaa93bd10850_EOF' - GH_AW_PROMPT_7c74984cf505da3e_EOF + GH_AW_PROMPT_d3dcfaa93bd10850_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_7c74984cf505da3e_EOF' + cat << 'GH_AW_PROMPT_d3dcfaa93bd10850_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -250,18 +261,26 @@ jobs: - **Note**: If a branch you need is not in the list above and is not listed as an additional fetched ref, it has NOT been checked out. For private repositories you cannot fetch it without proper authentication. If the branch is required and not available, exit with an error and ask the user to add it to the `fetch:` option of the `checkout:` configuration (e.g., `fetch: ["refs/pulls/open/*"]` for all open PR refs, or `fetch: ["main", "feature/my-branch"]` for specific branches). - GH_AW_PROMPT_7c74984cf505da3e_EOF + GH_AW_PROMPT_d3dcfaa93bd10850_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_7c74984cf505da3e_EOF' + cat << 'GH_AW_PROMPT_d3dcfaa93bd10850_EOF' {{#runtime-import .github/workflows/ci-failure-triage-agent.md}} - GH_AW_PROMPT_7c74984cf505da3e_EOF + GH_AW_PROMPT_d3dcfaa93bd10850_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_ENGINE_ID: "copilot" + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_CONTEXT_MARKDOWN: ${{ needs.night_owl_prepare.outputs.context_markdown }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_KEY: ${{ needs.night_owl_prepare.outputs.issue_key }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_SUMMARY: ${{ needs.night_owl_prepare.outputs.issue_summary }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_URL: ${{ needs.night_owl_prepare.outputs.issue_url }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_KEY: ${{ needs.night_owl_prepare.outputs.parent_key }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_SUMMARY: ${{ needs.night_owl_prepare.outputs.parent_summary }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_URL: ${{ needs.night_owl_prepare.outputs.parent_url }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PREP_STATUS: ${{ needs.night_owl_prepare.outputs.prep_status }} with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -281,6 +300,14 @@ jobs: GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_CONTEXT_MARKDOWN: ${{ needs.night_owl_prepare.outputs.context_markdown }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_KEY: ${{ needs.night_owl_prepare.outputs.issue_key }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_SUMMARY: ${{ needs.night_owl_prepare.outputs.issue_summary }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_URL: ${{ needs.night_owl_prepare.outputs.issue_url }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_KEY: ${{ needs.night_owl_prepare.outputs.parent_key }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_SUMMARY: ${{ needs.night_owl_prepare.outputs.parent_summary }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_URL: ${{ needs.night_owl_prepare.outputs.parent_url }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PREP_STATUS: ${{ needs.night_owl_prepare.outputs.prep_status }} with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -300,7 +327,15 @@ jobs: 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_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_CONTEXT_MARKDOWN: process.env.GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_CONTEXT_MARKDOWN, + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_KEY: process.env.GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_KEY, + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_SUMMARY: process.env.GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_SUMMARY, + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_URL: process.env.GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_URL, + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_KEY: process.env.GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_KEY, + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_SUMMARY: process.env.GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_SUMMARY, + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_URL: process.env.GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_URL, + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PREP_STATUS: process.env.GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PREP_STATUS } }); - name: Validate prompt placeholders @@ -334,7 +369,7 @@ jobs: agent: needs: - activation - - atlassian_mcp_auth + - night_owl_prepare runs-on: ubuntu-latest permissions: contents: read @@ -468,9 +503,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_bec8dc372f5517fb_EOF' - {"create_pull_request":{"base_branch":"${{ env.NIGHT_OWL_BASE_BRANCH }}","draft":true,"labels":["night-owl","automated"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"allowed","title_prefix":"[night-owl] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"report_incomplete":{},"slack-notify":{"description":"Send a night-owl message to Slack","inputs":{"message":{"default":null,"description":"The night-owl message to send","required":true,"type":"string"}}}} - GH_AW_SAFE_OUTPUTS_CONFIG_bec8dc372f5517fb_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_45b7b35a3323608a_EOF' + {"create_pull_request":{"base_branch":"${{ env.NIGHT_OWL_BASE_BRANCH }}","draft":true,"labels":["night-owl","automated"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"fallback-to-issue","title_prefix":"[night-owl] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"report_incomplete":{},"slack-notify":{"description":"Send a night-owl message to Slack","inputs":{"message":{"default":null,"description":"The night-owl message to send","required":true,"type":"string"}}}} + GH_AW_SAFE_OUTPUTS_CONFIG_45b7b35a3323608a_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -690,29 +725,9 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_23b0a2526e25efe8_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_72f50d2a7606cca8_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { - "atlassian": { - "type": "http", - "url": "https://mcp.atlassian.com/v1/mcp/authv2", - "headers": { - "Authorization": "${{ needs.atlassian_mcp_auth.outputs.authorization_header }}" - }, - "tools": [ - "getAccessibleAtlassianResources", - "searchJiraIssuesUsingJql", - "getJiraIssue", - "getJiraIssueRemoteIssueLinks" - ], - "guard-policies": { - "write-sink": { - "accept": [ - "*" - ] - } - } - }, "github": { "type": "stdio", "container": "ghcr.io/github/github-mcp-server:v1.0.4", @@ -751,7 +766,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_23b0a2526e25efe8_EOF + GH_AW_MCP_CONFIG_72f50d2a7606cca8_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -785,7 +800,7 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.55/awf-config.schema.json","network":{"allowDomains":["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","mcp.atlassian.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","www.googleapis.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5.3","gemini-pro","any"],"antigravity":["copilot/antigravity*","google/antigravity*","gemini/antigravity*"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"claude":["agent"],"codex":["agent"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"computer-use":["copilot/*computer-use*","google/*computer-use*","gemini/*computer-use*","openai/*computer-use*"],"copilot":["agent"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent"],"gemini-3-flash":["copilot/gemini-3*flash*","google/gemini-3*flash*","gemini/gemini-3*flash*"],"gemini-3-pro":["copilot/gemini-3*pro*","google/gemini-3*pro*","gemini/gemini-3*pro*"],"gemini-3.1-flash":["copilot/gemini-3.1*flash*","google/gemini-3.1*flash*","gemini/gemini-3.1*flash*"],"gemini-3.1-pro":["copilot/gemini-3.1*pro*","google/gemini-3.1*pro*","gemini/gemini-3.1*pro*"],"gemini-3.5-flash":["copilot/gemini-3.5*flash*","google/gemini-3.5*flash*","gemini/gemini-3.5*flash*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"gpt-5.2":["copilot/gpt-5.2*","openai/gpt-5.2*"],"gpt-5.3":["copilot/gpt-5.3*","openai/gpt-5.3*"],"gpt-5.4":["copilot/gpt-5.4*","openai/gpt-5.4*"],"gpt-5.5":["copilot/gpt-5.5*","openai/gpt-5.5*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"opus":["copilot/*opus*","anthropic/*opus*"],"opusplan":["opus?effort=high"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"robotics":["copilot/*robotics*","google/*robotics*","gemini/*robotics*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4-5-*","anthropic/*sonnet-4-5-*","copilot/*sonnet-4-6*","anthropic/*sonnet-4-6*"],"summarization":["haiku","gpt-5-mini","gemini-flash-lite","mini"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.25.55"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.55/awf-config.schema.json","network":{"allowDomains":["acli.atlassian.com","api.atlassian.com","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","sonarsource.atlassian.net","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5.3","gemini-pro","any"],"antigravity":["copilot/antigravity*","google/antigravity*","gemini/antigravity*"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"claude":["agent"],"codex":["agent"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"computer-use":["copilot/*computer-use*","google/*computer-use*","gemini/*computer-use*","openai/*computer-use*"],"copilot":["agent"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent"],"gemini-3-flash":["copilot/gemini-3*flash*","google/gemini-3*flash*","gemini/gemini-3*flash*"],"gemini-3-pro":["copilot/gemini-3*pro*","google/gemini-3*pro*","gemini/gemini-3*pro*"],"gemini-3.1-flash":["copilot/gemini-3.1*flash*","google/gemini-3.1*flash*","gemini/gemini-3.1*flash*"],"gemini-3.1-pro":["copilot/gemini-3.1*pro*","google/gemini-3.1*pro*","gemini/gemini-3.1*pro*"],"gemini-3.5-flash":["copilot/gemini-3.5*flash*","google/gemini-3.5*flash*","gemini/gemini-3.5*flash*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"gpt-5.2":["copilot/gpt-5.2*","openai/gpt-5.2*"],"gpt-5.3":["copilot/gpt-5.3*","openai/gpt-5.3*"],"gpt-5.4":["copilot/gpt-5.4*","openai/gpt-5.4*"],"gpt-5.5":["copilot/gpt-5.5*","openai/gpt-5.5*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"opus":["copilot/*opus*","anthropic/*opus*"],"opusplan":["opus?effort=high"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"robotics":["copilot/*robotics*","google/*robotics*","gemini/*robotics*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4-5-*","anthropic/*sonnet-4-5-*","copilot/*sonnet-4-6*","anthropic/*sonnet-4-6*"],"summarization":["haiku","gpt-5-mini","gemini-flash-lite","mini"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.25.55"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then @@ -881,7 +896,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.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,mcp.atlassian.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,www.googleapis.com" + GH_AW_ALLOWED_DOMAINS: "acli.atlassian.com,api.atlassian.com,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,sonarsource.atlassian.net,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} with: @@ -978,48 +993,12 @@ jobs: /tmp/gh-aw/sandbox/firewall/awf-reflect.json if-no-files-found: ignore - atlassian_mcp_auth: - needs: activation - runs-on: ubuntu-latest - permissions: - id-token: write - - outputs: - authorization_header: ${{ steps.build_auth_header.outputs.authorization_header }} - steps: - - name: Configure GH_HOST for enterprise compatibility - id: ghes-host-config - shell: bash - run: | - # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct - # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. - GH_HOST="${GITHUB_SERVER_URL#https://}" - GH_HOST="${GH_HOST#http://}" - echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" - - name: Get Jira credentials from Vault - id: secrets - uses: SonarSource/vault-action-wrapper@c154b4a417b51cb98dd71137f49bf20e77c56820 # c154b4a417b51cb98dd71137f49bf20e77c56820 - with: - secrets: | - development/kv/data/jira user | JIRA_USER; - development/kv/data/jira token | JIRA_TOKEN; - - name: Build Atlassian MCP authorization header - id: build_auth_header - run: | - encoded=$(printf '%s' "${JIRA_USER}:${JIRA_TOKEN}" | base64 | tr -d '\n') - echo "::add-mask::${encoded}" - echo "authorization_header=Basic ${encoded}" >> "$GITHUB_OUTPUT" - env: - JIRA_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_TOKEN }} - JIRA_USER: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_USER }} - shell: bash - conclusion: needs: - activation - agent - - atlassian_mcp_auth - detection + - night_owl_prepare - safe_outputs - slack_notify if: > @@ -1356,6 +1335,100 @@ jobs: } } + night_owl_prepare: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + pull-requests: read + + outputs: + context_markdown: ${{ steps.prepare.outputs.context_markdown }} + issue_key: ${{ steps.prepare.outputs.issue_key }} + issue_summary: ${{ steps.prepare.outputs.issue_summary }} + issue_url: ${{ steps.prepare.outputs.issue_url }} + parent_key: ${{ steps.prepare.outputs.parent_key }} + parent_summary: ${{ steps.prepare.outputs.parent_summary }} + parent_url: ${{ steps.prepare.outputs.parent_url }} + prep_status: ${{ steps.prepare.outputs.prep_status }} + slack_message: ${{ steps.prepare.outputs.slack_message }} + steps: + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Get Jira credentials from Vault + id: secrets + uses: SonarSource/vault-action-wrapper@c154b4a417b51cb98dd71137f49bf20e77c56820 # c154b4a417b51cb98dd71137f49bf20e77c56820 + with: + secrets: | + development/kv/data/jira user | JIRA_USER; + development/kv/data/jira token | JIRA_TOKEN; + development/kv/data/slack token | SLACK_BOT_TOKEN; + - name: Install Atlassian CLI + run: | + case "${RUNNER_ARCH}" in + X64) platform=amd64 ;; + ARM64) platform=arm64 ;; + *) + echo "::error::Unsupported runner architecture: ${RUNNER_ARCH}" + exit 1 + ;; + esac + + curl -fsSLo "${RUNNER_TEMP}/acli" "https://acli.atlassian.com/linux/latest/acli_linux_${platform}/acli" + chmod +x "${RUNNER_TEMP}/acli" + install_dir="${RUNNER_TEMP}/night-owl-bin" + mkdir -p "${install_dir}" + mv "${RUNNER_TEMP}/acli" "${install_dir}/acli" + echo "${install_dir}" >> "${GITHUB_PATH}" + shell: bash + - name: Authenticate Atlassian CLI + run: | + echo "::add-mask::${JIRA_TOKEN}" + printf '%s\n' "${JIRA_TOKEN}" | acli jira auth login --email "${JIRA_USER}" --site "${NIGHT_OWL_JIRA_SITE}" --token + env: + JIRA_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_TOKEN }} + JIRA_USER: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_USER }} + shell: bash + - name: Prepare Jira context for Night Owl + id: prepare + run: bash .github/scripts/night-owl/prepare-jira-context.sh + env: + GH_TOKEN: ${{ github.token }} + continue-on-error: true + shell: bash + - name: Post starvation message to Slack + if: ${{ steps.prepare.outcome == 'success' && steps.prepare.outputs.prep_status == 'starving' }} + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # 70cd7be8e40a46e8b0eced40b0de447bdb42f68e + env: + SLACK_BOT_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).SLACK_BOT_TOKEN }} + with: + channel-id: ${{ env.NIGHT_OWL_SLACK_CHANNEL_ID }} + slack-message: ${{ steps.prepare.outputs.slack_message }} + - name: Post preparation failure to Slack + if: ${{ steps.prepare.outcome == 'failure' }} + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # 70cd7be8e40a46e8b0eced40b0de447bdb42f68e + env: + SLACK_BOT_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).SLACK_BOT_TOKEN }} + with: + channel-id: ${{ env.NIGHT_OWL_SLACK_CHANNEL_ID }} + slack-message: | + Night Owl: :warning: preparation failure + Outcome: Jira preparation failed before the coding agent could start. + Action needed: Inspect the `night_owl_prepare` job logs for this workflow run and verify ACLI installation, ACLI authentication, and Jira access for `${{ env.NIGHT_OWL_JIRA_SITE }}`. + - name: Fail job when preparation fails + if: ${{ steps.prepare.outcome == 'failure' }} + run: exit 1 + shell: bash + safe_outputs: needs: - activation @@ -1491,11 +1564,11 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} - 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,mcp.atlassian.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,www.googleapis.com" + GH_AW_ALLOWED_DOMAINS: "acli.atlassian.com,api.atlassian.com,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,sonarsource.atlassian.net,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} GH_AW_SAFE_OUTPUT_JOBS: "{\"slack_notify\":\"\"}" - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"base_branch\":\"${{ env.NIGHT_OWL_BASE_BRANCH }}\",\"draft\":true,\"labels\":[\"night-owl\",\"automated\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"allowed\",\"title_prefix\":\"[night-owl] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"base_branch\":\"${{ env.NIGHT_OWL_BASE_BRANCH }}\",\"draft\":true,\"labels\":[\"night-owl\",\"automated\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"fallback-to-issue\",\"title_prefix\":\"[night-owl] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"report_incomplete\":{}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ci-failure-triage-agent.md b/.github/workflows/ci-failure-triage-agent.md index b1695f462..1ded85a78 100644 --- a/.github/workflows/ci-failure-triage-agent.md +++ b/.github/workflows/ci-failure-triage-agent.md @@ -17,39 +17,42 @@ checkout: network: allowed: - defaults - - mcp.atlassian.com + - acli.atlassian.com + - api.atlassian.com + - sonarsource.atlassian.net tools: github: toolsets: [context, repos, pull_requests] -mcp-servers: - atlassian: - type: http - url: https://mcp.atlassian.com/v1/mcp/authv2 - headers: - Authorization: "${{ needs.atlassian_mcp_auth.outputs.authorization_header }}" - allowed: - - getAccessibleAtlassianResources - - searchJiraIssuesUsingJql - - getJiraIssue - - getJiraIssueRemoteIssueLinks - env: - NIGHT_OWL_BASE_BRANCH: master + NIGHT_OWL_BASE_BRANCH: ${{ github.event_name == 'workflow_dispatch' && github.ref_name || 'master' }} NIGHT_OWL_CANDIDATE_JQL: 'project = CLI AND labels = "for-agent" AND statusCategory = "To Do" ORDER BY created ASC' NIGHT_OWL_JIRA_LABEL: for-agent NIGHT_OWL_JIRA_PROJECT: CLI + NIGHT_OWL_JIRA_SITE: sonarsource.atlassian.net NIGHT_OWL_SLACK_CHANNEL_ID: C0B7GN16473 jobs: - atlassian_mcp_auth: + night_owl_prepare: runs-on: ubuntu-latest permissions: id-token: write + contents: read + pull-requests: read outputs: - authorization_header: ${{ steps.build_auth_header.outputs.authorization_header }} + prep_status: ${{ steps.prepare.outputs.prep_status }} + issue_key: ${{ steps.prepare.outputs.issue_key }} + issue_url: ${{ steps.prepare.outputs.issue_url }} + issue_summary: ${{ steps.prepare.outputs.issue_summary }} + parent_key: ${{ steps.prepare.outputs.parent_key }} + parent_url: ${{ steps.prepare.outputs.parent_url }} + parent_summary: ${{ steps.prepare.outputs.parent_summary }} + context_markdown: ${{ steps.prepare.outputs.context_markdown }} + slack_message: ${{ steps.prepare.outputs.slack_message }} steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Get Jira credentials from Vault id: secrets uses: SonarSource/vault-action-wrapper@c154b4a417b51cb98dd71137f49bf20e77c56820 @@ -57,16 +60,63 @@ jobs: secrets: | development/kv/data/jira user | JIRA_USER; development/kv/data/jira token | JIRA_TOKEN; - - name: Build Atlassian MCP authorization header - id: build_auth_header + development/kv/data/slack token | SLACK_BOT_TOKEN; + - name: Install Atlassian CLI + shell: bash + run: | + case "${RUNNER_ARCH}" in + X64) platform=amd64 ;; + ARM64) platform=arm64 ;; + *) + echo "::error::Unsupported runner architecture: ${RUNNER_ARCH}" + exit 1 + ;; + esac + + curl -fsSLo "${RUNNER_TEMP}/acli" "https://acli.atlassian.com/linux/latest/acli_linux_${platform}/acli" + chmod +x "${RUNNER_TEMP}/acli" + install_dir="${RUNNER_TEMP}/night-owl-bin" + mkdir -p "${install_dir}" + mv "${RUNNER_TEMP}/acli" "${install_dir}/acli" + echo "${install_dir}" >> "${GITHUB_PATH}" + - name: Authenticate Atlassian CLI shell: bash run: | - encoded=$(printf '%s' "${JIRA_USER}:${JIRA_TOKEN}" | base64 | tr -d '\n') - echo "::add-mask::${encoded}" - echo "authorization_header=Basic ${encoded}" >> "$GITHUB_OUTPUT" + echo "::add-mask::${JIRA_TOKEN}" + printf '%s\n' "${JIRA_TOKEN}" | acli jira auth login --email "${JIRA_USER}" --site "${NIGHT_OWL_JIRA_SITE}" --token env: JIRA_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_TOKEN }} JIRA_USER: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_USER }} + - name: Prepare Jira context for Night Owl + id: prepare + continue-on-error: true + shell: bash + run: bash .github/scripts/night-owl/prepare-jira-context.sh + env: + GH_TOKEN: ${{ github.token }} + - name: Post starvation message to Slack + if: ${{ steps.prepare.outcome == 'success' && steps.prepare.outputs.prep_status == 'starving' }} + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e + env: + SLACK_BOT_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).SLACK_BOT_TOKEN }} + with: + channel-id: ${{ env.NIGHT_OWL_SLACK_CHANNEL_ID }} + slack-message: ${{ steps.prepare.outputs.slack_message }} + - name: Post preparation failure to Slack + if: ${{ steps.prepare.outcome == 'failure' }} + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e + env: + SLACK_BOT_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).SLACK_BOT_TOKEN }} + with: + channel-id: ${{ env.NIGHT_OWL_SLACK_CHANNEL_ID }} + slack-message: | + Night Owl: :warning: preparation failure + Outcome: Jira preparation failed before the coding agent could start. + Action needed: Inspect the `night_owl_prepare` job logs for this workflow run and verify ACLI installation, ACLI authentication, and Jira access for `${{ env.NIGHT_OWL_JIRA_SITE }}`. + - name: Fail job when preparation fails + if: ${{ steps.prepare.outcome == 'failure' }} + shell: bash + run: exit 1 safe-outputs: noop: false @@ -74,7 +124,7 @@ safe-outputs: draft: true title-prefix: "[night-owl] " labels: [night-owl, automated] - protected-files: allowed + protected-files: fallback-to-issue base-branch: ${{ env.NIGHT_OWL_BASE_BRANCH }} jobs: slack-notify: @@ -127,39 +177,28 @@ This workflow is temporarily repurposed to run the Night Owl implementation flow ## Instructions 1. Read `AGENTS.md` and `CLAUDE.md` before changing code so you follow the repository-specific rules. -2. Use Atlassian MCP for all Jira discovery and ticket reading. Do not rely on any separate hand-written brief. +2. Jira preparation has already been done for you by the workflow using Atlassian CLI. Do not call Jira, Atlassian MCP, or `acli` yourself. 3. Use GitHub MCP plus the local checkout for repository context, implementation work, and PR checks. -4. Emit exactly one `slack_notify` safe output for every terminal outcome: - - `starving`: no eligible Jira ticket remains after filtering and open-PR deduplication; - - `blocked`: the selected ticket or its parent/linked context is missing key product or implementation decisions; +4. Emit exactly one `slack_notify` safe output for every terminal outcome after Jira prep succeeds: + - `blocked`: the selected ticket or its prepared parent/linked context is missing key product or implementation decisions; - `draft-pr-opened`: the implementation is done and a draft PR was created. +5. If the prepared Jira status below is not `ready`, stop immediately without emitting any safe outputs. The workflow already handled starvation or infrastructure notification. -## 1. Discover candidate Jira tickets - -1. Use `getAccessibleAtlassianResources` to find the SonarSource Jira Cloud site. -2. Use `searchJiraIssuesUsingJql` with `$NIGHT_OWL_CANDIDATE_JQL`. -3. Process candidates in the order returned by Jira. Do not randomize. -4. Keep only issues whose current status name is exactly `Open`, `TODO`, or `To Do`. -5. Work only on issues in project `$NIGHT_OWL_JIRA_PROJECT` with label `$NIGHT_OWL_JIRA_LABEL`. - -## 2. Skip tickets already in flight - -For each candidate issue: - -1. Use GitHub MCP to inspect open pull requests in this repository. -2. If an open PR title, body, branch name, or obvious linked reference already targets the Jira key, skip that issue and continue with the next one. -3. If no eligible candidate remains after this deduplication, emit a `slack_notify` message that Night Owl is starving and stop successfully. +## Prepared Jira Inputs -## 3. Gather Jira context +- Prep status: `${{ needs.night_owl_prepare.outputs.prep_status }}` +- Ticket: `${{ needs.night_owl_prepare.outputs.issue_key }}` +- Ticket URL: `${{ needs.night_owl_prepare.outputs.issue_url }}` +- Ticket summary: `${{ needs.night_owl_prepare.outputs.issue_summary }}` +- Parent: `${{ needs.night_owl_prepare.outputs.parent_key }}` +- Parent URL: `${{ needs.night_owl_prepare.outputs.parent_url }}` +- Parent summary: `${{ needs.night_owl_prepare.outputs.parent_summary }}` -For the first eligible issue that is not already in flight: +${{ needs.night_owl_prepare.outputs.context_markdown }} -1. Read the Jira issue details, including summary, description, issue type, status, labels, comments, subtasks, and issue links. -2. If the issue has a parent or epic, read that issue too before making decisions. -3. Read additional linked Jira tickets only when they are directly relevant to implementation or acceptance criteria. -4. Treat Jira as the source of truth for the ticket context. +Treat the prepared Jira content above as the source of truth. Do not invent missing Jira details and do not assume you can fetch more Jira data later. -## 4. Decide whether the ticket is actionable +## 1. Decide whether the ticket is actionable Stop and emit a `slack_notify` message without creating a PR if any required behavior is missing or ambiguous, including: @@ -176,7 +215,7 @@ The blocked Slack message must include: - a short explanation of why you stopped, - the concrete missing decisions or questions that a human needs to answer. -## 5. Implement the ticket +## 2. Implement the ticket If the issue is actionable: @@ -186,7 +225,7 @@ If the issue is actionable: 4. Run the smallest set of meaningful checks that match the files you changed. If you edit TypeScript, follow the repository formatting requirements before you finish. 5. If you cannot finish a coherent implementation or cannot validate it well enough to justify a draft PR, stop and emit a blocked Slack message explaining what remains. -## 6. Open a draft PR +## 3. Open a draft PR When the implementation is complete: @@ -199,13 +238,13 @@ When the implementation is complete: - the checks you ran, - any open questions or follow-ups that remain. -## 7. Notify Slack +## 4. Notify Slack -Emit a `slack_notify` message for every terminal outcome. +Emit a `slack_notify` message for every terminal outcome you handle in the agent. The message should be structured and concise: -- `Night Owl`: starving, blocked, or draft PR opened +- `Night Owl`: blocked or draft PR opened - `Ticket`: Jira key, link, and summary when applicable - `Parent`: parent Jira key and summary when applicable - `Outcome`: one or two sentences diff --git a/.github/workflows/night-owl.lock.yml b/.github/workflows/night-owl.lock.yml index 9af837969..cb4265c6a 100644 --- a/.github/workflows/night-owl.lock.yml +++ b/.github/workflows/night-owl.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"b3ed5a23b246495a02613fe57f0e27e96cada4897433840fdaa0d10d8e409fcc","compiler_version":"v0.76.1","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"ee4270148b1deb7e28facefb2bc647e07b0b67bd767541d20eddf3b0faf5fcf2","compiler_version":"v0.76.1","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"SonarSource/vault-action-wrapper","sha":"c154b4a417b51cb98dd71137f49bf20e77c56820","version":"c154b4a417b51cb98dd71137f49bf20e77c56820"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"46d564922b082d0db93244972e8005ea6904ee5f","version":"v0.76.1"},{"repo":"slackapi/slack-github-action","sha":"70cd7be8e40a46e8b0eced40b0de447bdb42f68e","version":"70cd7be8e40a46e8b0eced40b0de447bdb42f68e"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.55"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.55"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.19"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4","digest":"sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.4@sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} # ___ _ _ # / _ \ | | (_) @@ -28,6 +28,7 @@ # - NIGHT_OWL_CANDIDATE_JQL: (main workflow) # - NIGHT_OWL_JIRA_LABEL: (main workflow) # - NIGHT_OWL_JIRA_PROJECT: (main workflow) +# - NIGHT_OWL_JIRA_SITE: (main workflow) # - NIGHT_OWL_SLACK_CHANNEL_ID: (main workflow) # # Secrets used: @@ -75,14 +76,16 @@ concurrency: night-owl run-name: "Night Owl" env: - NIGHT_OWL_BASE_BRANCH: master + NIGHT_OWL_BASE_BRANCH: ${{ github.event_name == 'workflow_dispatch' && github.ref_name || 'master' }} NIGHT_OWL_CANDIDATE_JQL: project = CLI AND labels = "for-agent" AND statusCategory = "To Do" ORDER BY created ASC NIGHT_OWL_JIRA_LABEL: for-agent NIGHT_OWL_JIRA_PROJECT: CLI + NIGHT_OWL_JIRA_SITE: sonarsource.atlassian.net NIGHT_OWL_SLACK_CHANNEL_ID: C0B7GN16473 jobs: activation: + needs: night_owl_prepare runs-on: ubuntu-slim permissions: actions: read @@ -124,7 +127,7 @@ jobs: GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" - GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","mcp.atlassian.com"]' + GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","acli.atlassian.com","api.atlassian.com","sonarsource.atlassian.net"]' GH_AW_INFO_FIREWALL_ENABLED: "true" GH_AW_INFO_AWF_VERSION: "v0.25.55" GH_AW_INFO_AWMG_VERSION: "" @@ -198,27 +201,35 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_CONTEXT_MARKDOWN: ${{ needs.night_owl_prepare.outputs.context_markdown }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_KEY: ${{ needs.night_owl_prepare.outputs.issue_key }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_SUMMARY: ${{ needs.night_owl_prepare.outputs.issue_summary }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_URL: ${{ needs.night_owl_prepare.outputs.issue_url }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_KEY: ${{ needs.night_owl_prepare.outputs.parent_key }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_SUMMARY: ${{ needs.night_owl_prepare.outputs.parent_summary }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_URL: ${{ needs.night_owl_prepare.outputs.parent_url }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PREP_STATUS: ${{ needs.night_owl_prepare.outputs.prep_status }} # poutine:ignore untrusted_checkout_exec run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_7c74984cf505da3e_EOF' + cat << 'GH_AW_PROMPT_d3dcfaa93bd10850_EOF' - GH_AW_PROMPT_7c74984cf505da3e_EOF + GH_AW_PROMPT_d3dcfaa93bd10850_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_7c74984cf505da3e_EOF' + cat << 'GH_AW_PROMPT_d3dcfaa93bd10850_EOF' Tools: create_pull_request, missing_tool, missing_data, slack_notify - GH_AW_PROMPT_7c74984cf505da3e_EOF + GH_AW_PROMPT_d3dcfaa93bd10850_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_7c74984cf505da3e_EOF' + cat << 'GH_AW_PROMPT_d3dcfaa93bd10850_EOF' - GH_AW_PROMPT_7c74984cf505da3e_EOF + GH_AW_PROMPT_d3dcfaa93bd10850_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_7c74984cf505da3e_EOF' + cat << 'GH_AW_PROMPT_d3dcfaa93bd10850_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -250,18 +261,26 @@ jobs: - **Note**: If a branch you need is not in the list above and is not listed as an additional fetched ref, it has NOT been checked out. For private repositories you cannot fetch it without proper authentication. If the branch is required and not available, exit with an error and ask the user to add it to the `fetch:` option of the `checkout:` configuration (e.g., `fetch: ["refs/pulls/open/*"]` for all open PR refs, or `fetch: ["main", "feature/my-branch"]` for specific branches). - GH_AW_PROMPT_7c74984cf505da3e_EOF + GH_AW_PROMPT_d3dcfaa93bd10850_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_7c74984cf505da3e_EOF' + cat << 'GH_AW_PROMPT_d3dcfaa93bd10850_EOF' {{#runtime-import .github/workflows/night-owl.md}} - GH_AW_PROMPT_7c74984cf505da3e_EOF + GH_AW_PROMPT_d3dcfaa93bd10850_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_ENGINE_ID: "copilot" + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_CONTEXT_MARKDOWN: ${{ needs.night_owl_prepare.outputs.context_markdown }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_KEY: ${{ needs.night_owl_prepare.outputs.issue_key }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_SUMMARY: ${{ needs.night_owl_prepare.outputs.issue_summary }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_URL: ${{ needs.night_owl_prepare.outputs.issue_url }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_KEY: ${{ needs.night_owl_prepare.outputs.parent_key }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_SUMMARY: ${{ needs.night_owl_prepare.outputs.parent_summary }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_URL: ${{ needs.night_owl_prepare.outputs.parent_url }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PREP_STATUS: ${{ needs.night_owl_prepare.outputs.prep_status }} with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -281,6 +300,14 @@ jobs: GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_CONTEXT_MARKDOWN: ${{ needs.night_owl_prepare.outputs.context_markdown }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_KEY: ${{ needs.night_owl_prepare.outputs.issue_key }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_SUMMARY: ${{ needs.night_owl_prepare.outputs.issue_summary }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_URL: ${{ needs.night_owl_prepare.outputs.issue_url }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_KEY: ${{ needs.night_owl_prepare.outputs.parent_key }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_SUMMARY: ${{ needs.night_owl_prepare.outputs.parent_summary }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_URL: ${{ needs.night_owl_prepare.outputs.parent_url }} + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PREP_STATUS: ${{ needs.night_owl_prepare.outputs.prep_status }} with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -300,7 +327,15 @@ jobs: 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_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_CONTEXT_MARKDOWN: process.env.GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_CONTEXT_MARKDOWN, + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_KEY: process.env.GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_KEY, + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_SUMMARY: process.env.GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_SUMMARY, + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_URL: process.env.GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_ISSUE_URL, + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_KEY: process.env.GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_KEY, + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_SUMMARY: process.env.GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_SUMMARY, + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_URL: process.env.GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PARENT_URL, + GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PREP_STATUS: process.env.GH_AW_NEEDS_NIGHT_OWL_PREPARE_OUTPUTS_PREP_STATUS } }); - name: Validate prompt placeholders @@ -334,7 +369,7 @@ jobs: agent: needs: - activation - - atlassian_mcp_auth + - night_owl_prepare runs-on: ubuntu-latest permissions: contents: read @@ -468,9 +503,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_bec8dc372f5517fb_EOF' - {"create_pull_request":{"base_branch":"${{ env.NIGHT_OWL_BASE_BRANCH }}","draft":true,"labels":["night-owl","automated"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"allowed","title_prefix":"[night-owl] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"report_incomplete":{},"slack-notify":{"description":"Send a night-owl message to Slack","inputs":{"message":{"default":null,"description":"The night-owl message to send","required":true,"type":"string"}}}} - GH_AW_SAFE_OUTPUTS_CONFIG_bec8dc372f5517fb_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_45b7b35a3323608a_EOF' + {"create_pull_request":{"base_branch":"${{ env.NIGHT_OWL_BASE_BRANCH }}","draft":true,"labels":["night-owl","automated"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"fallback-to-issue","title_prefix":"[night-owl] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"report_incomplete":{},"slack-notify":{"description":"Send a night-owl message to Slack","inputs":{"message":{"default":null,"description":"The night-owl message to send","required":true,"type":"string"}}}} + GH_AW_SAFE_OUTPUTS_CONFIG_45b7b35a3323608a_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -690,29 +725,9 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_23b0a2526e25efe8_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_72f50d2a7606cca8_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { - "atlassian": { - "type": "http", - "url": "https://mcp.atlassian.com/v1/mcp/authv2", - "headers": { - "Authorization": "${{ needs.atlassian_mcp_auth.outputs.authorization_header }}" - }, - "tools": [ - "getAccessibleAtlassianResources", - "searchJiraIssuesUsingJql", - "getJiraIssue", - "getJiraIssueRemoteIssueLinks" - ], - "guard-policies": { - "write-sink": { - "accept": [ - "*" - ] - } - } - }, "github": { "type": "stdio", "container": "ghcr.io/github/github-mcp-server:v1.0.4", @@ -751,7 +766,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_23b0a2526e25efe8_EOF + GH_AW_MCP_CONFIG_72f50d2a7606cca8_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -785,7 +800,7 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.55/awf-config.schema.json","network":{"allowDomains":["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","mcp.atlassian.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","www.googleapis.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5.3","gemini-pro","any"],"antigravity":["copilot/antigravity*","google/antigravity*","gemini/antigravity*"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"claude":["agent"],"codex":["agent"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"computer-use":["copilot/*computer-use*","google/*computer-use*","gemini/*computer-use*","openai/*computer-use*"],"copilot":["agent"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent"],"gemini-3-flash":["copilot/gemini-3*flash*","google/gemini-3*flash*","gemini/gemini-3*flash*"],"gemini-3-pro":["copilot/gemini-3*pro*","google/gemini-3*pro*","gemini/gemini-3*pro*"],"gemini-3.1-flash":["copilot/gemini-3.1*flash*","google/gemini-3.1*flash*","gemini/gemini-3.1*flash*"],"gemini-3.1-pro":["copilot/gemini-3.1*pro*","google/gemini-3.1*pro*","gemini/gemini-3.1*pro*"],"gemini-3.5-flash":["copilot/gemini-3.5*flash*","google/gemini-3.5*flash*","gemini/gemini-3.5*flash*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"gpt-5.2":["copilot/gpt-5.2*","openai/gpt-5.2*"],"gpt-5.3":["copilot/gpt-5.3*","openai/gpt-5.3*"],"gpt-5.4":["copilot/gpt-5.4*","openai/gpt-5.4*"],"gpt-5.5":["copilot/gpt-5.5*","openai/gpt-5.5*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"opus":["copilot/*opus*","anthropic/*opus*"],"opusplan":["opus?effort=high"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"robotics":["copilot/*robotics*","google/*robotics*","gemini/*robotics*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4-5-*","anthropic/*sonnet-4-5-*","copilot/*sonnet-4-6*","anthropic/*sonnet-4-6*"],"summarization":["haiku","gpt-5-mini","gemini-flash-lite","mini"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.25.55"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.55/awf-config.schema.json","network":{"allowDomains":["acli.atlassian.com","api.atlassian.com","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","sonarsource.atlassian.net","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5.3","gemini-pro","any"],"antigravity":["copilot/antigravity*","google/antigravity*","gemini/antigravity*"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"claude":["agent"],"codex":["agent"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"computer-use":["copilot/*computer-use*","google/*computer-use*","gemini/*computer-use*","openai/*computer-use*"],"copilot":["agent"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent"],"gemini-3-flash":["copilot/gemini-3*flash*","google/gemini-3*flash*","gemini/gemini-3*flash*"],"gemini-3-pro":["copilot/gemini-3*pro*","google/gemini-3*pro*","gemini/gemini-3*pro*"],"gemini-3.1-flash":["copilot/gemini-3.1*flash*","google/gemini-3.1*flash*","gemini/gemini-3.1*flash*"],"gemini-3.1-pro":["copilot/gemini-3.1*pro*","google/gemini-3.1*pro*","gemini/gemini-3.1*pro*"],"gemini-3.5-flash":["copilot/gemini-3.5*flash*","google/gemini-3.5*flash*","gemini/gemini-3.5*flash*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"gpt-5.2":["copilot/gpt-5.2*","openai/gpt-5.2*"],"gpt-5.3":["copilot/gpt-5.3*","openai/gpt-5.3*"],"gpt-5.4":["copilot/gpt-5.4*","openai/gpt-5.4*"],"gpt-5.5":["copilot/gpt-5.5*","openai/gpt-5.5*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"opus":["copilot/*opus*","anthropic/*opus*"],"opusplan":["opus?effort=high"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"robotics":["copilot/*robotics*","google/*robotics*","gemini/*robotics*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4-5-*","anthropic/*sonnet-4-5-*","copilot/*sonnet-4-6*","anthropic/*sonnet-4-6*"],"summarization":["haiku","gpt-5-mini","gemini-flash-lite","mini"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.25.55"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then @@ -881,7 +896,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.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,mcp.atlassian.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,www.googleapis.com" + GH_AW_ALLOWED_DOMAINS: "acli.atlassian.com,api.atlassian.com,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,sonarsource.atlassian.net,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} with: @@ -978,48 +993,12 @@ jobs: /tmp/gh-aw/sandbox/firewall/awf-reflect.json if-no-files-found: ignore - atlassian_mcp_auth: - needs: activation - runs-on: ubuntu-latest - permissions: - id-token: write - - outputs: - authorization_header: ${{ steps.build_auth_header.outputs.authorization_header }} - steps: - - name: Configure GH_HOST for enterprise compatibility - id: ghes-host-config - shell: bash - run: | - # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct - # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. - GH_HOST="${GITHUB_SERVER_URL#https://}" - GH_HOST="${GH_HOST#http://}" - echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" - - name: Get Jira credentials from Vault - id: secrets - uses: SonarSource/vault-action-wrapper@c154b4a417b51cb98dd71137f49bf20e77c56820 # c154b4a417b51cb98dd71137f49bf20e77c56820 - with: - secrets: | - development/kv/data/jira user | JIRA_USER; - development/kv/data/jira token | JIRA_TOKEN; - - name: Build Atlassian MCP authorization header - id: build_auth_header - run: | - encoded=$(printf '%s' "${JIRA_USER}:${JIRA_TOKEN}" | base64 | tr -d '\n') - echo "::add-mask::${encoded}" - echo "authorization_header=Basic ${encoded}" >> "$GITHUB_OUTPUT" - env: - JIRA_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_TOKEN }} - JIRA_USER: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_USER }} - shell: bash - conclusion: needs: - activation - agent - - atlassian_mcp_auth - detection + - night_owl_prepare - safe_outputs - slack_notify if: > @@ -1356,6 +1335,100 @@ jobs: } } + night_owl_prepare: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + pull-requests: read + + outputs: + context_markdown: ${{ steps.prepare.outputs.context_markdown }} + issue_key: ${{ steps.prepare.outputs.issue_key }} + issue_summary: ${{ steps.prepare.outputs.issue_summary }} + issue_url: ${{ steps.prepare.outputs.issue_url }} + parent_key: ${{ steps.prepare.outputs.parent_key }} + parent_summary: ${{ steps.prepare.outputs.parent_summary }} + parent_url: ${{ steps.prepare.outputs.parent_url }} + prep_status: ${{ steps.prepare.outputs.prep_status }} + slack_message: ${{ steps.prepare.outputs.slack_message }} + steps: + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Get Jira credentials from Vault + id: secrets + uses: SonarSource/vault-action-wrapper@c154b4a417b51cb98dd71137f49bf20e77c56820 # c154b4a417b51cb98dd71137f49bf20e77c56820 + with: + secrets: | + development/kv/data/jira user | JIRA_USER; + development/kv/data/jira token | JIRA_TOKEN; + development/kv/data/slack token | SLACK_BOT_TOKEN; + - name: Install Atlassian CLI + run: | + case "${RUNNER_ARCH}" in + X64) platform=amd64 ;; + ARM64) platform=arm64 ;; + *) + echo "::error::Unsupported runner architecture: ${RUNNER_ARCH}" + exit 1 + ;; + esac + + curl -fsSLo "${RUNNER_TEMP}/acli" "https://acli.atlassian.com/linux/latest/acli_linux_${platform}/acli" + chmod +x "${RUNNER_TEMP}/acli" + install_dir="${RUNNER_TEMP}/night-owl-bin" + mkdir -p "${install_dir}" + mv "${RUNNER_TEMP}/acli" "${install_dir}/acli" + echo "${install_dir}" >> "${GITHUB_PATH}" + shell: bash + - name: Authenticate Atlassian CLI + run: | + echo "::add-mask::${JIRA_TOKEN}" + printf '%s\n' "${JIRA_TOKEN}" | acli jira auth login --email "${JIRA_USER}" --site "${NIGHT_OWL_JIRA_SITE}" --token + env: + JIRA_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_TOKEN }} + JIRA_USER: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_USER }} + shell: bash + - name: Prepare Jira context for Night Owl + id: prepare + run: bash .github/scripts/night-owl/prepare-jira-context.sh + env: + GH_TOKEN: ${{ github.token }} + continue-on-error: true + shell: bash + - name: Post starvation message to Slack + if: ${{ steps.prepare.outcome == 'success' && steps.prepare.outputs.prep_status == 'starving' }} + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # 70cd7be8e40a46e8b0eced40b0de447bdb42f68e + env: + SLACK_BOT_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).SLACK_BOT_TOKEN }} + with: + channel-id: ${{ env.NIGHT_OWL_SLACK_CHANNEL_ID }} + slack-message: ${{ steps.prepare.outputs.slack_message }} + - name: Post preparation failure to Slack + if: ${{ steps.prepare.outcome == 'failure' }} + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # 70cd7be8e40a46e8b0eced40b0de447bdb42f68e + env: + SLACK_BOT_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).SLACK_BOT_TOKEN }} + with: + channel-id: ${{ env.NIGHT_OWL_SLACK_CHANNEL_ID }} + slack-message: | + Night Owl: :warning: preparation failure + Outcome: Jira preparation failed before the coding agent could start. + Action needed: Inspect the `night_owl_prepare` job logs for this workflow run and verify ACLI installation, ACLI authentication, and Jira access for `${{ env.NIGHT_OWL_JIRA_SITE }}`. + - name: Fail job when preparation fails + if: ${{ steps.prepare.outcome == 'failure' }} + run: exit 1 + shell: bash + safe_outputs: needs: - activation @@ -1491,11 +1564,11 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} - 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,mcp.atlassian.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,www.googleapis.com" + GH_AW_ALLOWED_DOMAINS: "acli.atlassian.com,api.atlassian.com,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,sonarsource.atlassian.net,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} GH_AW_SAFE_OUTPUT_JOBS: "{\"slack_notify\":\"\"}" - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"base_branch\":\"${{ env.NIGHT_OWL_BASE_BRANCH }}\",\"draft\":true,\"labels\":[\"night-owl\",\"automated\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"allowed\",\"title_prefix\":\"[night-owl] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"base_branch\":\"${{ env.NIGHT_OWL_BASE_BRANCH }}\",\"draft\":true,\"labels\":[\"night-owl\",\"automated\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"fallback-to-issue\",\"title_prefix\":\"[night-owl] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"report_incomplete\":{}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/night-owl.md b/.github/workflows/night-owl.md index a3ebf2a7f..7cb76ee09 100644 --- a/.github/workflows/night-owl.md +++ b/.github/workflows/night-owl.md @@ -17,39 +17,42 @@ checkout: network: allowed: - defaults - - mcp.atlassian.com + - acli.atlassian.com + - api.atlassian.com + - sonarsource.atlassian.net tools: github: toolsets: [context, repos, pull_requests] -mcp-servers: - atlassian: - type: http - url: https://mcp.atlassian.com/v1/mcp/authv2 - headers: - Authorization: "${{ needs.atlassian_mcp_auth.outputs.authorization_header }}" - allowed: - - getAccessibleAtlassianResources - - searchJiraIssuesUsingJql - - getJiraIssue - - getJiraIssueRemoteIssueLinks - env: - NIGHT_OWL_BASE_BRANCH: master + NIGHT_OWL_BASE_BRANCH: ${{ github.event_name == 'workflow_dispatch' && github.ref_name || 'master' }} NIGHT_OWL_CANDIDATE_JQL: 'project = CLI AND labels = "for-agent" AND statusCategory = "To Do" ORDER BY created ASC' NIGHT_OWL_JIRA_LABEL: for-agent NIGHT_OWL_JIRA_PROJECT: CLI + NIGHT_OWL_JIRA_SITE: sonarsource.atlassian.net NIGHT_OWL_SLACK_CHANNEL_ID: C0B7GN16473 jobs: - atlassian_mcp_auth: + night_owl_prepare: runs-on: ubuntu-latest permissions: id-token: write + contents: read + pull-requests: read outputs: - authorization_header: ${{ steps.build_auth_header.outputs.authorization_header }} + prep_status: ${{ steps.prepare.outputs.prep_status }} + issue_key: ${{ steps.prepare.outputs.issue_key }} + issue_url: ${{ steps.prepare.outputs.issue_url }} + issue_summary: ${{ steps.prepare.outputs.issue_summary }} + parent_key: ${{ steps.prepare.outputs.parent_key }} + parent_url: ${{ steps.prepare.outputs.parent_url }} + parent_summary: ${{ steps.prepare.outputs.parent_summary }} + context_markdown: ${{ steps.prepare.outputs.context_markdown }} + slack_message: ${{ steps.prepare.outputs.slack_message }} steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Get Jira credentials from Vault id: secrets uses: SonarSource/vault-action-wrapper@c154b4a417b51cb98dd71137f49bf20e77c56820 @@ -57,16 +60,63 @@ jobs: secrets: | development/kv/data/jira user | JIRA_USER; development/kv/data/jira token | JIRA_TOKEN; - - name: Build Atlassian MCP authorization header - id: build_auth_header + development/kv/data/slack token | SLACK_BOT_TOKEN; + - name: Install Atlassian CLI + shell: bash + run: | + case "${RUNNER_ARCH}" in + X64) platform=amd64 ;; + ARM64) platform=arm64 ;; + *) + echo "::error::Unsupported runner architecture: ${RUNNER_ARCH}" + exit 1 + ;; + esac + + curl -fsSLo "${RUNNER_TEMP}/acli" "https://acli.atlassian.com/linux/latest/acli_linux_${platform}/acli" + chmod +x "${RUNNER_TEMP}/acli" + install_dir="${RUNNER_TEMP}/night-owl-bin" + mkdir -p "${install_dir}" + mv "${RUNNER_TEMP}/acli" "${install_dir}/acli" + echo "${install_dir}" >> "${GITHUB_PATH}" + - name: Authenticate Atlassian CLI shell: bash run: | - encoded=$(printf '%s' "${JIRA_USER}:${JIRA_TOKEN}" | base64 | tr -d '\n') - echo "::add-mask::${encoded}" - echo "authorization_header=Basic ${encoded}" >> "$GITHUB_OUTPUT" + echo "::add-mask::${JIRA_TOKEN}" + printf '%s\n' "${JIRA_TOKEN}" | acli jira auth login --email "${JIRA_USER}" --site "${NIGHT_OWL_JIRA_SITE}" --token env: JIRA_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_TOKEN }} JIRA_USER: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_USER }} + - name: Prepare Jira context for Night Owl + id: prepare + continue-on-error: true + shell: bash + run: bash .github/scripts/night-owl/prepare-jira-context.sh + env: + GH_TOKEN: ${{ github.token }} + - name: Post starvation message to Slack + if: ${{ steps.prepare.outcome == 'success' && steps.prepare.outputs.prep_status == 'starving' }} + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e + env: + SLACK_BOT_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).SLACK_BOT_TOKEN }} + with: + channel-id: ${{ env.NIGHT_OWL_SLACK_CHANNEL_ID }} + slack-message: ${{ steps.prepare.outputs.slack_message }} + - name: Post preparation failure to Slack + if: ${{ steps.prepare.outcome == 'failure' }} + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e + env: + SLACK_BOT_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).SLACK_BOT_TOKEN }} + with: + channel-id: ${{ env.NIGHT_OWL_SLACK_CHANNEL_ID }} + slack-message: | + Night Owl: :warning: preparation failure + Outcome: Jira preparation failed before the coding agent could start. + Action needed: Inspect the `night_owl_prepare` job logs for this workflow run and verify ACLI installation, ACLI authentication, and Jira access for `${{ env.NIGHT_OWL_JIRA_SITE }}`. + - name: Fail job when preparation fails + if: ${{ steps.prepare.outcome == 'failure' }} + shell: bash + run: exit 1 safe-outputs: noop: false @@ -74,7 +124,7 @@ safe-outputs: draft: true title-prefix: "[night-owl] " labels: [night-owl, automated] - protected-files: allowed + protected-files: fallback-to-issue base-branch: ${{ env.NIGHT_OWL_BASE_BRANCH }} jobs: slack-notify: @@ -127,39 +177,28 @@ You are the nightly implementation agent for this repository. ## Instructions 1. Read `AGENTS.md` and `CLAUDE.md` before changing code so you follow the repository-specific rules. -2. Use Atlassian MCP for all Jira discovery and ticket reading. Do not rely on any separate hand-written brief. +2. Jira preparation has already been done for you by the workflow using Atlassian CLI. Do not call Jira, Atlassian MCP, or `acli` yourself. 3. Use GitHub MCP plus the local checkout for repository context, implementation work, and PR checks. -4. Emit exactly one `slack_notify` safe output for every terminal outcome: - - `starving`: no eligible Jira ticket remains after filtering and open-PR deduplication; - - `blocked`: the selected ticket or its parent/linked context is missing key product or implementation decisions; +4. Emit exactly one `slack_notify` safe output for every terminal outcome after Jira prep succeeds: + - `blocked`: the selected ticket or its prepared parent/linked context is missing key product or implementation decisions; - `draft-pr-opened`: the implementation is done and a draft PR was created. +5. If the prepared Jira status below is not `ready`, stop immediately without emitting any safe outputs. The workflow already handled starvation or infrastructure notification. -## 1. Discover candidate Jira tickets - -1. Use `getAccessibleAtlassianResources` to find the SonarSource Jira Cloud site. -2. Use `searchJiraIssuesUsingJql` with `$NIGHT_OWL_CANDIDATE_JQL`. -3. Process candidates in the order returned by Jira. Do not randomize. -4. Keep only issues whose current status name is exactly `Open`, `TODO`, or `To Do`. -5. Work only on issues in project `$NIGHT_OWL_JIRA_PROJECT` with label `$NIGHT_OWL_JIRA_LABEL`. - -## 2. Skip tickets already in flight - -For each candidate issue: - -1. Use GitHub MCP to inspect open pull requests in this repository. -2. If an open PR title, body, branch name, or obvious linked reference already targets the Jira key, skip that issue and continue with the next one. -3. If no eligible candidate remains after this deduplication, emit a `slack_notify` message that Night Owl is starving and stop successfully. +## Prepared Jira Inputs -## 3. Gather Jira context +- Prep status: `${{ needs.night_owl_prepare.outputs.prep_status }}` +- Ticket: `${{ needs.night_owl_prepare.outputs.issue_key }}` +- Ticket URL: `${{ needs.night_owl_prepare.outputs.issue_url }}` +- Ticket summary: `${{ needs.night_owl_prepare.outputs.issue_summary }}` +- Parent: `${{ needs.night_owl_prepare.outputs.parent_key }}` +- Parent URL: `${{ needs.night_owl_prepare.outputs.parent_url }}` +- Parent summary: `${{ needs.night_owl_prepare.outputs.parent_summary }}` -For the first eligible issue that is not already in flight: +${{ needs.night_owl_prepare.outputs.context_markdown }} -1. Read the Jira issue details, including summary, description, issue type, status, labels, comments, subtasks, and issue links. -2. If the issue has a parent or epic, read that issue too before making decisions. -3. Read additional linked Jira tickets only when they are directly relevant to implementation or acceptance criteria. -4. Treat Jira as the source of truth for the ticket context. +Treat the prepared Jira content above as the source of truth. Do not invent missing Jira details and do not assume you can fetch more Jira data later. -## 4. Decide whether the ticket is actionable +## 1. Decide whether the ticket is actionable Stop and emit a `slack_notify` message without creating a PR if any required behavior is missing or ambiguous, including: @@ -176,7 +215,7 @@ The blocked Slack message must include: - a short explanation of why you stopped, - the concrete missing decisions or questions that a human needs to answer. -## 5. Implement the ticket +## 2. Implement the ticket If the issue is actionable: @@ -186,7 +225,7 @@ If the issue is actionable: 4. Run the smallest set of meaningful checks that match the files you changed. If you edit TypeScript, follow the repository formatting requirements before you finish. 5. If you cannot finish a coherent implementation or cannot validate it well enough to justify a draft PR, stop and emit a blocked Slack message explaining what remains. -## 6. Open a draft PR +## 3. Open a draft PR When the implementation is complete: @@ -199,13 +238,13 @@ When the implementation is complete: - the checks you ran, - any open questions or follow-ups that remain. -## 7. Notify Slack +## 4. Notify Slack -Emit a `slack_notify` message for every terminal outcome. +Emit a `slack_notify` message for every terminal outcome you handle in the agent. The message should be structured and concise: -- `Night Owl`: starving, blocked, or draft PR opened +- `Night Owl`: blocked or draft PR opened - `Ticket`: Jira key, link, and summary when applicable - `Parent`: parent Jira key and summary when applicable - `Outcome`: one or two sentences diff --git a/CLAUDE.md b/CLAUDE.md index 0dccecca4..6bf6f2439 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -111,8 +111,8 @@ When adding, removing, or changing commands, scripts, or project structure, upda ## GitHub Agentic Workflows - Agentic workflow source files live in `.github/workflows/*.md`. Generated `.lock.yml` files are compiler output; update them with `gh aw compile ` instead of editing them directly. -- `ci-failure-triage-agent.md` watches failed workflow runs and can open draft remediation PRs. -- `night-owl.md` runs nightly at `00:00 UTC`, uses GitHub MCP plus Atlassian MCP to select one `CLI` Jira ticket labeled `for-agent` in a to-do status, reads the ticket and parent context from Jira, opens draft PRs when the work is implementable, and posts Slack updates for starvation, blocked tickets, and draft PR creation. +- `night-owl.md` runs nightly at `00:00 UTC`, installs Atlassian CLI in a prep job, authenticates with Vault-provided Jira bot credentials, selects the first `CLI` Jira ticket labeled `for-agent` in status `Open`, `TODO`, or `To Do`, materializes the ticket plus parent/comment/link context, posts Slack when the queue is starving or prep fails, then lets the agent work only from that prepared Jira context and open draft PRs. Scheduled runs target `master`; manual `workflow_dispatch` runs target the branch they were launched from so Night Owl testing on feature branches does not drag workflow changes into the generated PR patch. +- `ci-failure-triage-agent.md` is temporarily repurposed to mirror `night-owl.md` so the Night Owl flow can be triggered from the existing workflow slot during rollout; like `night-owl.md`, manual dispatches target the selected branch while the nightly schedule targets `master`. ## Docs site (`docs/`)