diff --git a/.github/workflows/daily-compiler-quality.lock.yml b/.github/workflows/daily-compiler-quality.lock.yml index d09f2e2b5e4..dba83d8f3b7 100644 --- a/.github/workflows/daily-compiler-quality.lock.yml +++ b/.github/workflows/daily-compiler-quality.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"7bc1a19e4a2af352c977f50c373f15abb29d5c9c10f502ba5972784b4aa91318","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"04b56ba3a0712696e891e0d3a56d877640a47c95425b656283b385634c1695bc","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"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"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.42"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.42"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.42"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.42"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"ghcr.io/github/serena-mcp-server:latest","digest":"sha256:bf343399e3725c45528f531a230f3a04521d4cdef29f9a5af6282ff0d3c393c5","pinned_image":"ghcr.io/github/serena-mcp-server:latest@sha256:bf343399e3725c45528f531a230f3a04521d4cdef29f9a5af6282ff0d3c393c5"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -199,21 +199,21 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_f8c19ab5cd1cee06_EOF' + cat << 'GH_AW_PROMPT_008a45fba0979ffd_EOF' - GH_AW_PROMPT_f8c19ab5cd1cee06_EOF + GH_AW_PROMPT_008a45fba0979ffd_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/cache_memory_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_f8c19ab5cd1cee06_EOF' + cat << 'GH_AW_PROMPT_008a45fba0979ffd_EOF' Tools: create_discussion, missing_tool, missing_data, noop - GH_AW_PROMPT_f8c19ab5cd1cee06_EOF + GH_AW_PROMPT_008a45fba0979ffd_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_f8c19ab5cd1cee06_EOF' + cat << 'GH_AW_PROMPT_008a45fba0979ffd_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -242,9 +242,9 @@ jobs: {{/if}} - GH_AW_PROMPT_f8c19ab5cd1cee06_EOF + GH_AW_PROMPT_008a45fba0979ffd_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_f8c19ab5cd1cee06_EOF' + cat << 'GH_AW_PROMPT_008a45fba0979ffd_EOF' ## Serena Code Analysis @@ -283,7 +283,7 @@ jobs: {{#runtime-import .github/workflows/shared/mcp/serena-go.md}} {{#runtime-import .github/workflows/shared/noop-reminder.md}} {{#runtime-import .github/workflows/daily-compiler-quality.md}} - GH_AW_PROMPT_f8c19ab5cd1cee06_EOF + GH_AW_PROMPT_008a45fba0979ffd_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -520,9 +520,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_f4b4b62b017a06f7_EOF' - {"create_discussion":{"category":"audits","close_older_discussions":true,"expires":24,"fallback_to_issue":true,"max":1,"title_prefix":"[daily-compiler-quality] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_f4b4b62b017a06f7_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_8edcb29d7b09758d_EOF' + {"create_discussion":{"category":"audits","close_older_discussions":true,"expires":24,"fallback_to_issue":true,"max":1,"min_body_length":200,"title_prefix":"[daily-compiler-quality] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_8edcb29d7b09758d_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -706,7 +706,7 @@ jobs: export GH_AW_ENGINE="copilot" export GH_AW_MCP_CLI_SERVERS='["safeoutputs","serena"]' - echo 'GH_AW_MCP_CLI_SERVERS=["safeoutputs","serena"]' >> "$GITHUB_ENV" + echo GH_AW_MCP_CLI_SERVERS='["safeoutputs","serena"]' >> "$GITHUB_ENV" MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') @@ -714,7 +714,7 @@ 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_ab9d2d1b2af2d4cc_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_9369973ac67aa334_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "safeoutputs": { @@ -774,7 +774,7 @@ jobs: } } } - GH_AW_MCP_CONFIG_ab9d2d1b2af2d4cc_EOF + GH_AW_MCP_CONFIG_9369973ac67aa334_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -855,7 +855,7 @@ jobs: printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.42/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,"maxEffectiveTokens":10000000,"models":{"auto":["large"],"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-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"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*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"]}},"container":{"imageTag":"0.25.42"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json # 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" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GH_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 --difc-proxy-host host.docker.internal:18443 --difc-proxy-ca-cert /tmp/gh-aw/difc-proxy-tls/ca.crt \ - -- /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 || echo node)"; 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-tool github --allow-tool safeoutputs --allow-tool serena --allow-tool '\''shell(bc)'\'' --allow-tool '\''shell(cat /tmp/gh-aw/cache-memory/)'\'' --allow-tool '\''shell(cat > /tmp/gh-aw/cache-memory/)'\'' --allow-tool '\''shell(cat pkg/**/*.go)'\'' --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(find pkg -name)'\'' --allow-tool '\''shell(find pkg -type f -name)'\'' --allow-tool '\''shell(find pkg/ -maxdepth 1 -ls)'\'' --allow-tool '\''shell(find pkg/workflow -name)'\'' --allow-tool '\''shell(find pkg/workflow/ -maxdepth 1 -ls)'\'' --allow-tool '\''shell(gh:*)'\'' --allow-tool '\''shell(git log --since=)'\'' --allow-tool '\''shell(git log -1 --format=%H --)'\'' --allow-tool '\''shell(grep -r)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head -n * pkg/**/*.go)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(mkdir -p /tmp/gh-aw/cache-memory/compiler-quality)'\'' --allow-tool '\''shell(mv /tmp/gh-aw/cache-memory/)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(safeoutputs:*)'\'' --allow-tool '\''shell(serena:*)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc -l < pkg/workflow/)'\'' --allow-tool '\''shell(wc -l pkg/**/*.go)'\'' --allow-tool '\''shell(wc -l pkg/workflow/compiler*.go)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --add-dir /tmp/gh-aw/cache-memory/ --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 + -- /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-tool github --allow-tool safeoutputs --allow-tool serena --allow-tool '\''shell(bc)'\'' --allow-tool '\''shell(cat /tmp/gh-aw/cache-memory/)'\'' --allow-tool '\''shell(cat > /tmp/gh-aw/cache-memory/)'\'' --allow-tool '\''shell(cat pkg/**/*.go)'\'' --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(find pkg -name)'\'' --allow-tool '\''shell(find pkg -type f -name)'\'' --allow-tool '\''shell(find pkg/ -maxdepth 1 -ls)'\'' --allow-tool '\''shell(find pkg/workflow -name)'\'' --allow-tool '\''shell(find pkg/workflow/ -maxdepth 1 -ls)'\'' --allow-tool '\''shell(gh:*)'\'' --allow-tool '\''shell(git log --since=)'\'' --allow-tool '\''shell(git log -1 --format=%H --)'\'' --allow-tool '\''shell(grep -r)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head -n * pkg/**/*.go)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(mkdir -p /tmp/gh-aw/cache-memory/compiler-quality)'\'' --allow-tool '\''shell(mv /tmp/gh-aw/cache-memory/)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(safeoutputs:*)'\'' --allow-tool '\''shell(serena:*)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc -l < pkg/workflow/)'\'' --allow-tool '\''shell(wc -l pkg/**/*.go)'\'' --allow-tool '\''shell(wc -l pkg/workflow/compiler*.go)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --add-dir /tmp/gh-aw/cache-memory/ --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 @@ -1367,7 +1367,7 @@ jobs: printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.42/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,"maxEffectiveTokens":10000000},"container":{"imageTag":"0.25.42"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json # 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" --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 || echo node)"; 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 + -- /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 @@ -1514,7 +1514,7 @@ jobs: 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" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":24,\"fallback_to_issue\":true,\"max\":1,\"title_prefix\":\"[daily-compiler-quality] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":24,\"fallback_to_issue\":true,\"max\":1,\"min_body_length\":200,\"title_prefix\":\"[daily-compiler-quality] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/daily-compiler-quality.md b/.github/workflows/daily-compiler-quality.md index a59dff32192..e1cefee1a74 100644 --- a/.github/workflows/daily-compiler-quality.md +++ b/.github/workflows/daily-compiler-quality.md @@ -39,6 +39,15 @@ tools: - "mv /tmp/gh-aw/cache-memory/" - "echo" - "bc" +safe-outputs: + create-discussion: + category: "audits" + title-prefix: "[daily-compiler-quality] " + expires: 1d + close-older-discussions: true + fallback-to-issue: true + max: 1 + min-body-length: 200 timeout-minutes: 30 strict: true features: @@ -314,6 +323,13 @@ Compare current analysis with previous analyses: Generate a comprehensive discussion report with findings. +### Output Contract (Required) + +1. Emit **exactly one** `create_discussion` safe-output item. +2. Do **not** emit placeholder or draft bodies (for example: `test`, `.`, `todo`, or similar short placeholders). +3. Only emit `create_discussion` after the final report body is complete and fully rendered. +4. The workflow enforces a **minimum 200-character body length**, so very short outputs (placeholder or otherwise) will fail safe-outputs. + ### Discussion Title ``` diff --git a/actions/setup/js/create_discussion.cjs b/actions/setup/js/create_discussion.cjs index 94ba26308ed..f0717d3c3ac 100644 --- a/actions/setup/js/create_discussion.cjs +++ b/actions/setup/js/create_discussion.cjs @@ -295,6 +295,7 @@ async function main(config = {}) { const configCategory = config.category || ""; const maxCount = config.max || 10; const expiresHours = config.expires ? parseInt(String(config.expires), 10) : 0; + const minBodyLength = config.min_body_length ? parseInt(String(config.min_body_length), 10) : 0; const fallbackToIssue = config.fallback_to_issue !== false; // Default to true const closeOlderDiscussionsEnabled = parseBoolTemplatable(config.close_older_discussions, false); const rawCloseOlderKey = config.close_older_key ? String(config.close_older_key) : ""; @@ -302,6 +303,9 @@ async function main(config = {}) { if (rawCloseOlderKey && !closeOlderKey) { throw new Error(`${ERR_VALIDATION}: close-older-key "${rawCloseOlderKey}" is invalid: it must contain at least one alphanumeric character after normalization`); } + if (isNaN(minBodyLength) || minBodyLength < 0) { + throw new Error(`${ERR_VALIDATION}: min_body_length must be a non-negative integer (got: ${config.min_body_length})`); + } const includeFooter = parseBoolTemplatable(config.footer, true); // Create an authenticated GitHub client. Uses config["github-token"] when set @@ -321,6 +325,9 @@ async function main(config = {}) { .filter(l => l.length > 0); core.info(`Create discussion configuration: max=${maxCount}`); + if (minBodyLength > 0) { + core.info(`Minimum discussion body length guard enabled: ${minBodyLength}`); + } core.info(`Default target repo: ${defaultTargetRepo}`); if (allowedRepos.size > 0) { core.info(`Allowed repos: ${Array.from(allowedRepos).join(", ")}`); @@ -484,9 +491,18 @@ async function main(config = {}) { let title = item.title ? item.title.trim() : ""; let processedBody = replaceTemporaryIdReferences(item.body || "", temporaryIdMap, qualifiedItemRepo); processedBody = removeDuplicateTitleFromDescription(title, processedBody); + const preSanitizeBodyLength = processedBody.trim().length; // Sanitize body content to neutralize @mentions, URLs, and other security risks processedBody = sanitizeContent(processedBody); + if (minBodyLength > 0 && preSanitizeBodyLength < minBodyLength) { + const error = `Discussion body length ${preSanitizeBodyLength} is below configured minimum ${minBodyLength}`; + core.error(error); + return { + success: false, + error, + }; + } if (!title) { title = item.body || "Discussion"; diff --git a/actions/setup/js/create_discussion_sanitization.test.cjs b/actions/setup/js/create_discussion_sanitization.test.cjs index 2f806b855d7..979e0a677b5 100644 --- a/actions/setup/js/create_discussion_sanitization.test.cjs +++ b/actions/setup/js/create_discussion_sanitization.test.cjs @@ -169,4 +169,21 @@ describe("create_discussion body sanitization", () => { // System-generated footer marker must still be present expect(body).toContain("gh-aw-workflow-id"); }); + + it("should fail when body is below configured minimum length", async () => { + const handler = await createDiscussionMain({ max: 5, category: "general", min_body_length: 200 }); + const result = await handler( + { + title: "Too short", + body: "test", + }, + {} + ); + + expect(result.success).toBe(false); + expect(result.error).toContain("below configured minimum 200"); + + const createMutationCall = mockGithub.graphql.mock.calls.find(call => call[0].includes("createDiscussion")); + expect(createMutationCall).toBeUndefined(); + }); }); diff --git a/docs/src/content/docs/reference/frontmatter-full.md b/docs/src/content/docs/reference/frontmatter-full.md index 69a370b3d2c..deb02f0f56c 100644 --- a/docs/src/content/docs/reference/frontmatter-full.md +++ b/docs/src/content/docs/reference/frontmatter-full.md @@ -3631,6 +3631,12 @@ safe-outputs: # (optional) category: null + # Minimum required length of the discussion body content (before + # footer/metadata) in characters. If a create_discussion message body is + # shorter than this value, the safe-outputs job fails. + # (optional) + min-body-length: 200 + # Optional list of labels to attach to created discussions. Also used for matching # when close-older-discussions is enabled - discussions must have ALL specified # labels (AND logic). diff --git a/docs/src/content/docs/reference/safe-outputs-specification.md b/docs/src/content/docs/reference/safe-outputs-specification.md index 4447d784981..c51eae9faee 100644 --- a/docs/src/content/docs/reference/safe-outputs-specification.md +++ b/docs/src/content/docs/reference/safe-outputs-specification.md @@ -1612,6 +1612,7 @@ upload-asset: create-discussion: category: "General" # Discussion category (name/slug/ID) title-prefix: "[Report] " # Prepend to titles + min-body-length: 200 # Optional minimum report body length labels: [report, automated] # Auto-apply labels allowed-labels: [...] # Agent label restrictions ``` @@ -2654,11 +2655,14 @@ This section provides complete definitions for all remaining safe output types. 3. **Footer Injection**: Appends attribution footer to the discussion body when configured. 4. **Cross-Repository**: When `target-repo` is configured, creates in that repository (must be in `allowed-repos`). 5. **Temporary ID Support**: Supports `temporary_id` field for referencing before creation. +6. **Body Length Guard**: When `min-body-length` is configured, discussion creation is rejected if the body is shorter than the configured minimum. **Configuration Parameters**: - `max`: Operation limit (default: 1) - `category`: Default discussion category +- `title-prefix`: Prepend to titles +- `min-body-length`: Minimum required body length (characters, before footer/metadata) - `target-repo`: Cross-repository target - `allowed-repos`: Cross-repo allowlist - `footer`: Footer override diff --git a/docs/src/content/docs/reference/safe-outputs.md b/docs/src/content/docs/reference/safe-outputs.md index 17af5ea9f5d..1865a9468e4 100644 --- a/docs/src/content/docs/reference/safe-outputs.md +++ b/docs/src/content/docs/reference/safe-outputs.md @@ -1013,6 +1013,7 @@ safe-outputs: create-discussion: title-prefix: "[ai] " # prefix for titles category: "announcements" # category slug, name, or ID (use lowercase) + min-body-length: 200 # optional minimum body length guard (fails safe-outputs job if shorter) expires: 3 # auto-close after 3 days (or false to disable) max: 3 # max discussions (default: 1) target-repo: "owner/repo" # cross-repository @@ -1021,6 +1022,8 @@ safe-outputs: github-token: ${{ secrets.SOME_CUSTOM_TOKEN }} # optional custom token for permissions ``` +Use `min-body-length` when you want a hard floor for report quality (for example, to prevent accidental placeholder bodies like `test` from being posted). + #### Fallback to Issue Creation The `fallback-to-issue` field (default: `true`) automatically falls back to creating an issue when discussion creation fails (e.g., discussions disabled, insufficient `discussions: write` permissions, or org policy restrictions). The issue body notes it was intended to be a discussion. Set to `false` to fail instead of falling back. diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index dcdfe276e50..aa91bf2be95 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -5422,6 +5422,11 @@ "description": "Optional discussion category. Can be a category ID (string or numeric value), category name, or category slug/route. If not specified, uses the first available category. Matched first against category IDs, then against category names, then against category slugs. Numeric values are automatically converted to strings at runtime.", "examples": ["General", "audits", 123456789] }, + "min-body-length": { + "type": "integer", + "minimum": 1, + "description": "Minimum required length of the discussion body content (before footer/metadata) in characters. If a create_discussion message body is shorter than this value, the safe-outputs job fails." + }, "labels": { "type": "array", "items": { diff --git a/pkg/workflow/compiler_safe_outputs_config_test.go b/pkg/workflow/compiler_safe_outputs_config_test.go index bad798f3dbb..92176f0b239 100644 --- a/pkg/workflow/compiler_safe_outputs_config_test.go +++ b/pkg/workflow/compiler_safe_outputs_config_test.go @@ -1138,6 +1138,17 @@ func TestHandlerConfigBooleanFields(t *testing.T) { checkKey: "draft", expected: true, // AddTemplatableBool converts "true" string to JSON boolean }, + { + name: "create discussion minimum body length", + safeOutputs: &SafeOutputsConfig{ + CreateDiscussions: &CreateDiscussionsConfig{ + MinBodyLength: 200, + }, + }, + checkField: "create_discussion", + checkKey: "min_body_length", + expected: float64(200), + }, } for _, tt := range tests { diff --git a/pkg/workflow/compiler_safe_outputs_handlers.go b/pkg/workflow/compiler_safe_outputs_handlers.go index 92dbb2ca749..74be7eaabb7 100644 --- a/pkg/workflow/compiler_safe_outputs_handlers.go +++ b/pkg/workflow/compiler_safe_outputs_handlers.go @@ -69,6 +69,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddTemplatableInt("max", c.Max). AddIfNotEmpty("category", c.Category). AddIfNotEmpty("title_prefix", c.TitlePrefix). + AddIfPositive("min_body_length", c.MinBodyLength). AddStringSlice("labels", c.Labels). AddStringSlice("allowed_labels", c.AllowedLabels). AddStringSlice("allowed_repos", c.AllowedRepos). diff --git a/pkg/workflow/create_discussion.go b/pkg/workflow/create_discussion.go index c5bfbf9c62c..da66c157b04 100644 --- a/pkg/workflow/create_discussion.go +++ b/pkg/workflow/create_discussion.go @@ -15,6 +15,7 @@ type CreateDiscussionsConfig struct { BaseSafeOutputConfig `yaml:",inline"` TitlePrefix string `yaml:"title-prefix,omitempty"` Category string `yaml:"category,omitempty"` // Discussion category ID or name + MinBodyLength int `yaml:"min-body-length,omitempty"` // Minimum required discussion body length before footer/markers Labels []string `yaml:"labels,omitempty"` // Labels to attach to discussions and match when closing older ones AllowedLabels []string `yaml:"allowed-labels,omitempty"` // Optional list of allowed labels. If omitted, any labels are allowed (including creating new ones). TargetRepoSlug string `yaml:"target-repo,omitempty"` // Target repository in format "owner/repo" for cross-repository discussions