diff --git a/.github/workflows/blog-auditor.lock.yml b/.github/workflows/blog-auditor.lock.yml index 9d8842be8fc..1cf8325d083 100644 --- a/.github/workflows/blog-auditor.lock.yml +++ b/.github/workflows/blog-auditor.lock.yml @@ -429,7 +429,7 @@ jobs: - name: Install Claude Code CLI run: npm install -g @anthropic-ai/claude-code@2.1.133 - name: Install Playwright CLI - run: npm install -g @playwright/cli@0.1.11 + run: npm install -g @playwright/cli@0.1.13 - name: Install Playwright CLI skills run: playwright-cli install --skills - name: Determine automatic lockdown mode for GitHub MCP Server diff --git a/.github/workflows/cgo.yml b/.github/workflows/cgo.yml index d6843876f6c..a29d4aff783 100644 --- a/.github/workflows/cgo.yml +++ b/.github/workflows/cgo.yml @@ -1024,10 +1024,15 @@ jobs: run_fuzz_test() { local fuzz_name=$1 local package=$2 + local tags_arg="" local output_file="fuzz-results/${fuzz_name}.txt" + + if [ "${fuzz_name}" = "FuzzSanitizeOutput" ] || [ "${fuzz_name}" = "FuzzSanitizeIncomingText" ]; then + tags_arg="-tags=integration" + fi echo "Running ${fuzz_name}..." - if go test -run='^$' -fuzz="^${fuzz_name}$" -fuzztime=10s "${package}" 2>&1 | tee "${output_file}"; then + if go test -run='^$' -fuzz="^${fuzz_name}$" -fuzztime=10s ${tags_arg:+"${tags_arg}"} "${package}" 2>&1 | tee "${output_file}"; then echo "✅ ${fuzz_name} completed successfully" return 0 else @@ -1718,6 +1723,7 @@ jobs: const failedJobs = Object.entries(needs) .filter(([, job]) => job.result === 'failure') .map(([name]) => name); + const hasFuzzFailure = failedJobs.some(name => name === 'fuzz' || name.startsWith('fuzz-')); if (failedJobs.length === 0) { core.info('No jobs failed. Nothing to do.'); @@ -1734,13 +1740,35 @@ jobs: state: 'open', }); - if (existingIssues.data.length > 0) { + if (existingIssues.data.length > 0 && !hasFuzzFailure) { core.info(`Existing CGO failure issue #${existingIssues.data[0].number} is still open. Skipping.`); return; } + if (hasFuzzFailure) { + const existingFuzzIssues = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + labels: 'cgo-fuzz-failure', + state: 'open', + }); + + const existingFuzzIssueForRun = existingFuzzIssues.data.find(issue => + issue.title.includes(`Run #${context.runNumber}`), + ); + if (existingFuzzIssueForRun) { + core.info(`Fuzz failure issue #${existingFuzzIssueForRun.number} already exists for run #${context.runNumber}. Skipping.`); + return; + } + } + // Ensure required labels exist, creating them if missing - for (const [label, color] of [['cookie', 'e4e669'], ['cgo-failure', 'b60205']]) { + const requiredLabels = [['cookie', 'e4e669'], ['cgo-failure', 'b60205']]; + if (hasFuzzFailure) { + requiredLabels.push(['cgo-fuzz-failure', 'd93f0b']); + } + + for (const [label, color] of requiredLabels) { try { await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label }); } catch (e) { @@ -1798,14 +1826,32 @@ jobs: ``, `> This issue expires at ${expiresAt}. Please investigate the failed jobs above and close once resolved.`, `> ${expirationLine}`, - ].join('\n'); + ]; + + if (hasFuzzFailure) { + body.splice( + 4, + 0, + `Detected failure in the \`fuzz\` job matrix. A dedicated fuzz failure label was applied so this run is tracked.`, + ``, + ); + } + + const issueBody = body.join('\n'); + + const issueLabels = ['cookie', 'cgo-failure']; + if (hasFuzzFailure) { + issueLabels.push('cgo-fuzz-failure'); + } const issue = await github.rest.issues.create({ owner: context.repo.owner, repo: context.repo.repo, - title: `[CGO] Workflow failure on main - Run #${context.runNumber}`, - body, - labels: ['cookie', 'cgo-failure'], + title: hasFuzzFailure + ? `[CGO][FUZZ] Workflow failure on main - Run #${context.runNumber}` + : `[CGO] Workflow failure on main - Run #${context.runNumber}`, + body: issueBody, + labels: issueLabels, }); core.info(`Created issue #${issue.data.number}: ${issue.data.html_url}`); diff --git a/.github/workflows/cloclo.lock.yml b/.github/workflows/cloclo.lock.yml index 83d759af471..209efd84985 100644 --- a/.github/workflows/cloclo.lock.yml +++ b/.github/workflows/cloclo.lock.yml @@ -638,7 +638,7 @@ jobs: - name: Install Claude Code CLI run: npm install -g @anthropic-ai/claude-code@2.1.133 - name: Install Playwright CLI - run: npm install -g @playwright/cli@0.1.11 + run: npm install -g @playwright/cli@0.1.13 - name: Install Playwright CLI skills run: playwright-cli install --skills - name: Determine automatic lockdown mode for GitHub MCP Server diff --git a/.github/workflows/copilot-pr-nlp-analysis.md b/.github/workflows/copilot-pr-nlp-analysis.md index be76fb470d4..37d7c599ade 100644 --- a/.github/workflows/copilot-pr-nlp-analysis.md +++ b/.github/workflows/copilot-pr-nlp-analysis.md @@ -88,6 +88,12 @@ Generate a daily NLP-based analysis report of Copilot-created PRs merged within - **Python Environment**: NumPy, Pandas, Matplotlib, Seaborn, SciPy, NLTK, scikit-learn, TextBlob, WordCloud - **Output Directory**: `/tmp/gh-aw/python/charts/` +### Runtime Constraint (Required) + +- Python analysis dependencies are already installed by pre-agent workflow steps. +- **Do NOT run any `pip install` commands in agent turns.** +- Run Python scripts with `/tmp/gh-aw/venv/bin/python3` to use the preinstalled environment. + ## Task Overview ### Phase 1: Load and Parse PR Conversation Data diff --git a/.github/workflows/daily-cache-strategy-analyzer.lock.yml b/.github/workflows/daily-cache-strategy-analyzer.lock.yml index f7b15bd2e50..58d7af0e968 100644 --- a/.github/workflows/daily-cache-strategy-analyzer.lock.yml +++ b/.github/workflows/daily-cache-strategy-analyzer.lock.yml @@ -1432,18 +1432,18 @@ jobs: DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 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 /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_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 CODEX_HOME -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.6' - cat > "${RUNNER_TEMP}/gh-aw/mcp-config/config.toml" << GH_AW_MCP_CONFIG_32dabedd72663b43_EOF + cat > "${RUNNER_TEMP}/gh-aw/mcp-config/config.toml" << GH_AW_MCP_CONFIG_f4b14640acf27bb0_EOF [history] persistence = "none" [shell_environment_policy] inherit = "core" include_only = ["CODEX_API_KEY", "HOME", "OPENAI_API_KEY", "PATH"] - GH_AW_MCP_CONFIG_32dabedd72663b43_EOF + GH_AW_MCP_CONFIG_f4b14640acf27bb0_EOF # Generate JSON config for MCP gateway GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_c449ad4347f97c5d_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_7e52e98a868362b7_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { }, @@ -1454,11 +1454,11 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_c449ad4347f97c5d_EOF + GH_AW_MCP_CONFIG_7e52e98a868362b7_EOF # Sync converter output to writable CODEX_HOME for Codex mkdir -p /tmp/gh-aw/mcp-config - cat > "/tmp/gh-aw/mcp-config/config.toml" << GH_AW_CODEX_SHELL_POLICY_dc42fa9ef6ebd3a8_EOF + cat > "/tmp/gh-aw/mcp-config/config.toml" << GH_AW_CODEX_SHELL_POLICY_5aa7a18128301d1a_EOF model_provider = "openai-proxy" [model_providers.openai-proxy] name = "OpenAI AWF proxy" @@ -1468,7 +1468,7 @@ jobs: [shell_environment_policy] inherit = "core" include_only = ["CODEX_API_KEY", "HOME", "OPENAI_API_KEY", "PATH"] - GH_AW_CODEX_SHELL_POLICY_dc42fa9ef6ebd3a8_EOF + GH_AW_CODEX_SHELL_POLICY_5aa7a18128301d1a_EOF awk ' BEGIN { skip_openai_proxy = 0 } /^[[:space:]]*model_provider[[:space:]]*=/ { next } diff --git a/.github/workflows/daily-fact.lock.yml b/.github/workflows/daily-fact.lock.yml index 00ee99bdbc6..8bd6b8db35d 100644 --- a/.github/workflows/daily-fact.lock.yml +++ b/.github/workflows/daily-fact.lock.yml @@ -1437,18 +1437,18 @@ jobs: DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 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 /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_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 CODEX_HOME -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.6' - cat > "${RUNNER_TEMP}/gh-aw/mcp-config/config.toml" << GH_AW_MCP_CONFIG_5d28eb3912dd2d50_EOF + cat > "${RUNNER_TEMP}/gh-aw/mcp-config/config.toml" << GH_AW_MCP_CONFIG_410f713d8f2aaa9b_EOF [history] persistence = "none" [shell_environment_policy] inherit = "core" include_only = ["CODEX_API_KEY", "HOME", "OPENAI_API_KEY", "PATH"] - GH_AW_MCP_CONFIG_5d28eb3912dd2d50_EOF + GH_AW_MCP_CONFIG_410f713d8f2aaa9b_EOF # Generate JSON config for MCP gateway GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_6518eed655bd734f_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_d3c7d393eba4c52b_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { }, @@ -1459,11 +1459,11 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_6518eed655bd734f_EOF + GH_AW_MCP_CONFIG_d3c7d393eba4c52b_EOF # Sync converter output to writable CODEX_HOME for Codex mkdir -p /tmp/gh-aw/mcp-config - cat > "/tmp/gh-aw/mcp-config/config.toml" << GH_AW_CODEX_SHELL_POLICY_5ce0d08e0c625ba3_EOF + cat > "/tmp/gh-aw/mcp-config/config.toml" << GH_AW_CODEX_SHELL_POLICY_e69a73e35502ba20_EOF model_provider = "openai-proxy" [model_providers.openai-proxy] name = "OpenAI AWF proxy" @@ -1473,7 +1473,7 @@ jobs: [shell_environment_policy] inherit = "core" include_only = ["CODEX_API_KEY", "HOME", "OPENAI_API_KEY", "PATH"] - GH_AW_CODEX_SHELL_POLICY_5ce0d08e0c625ba3_EOF + GH_AW_CODEX_SHELL_POLICY_e69a73e35502ba20_EOF awk ' BEGIN { skip_openai_proxy = 0 } /^[[:space:]]*model_provider[[:space:]]*=/ { next } diff --git a/.github/workflows/daily-model-inventory.lock.yml b/.github/workflows/daily-model-inventory.lock.yml index 3245a99edda..f99be7c9ad2 100644 --- a/.github/workflows/daily-model-inventory.lock.yml +++ b/.github/workflows/daily-model-inventory.lock.yml @@ -444,7 +444,7 @@ jobs: - name: Install AWF binary run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 - name: Install Playwright CLI - run: npm install -g @playwright/cli@0.1.11 + run: npm install -g @playwright/cli@0.1.13 - name: Install Playwright CLI skills run: playwright-cli install --skills - name: Determine automatic lockdown mode for GitHub MCP Server diff --git a/.github/workflows/daily-multi-device-docs-tester.lock.yml b/.github/workflows/daily-multi-device-docs-tester.lock.yml index d8fb76cc683..f8e61324dfb 100644 --- a/.github/workflows/daily-multi-device-docs-tester.lock.yml +++ b/.github/workflows/daily-multi-device-docs-tester.lock.yml @@ -441,7 +441,7 @@ jobs: - name: Install Claude Code CLI run: npm install -g @anthropic-ai/claude-code@2.1.133 - name: Install Playwright CLI - run: npm install -g @playwright/cli@0.1.11 + run: npm install -g @playwright/cli@0.1.13 - name: Install Playwright CLI skills run: playwright-cli install --skills - name: Determine automatic lockdown mode for GitHub MCP Server diff --git a/.github/workflows/daily-observability-report.lock.yml b/.github/workflows/daily-observability-report.lock.yml index 1526036b846..b7751cbc744 100644 --- a/.github/workflows/daily-observability-report.lock.yml +++ b/.github/workflows/daily-observability-report.lock.yml @@ -1352,18 +1352,18 @@ jobs: DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 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 /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_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 CODEX_HOME -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.6' - cat > "${RUNNER_TEMP}/gh-aw/mcp-config/config.toml" << GH_AW_MCP_CONFIG_a9213838d68319fe_EOF + cat > "${RUNNER_TEMP}/gh-aw/mcp-config/config.toml" << GH_AW_MCP_CONFIG_a30ed2973e6aeea5_EOF [history] persistence = "none" [shell_environment_policy] inherit = "core" include_only = ["CODEX_API_KEY", "HOME", "OPENAI_API_KEY", "PATH"] - GH_AW_MCP_CONFIG_a9213838d68319fe_EOF + GH_AW_MCP_CONFIG_a30ed2973e6aeea5_EOF # Generate JSON config for MCP gateway GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_43a2d3f74066392f_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_f885ef37400d92d1_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { }, @@ -1374,11 +1374,11 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_43a2d3f74066392f_EOF + GH_AW_MCP_CONFIG_f885ef37400d92d1_EOF # Sync converter output to writable CODEX_HOME for Codex mkdir -p /tmp/gh-aw/mcp-config - cat > "/tmp/gh-aw/mcp-config/config.toml" << GH_AW_CODEX_SHELL_POLICY_914144eab4ea4ebe_EOF + cat > "/tmp/gh-aw/mcp-config/config.toml" << GH_AW_CODEX_SHELL_POLICY_6cbf8a0c4229c128_EOF model_provider = "openai-proxy" [model_providers.openai-proxy] name = "OpenAI AWF proxy" @@ -1388,7 +1388,7 @@ jobs: [shell_environment_policy] inherit = "core" include_only = ["CODEX_API_KEY", "HOME", "OPENAI_API_KEY", "PATH"] - GH_AW_CODEX_SHELL_POLICY_914144eab4ea4ebe_EOF + GH_AW_CODEX_SHELL_POLICY_6cbf8a0c4229c128_EOF awk ' BEGIN { skip_openai_proxy = 0 } /^[[:space:]]*model_provider[[:space:]]*=/ { next } diff --git a/.github/workflows/docs-noob-tester.lock.yml b/.github/workflows/docs-noob-tester.lock.yml index 492dd76a817..6ed3038e47a 100644 --- a/.github/workflows/docs-noob-tester.lock.yml +++ b/.github/workflows/docs-noob-tester.lock.yml @@ -431,7 +431,7 @@ jobs: - name: Install AWF binary run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 - name: Install Playwright CLI - run: npm install -g @playwright/cli@0.1.11 + run: npm install -g @playwright/cli@0.1.13 - name: Install Playwright CLI skills run: playwright-cli install --skills - name: Determine automatic lockdown mode for GitHub MCP Server diff --git a/.github/workflows/duplicate-code-detector.lock.yml b/.github/workflows/duplicate-code-detector.lock.yml index 5407186a76c..d5431e04104 100644 --- a/.github/workflows/duplicate-code-detector.lock.yml +++ b/.github/workflows/duplicate-code-detector.lock.yml @@ -1352,18 +1352,18 @@ jobs: DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 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 /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_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 CODEX_HOME -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.6' - cat > "${RUNNER_TEMP}/gh-aw/mcp-config/config.toml" << GH_AW_MCP_CONFIG_3d0e505e5ef6884d_EOF + cat > "${RUNNER_TEMP}/gh-aw/mcp-config/config.toml" << GH_AW_MCP_CONFIG_406d692335f70178_EOF [history] persistence = "none" [shell_environment_policy] inherit = "core" include_only = ["CODEX_API_KEY", "HOME", "OPENAI_API_KEY", "PATH"] - GH_AW_MCP_CONFIG_3d0e505e5ef6884d_EOF + GH_AW_MCP_CONFIG_406d692335f70178_EOF # Generate JSON config for MCP gateway GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_0659a8f17ff20b43_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_650acdba67caf6c0_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { }, @@ -1374,11 +1374,11 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_0659a8f17ff20b43_EOF + GH_AW_MCP_CONFIG_650acdba67caf6c0_EOF # Sync converter output to writable CODEX_HOME for Codex mkdir -p /tmp/gh-aw/mcp-config - cat > "/tmp/gh-aw/mcp-config/config.toml" << GH_AW_CODEX_SHELL_POLICY_065ae57d3eb60ac3_EOF + cat > "/tmp/gh-aw/mcp-config/config.toml" << GH_AW_CODEX_SHELL_POLICY_66388c10a0ad5240_EOF model_provider = "openai-proxy" [model_providers.openai-proxy] name = "OpenAI AWF proxy" @@ -1388,7 +1388,7 @@ jobs: [shell_environment_policy] inherit = "core" include_only = ["CODEX_API_KEY", "HOME", "OPENAI_API_KEY", "PATH"] - GH_AW_CODEX_SHELL_POLICY_065ae57d3eb60ac3_EOF + GH_AW_CODEX_SHELL_POLICY_66388c10a0ad5240_EOF awk ' BEGIN { skip_openai_proxy = 0 } /^[[:space:]]*model_provider[[:space:]]*=/ { next } diff --git a/.github/workflows/q.lock.yml b/.github/workflows/q.lock.yml index c1497d38072..edc4b6a6b71 100644 --- a/.github/workflows/q.lock.yml +++ b/.github/workflows/q.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"e20e4680d6c18ac5c6ab50dcb79dbe7e4683e51c8481fa860052facd163d8e78","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","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"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-go","sha":"4a3601121dd01d1626a1e23e37211e3254c1c06c","version":"v6.4.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"docker/build-push-action","sha":"bcafcacb16a39f128d818304e6c9c0c18556b85f","version":"v7.1.0"},{"repo":"docker/setup-buildx-action","sha":"4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd","version":"v4.0.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41","digest":"sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41","digest":"sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.41","digest":"sha256:62171f2fa508667b8b0a9e096f826983f312e3da0ce894f80c0f83a875af60fe","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.41@sha256:62171f2fa508667b8b0a9e096f826983f312e3da0ce894f80c0f83a875af60fe"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41","digest":"sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4"},{"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"}]} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"564ebae0c9b91f61c018f3e4d9d8e3490fc8e5433f9e76b833a0aa1897126818","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","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"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-go","sha":"4a3601121dd01d1626a1e23e37211e3254c1c06c","version":"v6.4.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"docker/build-push-action","sha":"bcafcacb16a39f128d818304e6c9c0c18556b85f","version":"v7.1.0"},{"repo":"docker/setup-buildx-action","sha":"4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd","version":"v4.0.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41","digest":"sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41","digest":"sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.41","digest":"sha256:62171f2fa508667b8b0a9e096f826983f312e3da0ce894f80c0f83a875af60fe","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.41@sha256:62171f2fa508667b8b0a9e096f826983f312e3da0ce894f80c0f83a875af60fe"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41","digest":"sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4"},{"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":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -26,8 +26,6 @@ # # Resolved workflow manifest: # Imports: -# - shared/mcp/serena-go.md -# - shared/mcp/serena.md # - shared/observability-otlp.md # # Secrets used: @@ -56,7 +54,6 @@ # - ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4 # - ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c # - ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 -# - ghcr.io/github/serena-mcp-server:latest@sha256:bf343399e3725c45528f531a230f3a04521d4cdef29f9a5af6282ff0d3c393c5 # - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f name: "Q" @@ -272,24 +269,24 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_586784a0a8163e50_EOF' + cat << 'GH_AW_PROMPT_fa479903c98928c5_EOF' - GH_AW_PROMPT_586784a0a8163e50_EOF + GH_AW_PROMPT_fa479903c98928c5_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/agentic_workflows_guide.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_586784a0a8163e50_EOF' + cat << 'GH_AW_PROMPT_fa479903c98928c5_EOF' Tools: add_comment, create_pull_request, add_labels, missing_tool, missing_data, noop - GH_AW_PROMPT_586784a0a8163e50_EOF + GH_AW_PROMPT_fa479903c98928c5_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_586784a0a8163e50_EOF' + cat << 'GH_AW_PROMPT_fa479903c98928c5_EOF' - GH_AW_PROMPT_586784a0a8163e50_EOF + GH_AW_PROMPT_fa479903c98928c5_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_586784a0a8163e50_EOF' + cat << 'GH_AW_PROMPT_fa479903c98928c5_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -318,56 +315,23 @@ jobs: {{/if}} - GH_AW_PROMPT_586784a0a8163e50_EOF + GH_AW_PROMPT_fa479903c98928c5_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_with_safeoutputs_prompt.md" if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" fi - cat << 'GH_AW_PROMPT_586784a0a8163e50_EOF' + cat << 'GH_AW_PROMPT_fa479903c98928c5_EOF' - ## Serena Code Analysis - - The Serena MCP server is configured for **["go"]** analysis in this workspace: - - **Workspace**: `__GH_AW_GITHUB_WORKSPACE__` - - **Memory**: `/tmp/gh-aw/cache-memory/serena/` - - ### Project Activation - - Before analyzing code, activate the Serena project: - ``` - Tool: activate_project - Args: { "path": "__GH_AW_GITHUB_WORKSPACE__" } - ``` - - ### Available Capabilities - - Serena provides IDE-grade Language Server Protocol (LSP) tools including: - - **Symbol search**: `find_symbol` — locate functions, types, interfaces by name - - **Navigation**: `find_referencing_symbols` — find all callers/usages of a symbol - - **Type info**: `get_symbol_documentation` — hover-level type and doc information - - **Code editing**: `replace_symbol_body`, `insert_after_symbol` — symbol-level edits - - **Diagnostics**: `get_diagnostics` — compiler errors and linter warnings - - ### Analysis Guidelines - - 1. **Use semantic tools over text search** — prefer Serena's LSP tools over `grep` - 2. **Activate project first** — always call `activate_project` before other tools - 3. **Cross-reference findings** — validate with multiple tools for accuracy - 4. **Focus on the relevant language files** — ignore unrelated file types - - - {{#runtime-import .github/workflows/shared/mcp/serena-go.md}} {{#runtime-import .github/workflows/shared/observability-otlp.md}} {{#runtime-import .github/workflows/shared/noop-reminder.md}} {{#runtime-import .github/workflows/q.md}} - GH_AW_PROMPT_586784a0a8163e50_EOF + GH_AW_PROMPT_fa479903c98928c5_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_GITHUB_WORKSPACE: ${{ github.workspace }} GH_AW_GITHUB_ACTOR: ${{ github.actor }} GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} GH_AW_EXPR_799BE623: ${{ github.event.issue.number || github.event.pull_request.number }} @@ -395,7 +359,7 @@ jobs: GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} GH_AW_IS_PR_COMMENT: ${{ github.event.issue.pull_request && 'true' || '' }} - GH_AW_MCP_CLI_SERVERS_LIST: "- `agenticworkflows` — run `agenticworkflows --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools\n- `serena` — run `serena --help` to see available tools" + GH_AW_MCP_CLI_SERVERS_LIST: "- `agenticworkflows` — run `agenticworkflows --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools" GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_MATCHED_COMMAND: ${{ needs.pre_activation.outputs.matched_command }} GH_AW_STEPS_SANITIZED_OUTPUTS_TEXT: ${{ steps.sanitized.outputs.text }} @@ -607,7 +571,7 @@ jobs: GH_AW_SUB_AGENT_EXT: ".agent.md" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.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.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0 ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.41@sha256:62171f2fa508667b8b0a9e096f826983f312e3da0ce894f80c0f83a875af60fe ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4 ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 ghcr.io/github/serena-mcp-server:latest@sha256:bf343399e3725c45528f531a230f3a04521d4cdef29f9a5af6282ff0d3c393c5 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0 ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.41@sha256:62171f2fa508667b8b0a9e096f826983f312e3da0ce894f80c0f83a875af60fe ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4 ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f - name: Install gh-aw extension env: GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -641,9 +605,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_3bc3808e21e0a637_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_a36c348e3b07cfb5_EOF' {"add_comment":{"max":1},"add_labels":{"allowed":["spam"]},"create_pull_request":{"draft":false,"expires":48,"if_no_changes":"ignore","labels":["automation","workflow-optimization"],"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","reviewers":["copilot"],"title_prefix":"[q] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_3bc3808e21e0a637_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_a36c348e3b07cfb5_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -885,8 +849,8 @@ jobs: export DEBUG="*" export GH_AW_ENGINE="copilot" - export GH_AW_MCP_CLI_SERVERS='["agenticworkflows","safeoutputs","serena"]' - echo 'GH_AW_MCP_CLI_SERVERS=["agenticworkflows","safeoutputs","serena"]' >> "$GITHUB_ENV" + export GH_AW_MCP_CLI_SERVERS='["agenticworkflows","safeoutputs"]' + echo 'GH_AW_MCP_CLI_SERVERS=["agenticworkflows","safeoutputs"]' >> "$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') @@ -894,7 +858,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_b0ce79b95b8f7591_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_5fbd8a22ac4f9f88_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "agenticworkflows": { @@ -929,35 +893,6 @@ jobs: ] } } - }, - "serena": { - "type": "stdio", - "container": "ghcr.io/github/serena-mcp-server:latest", - "entrypoint": "serena", - "entrypointArgs": [ - "start-mcp-server", - "--context", - "codex", - "--project", - "\${GITHUB_WORKSPACE}" - ], - "mounts": [ - "\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw" - ], - "args": [ - "--network", - "host" - ], - "tools": [ - "*" - ], - "guard-policies": { - "write-sink": { - "accept": [ - "*" - ] - } - } } }, "gateway": { @@ -973,7 +908,7 @@ jobs: } } } - GH_AW_MCP_CONFIG_b0ce79b95b8f7591_EOF + GH_AW_MCP_CONFIG_5fbd8a22ac4f9f88_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true diff --git a/.github/workflows/q.md b/.github/workflows/q.md index ec7c0b038a4..43e4fc77831 100644 --- a/.github/workflows/q.md +++ b/.github/workflows/q.md @@ -15,7 +15,6 @@ permissions: discussions: read engine: copilot imports: - - shared/mcp/serena-go.md - shared/observability-otlp.md tools: cli-proxy: true @@ -57,6 +56,8 @@ strict: true You are Q, the quartermaster of agentic workflows - an expert system that improves, optimizes, and fixes agentic workflows. Like your namesake from James Bond, you provide agents with the best tools and configurations for their missions. +> **Efficiency rule**: Make parallel tool calls whenever reading multiple independent resources (for example, fetch issue + PR + recent logs in one compound turn). Each sequential turn costs a full context echo. Target fewer than 20 turns for typical requests. + ## Mission When invoked with the `/q` command in an issue, pull request, or discussion comment, analyze the current GitHub context and improve the target agentic workflows by: @@ -165,9 +166,9 @@ Use the gh-aw MCP server tools to gather real data: - **Performance Issues**: High token usage, excessive turns, timeouts - **Error Patterns**: Recurring failures and their causes -### Phase 2: Deep Analysis with Serena +### Phase 2: Deep Workflow Analysis -Use Serena's code analysis capabilities to: +Use repository analysis tools to: 1. **Examine Workflow Files**: Read and analyze workflow markdown files in `.github/workflows/` 2. **Identify Common Patterns**: Look for repeated code or configurations across workflows diff --git a/.github/workflows/schema-feature-coverage.lock.yml b/.github/workflows/schema-feature-coverage.lock.yml index 55e1a38e13b..b3eb5c176e6 100644 --- a/.github/workflows/schema-feature-coverage.lock.yml +++ b/.github/workflows/schema-feature-coverage.lock.yml @@ -1280,18 +1280,18 @@ jobs: DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 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 /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_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 CODEX_HOME -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.6' - cat > "${RUNNER_TEMP}/gh-aw/mcp-config/config.toml" << GH_AW_MCP_CONFIG_2e9f24eb570bdd2f_EOF + cat > "${RUNNER_TEMP}/gh-aw/mcp-config/config.toml" << GH_AW_MCP_CONFIG_9d968af98c479d13_EOF [history] persistence = "none" [shell_environment_policy] inherit = "core" include_only = ["CODEX_API_KEY", "HOME", "OPENAI_API_KEY", "PATH"] - GH_AW_MCP_CONFIG_2e9f24eb570bdd2f_EOF + GH_AW_MCP_CONFIG_9d968af98c479d13_EOF # Generate JSON config for MCP gateway GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_d97c69e5746bd0d1_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_142d4387df27003e_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { }, @@ -1302,11 +1302,11 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_d97c69e5746bd0d1_EOF + GH_AW_MCP_CONFIG_142d4387df27003e_EOF # Sync converter output to writable CODEX_HOME for Codex mkdir -p /tmp/gh-aw/mcp-config - cat > "/tmp/gh-aw/mcp-config/config.toml" << GH_AW_CODEX_SHELL_POLICY_0bb4bf26fd546bb0_EOF + cat > "/tmp/gh-aw/mcp-config/config.toml" << GH_AW_CODEX_SHELL_POLICY_3510e4e7180af902_EOF model_provider = "openai-proxy" [model_providers.openai-proxy] name = "OpenAI AWF proxy" @@ -1316,7 +1316,7 @@ jobs: [shell_environment_policy] inherit = "core" include_only = ["CODEX_API_KEY", "HOME", "OPENAI_API_KEY", "PATH"] - GH_AW_CODEX_SHELL_POLICY_0bb4bf26fd546bb0_EOF + GH_AW_CODEX_SHELL_POLICY_3510e4e7180af902_EOF awk ' BEGIN { skip_openai_proxy = 0 } /^[[:space:]]*model_provider[[:space:]]*=/ { next } diff --git a/.github/workflows/slide-deck-maintainer.lock.yml b/.github/workflows/slide-deck-maintainer.lock.yml index 6441d703766..98f15e7e1e1 100644 --- a/.github/workflows/slide-deck-maintainer.lock.yml +++ b/.github/workflows/slide-deck-maintainer.lock.yml @@ -484,7 +484,7 @@ jobs: - name: Install AWF binary run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 - name: Install Playwright CLI - run: npm install -g @playwright/cli@0.1.11 + run: npm install -g @playwright/cli@0.1.13 - name: Install Playwright CLI skills run: playwright-cli install --skills - name: Determine automatic lockdown mode for GitHub MCP Server diff --git a/.github/workflows/smoke-call-workflow.lock.yml b/.github/workflows/smoke-call-workflow.lock.yml index ee4e5f8990a..cb2be8af355 100644 --- a/.github/workflows/smoke-call-workflow.lock.yml +++ b/.github/workflows/smoke-call-workflow.lock.yml @@ -1292,18 +1292,18 @@ jobs: DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 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 /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_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 CODEX_HOME -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.6' - cat > "${RUNNER_TEMP}/gh-aw/mcp-config/config.toml" << GH_AW_MCP_CONFIG_fca1cdaf68719a22_EOF + cat > "${RUNNER_TEMP}/gh-aw/mcp-config/config.toml" << GH_AW_MCP_CONFIG_56630cff9bd522a5_EOF [history] persistence = "none" [shell_environment_policy] inherit = "core" include_only = ["CODEX_API_KEY", "HOME", "OPENAI_API_KEY", "PATH"] - GH_AW_MCP_CONFIG_fca1cdaf68719a22_EOF + GH_AW_MCP_CONFIG_56630cff9bd522a5_EOF # Generate JSON config for MCP gateway GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_43e3e3ad5b4825f7_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_d291d7aac5271862_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { }, @@ -1314,11 +1314,11 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_43e3e3ad5b4825f7_EOF + GH_AW_MCP_CONFIG_d291d7aac5271862_EOF # Sync converter output to writable CODEX_HOME for Codex mkdir -p /tmp/gh-aw/mcp-config - cat > "/tmp/gh-aw/mcp-config/config.toml" << GH_AW_CODEX_SHELL_POLICY_fab06ab206b780f1_EOF + cat > "/tmp/gh-aw/mcp-config/config.toml" << GH_AW_CODEX_SHELL_POLICY_23f6248cc9fb7a5f_EOF model_provider = "openai-proxy" [model_providers.openai-proxy] name = "OpenAI AWF proxy" @@ -1328,7 +1328,7 @@ jobs: [shell_environment_policy] inherit = "core" include_only = ["CODEX_API_KEY", "HOME", "OPENAI_API_KEY", "PATH"] - GH_AW_CODEX_SHELL_POLICY_fab06ab206b780f1_EOF + GH_AW_CODEX_SHELL_POLICY_23f6248cc9fb7a5f_EOF awk ' BEGIN { skip_openai_proxy = 0 } /^[[:space:]]*model_provider[[:space:]]*=/ { next } diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index cc0ea39794b..5ed1f76a0e6 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -961,7 +961,7 @@ jobs: - name: Install Claude Code CLI run: npm install -g @anthropic-ai/claude-code@2.1.133 - name: Install Playwright CLI - run: npm install -g @playwright/cli@0.1.11 + run: npm install -g @playwright/cli@0.1.13 - name: Install Playwright CLI skills run: playwright-cli install --skills - name: Determine automatic lockdown mode for GitHub MCP Server diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index 2baba62d65d..428ffe6ace8 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -564,7 +564,7 @@ jobs: - name: Install AWF binary run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 - name: Install Playwright CLI - run: npm install -g @playwright/cli@0.1.11 + run: npm install -g @playwright/cli@0.1.13 - name: Install Playwright CLI skills run: playwright-cli install --skills - name: Determine automatic lockdown mode for GitHub MCP Server @@ -1857,18 +1857,18 @@ jobs: DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 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 /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_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 CODEX_HOME -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.6' - cat > "${RUNNER_TEMP}/gh-aw/mcp-config/config.toml" << GH_AW_MCP_CONFIG_e3926364904b9e7f_EOF + cat > "${RUNNER_TEMP}/gh-aw/mcp-config/config.toml" << GH_AW_MCP_CONFIG_d47e449e4252abaa_EOF [history] persistence = "none" [shell_environment_policy] inherit = "core" include_only = ["CODEX_API_KEY", "HOME", "OPENAI_API_KEY", "PATH"] - GH_AW_MCP_CONFIG_e3926364904b9e7f_EOF + GH_AW_MCP_CONFIG_d47e449e4252abaa_EOF # Generate JSON config for MCP gateway GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_12fdff7d680a20f7_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_a6e38a7bd9fed836_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { }, @@ -1879,11 +1879,11 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_12fdff7d680a20f7_EOF + GH_AW_MCP_CONFIG_a6e38a7bd9fed836_EOF # Sync converter output to writable CODEX_HOME for Codex mkdir -p /tmp/gh-aw/mcp-config - cat > "/tmp/gh-aw/mcp-config/config.toml" << GH_AW_CODEX_SHELL_POLICY_351c47c89ac9fce9_EOF + cat > "/tmp/gh-aw/mcp-config/config.toml" << GH_AW_CODEX_SHELL_POLICY_d00434be8f1d9a32_EOF model_provider = "openai-proxy" [model_providers.openai-proxy] name = "OpenAI AWF proxy" @@ -1893,7 +1893,7 @@ jobs: [shell_environment_policy] inherit = "core" include_only = ["CODEX_API_KEY", "HOME", "OPENAI_API_KEY", "PATH"] - GH_AW_CODEX_SHELL_POLICY_351c47c89ac9fce9_EOF + GH_AW_CODEX_SHELL_POLICY_d00434be8f1d9a32_EOF awk ' BEGIN { skip_openai_proxy = 0 } /^[[:space:]]*model_provider[[:space:]]*=/ { next } diff --git a/.github/workflows/smoke-copilot-arm.lock.yml b/.github/workflows/smoke-copilot-arm.lock.yml index bf91b67fd91..6f3dbb78b6f 100644 --- a/.github/workflows/smoke-copilot-arm.lock.yml +++ b/.github/workflows/smoke-copilot-arm.lock.yml @@ -588,7 +588,7 @@ jobs: - name: Install AWF binary run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 - name: Install Playwright CLI - run: npm install -g @playwright/cli@0.1.11 + run: npm install -g @playwright/cli@0.1.13 - name: Install Playwright CLI skills run: playwright-cli install --skills - name: Determine automatic lockdown mode for GitHub MCP Server diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 52278f83746..4170dad5ecb 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -635,7 +635,7 @@ jobs: - name: Install AWF binary run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 - name: Install Playwright CLI - run: npm install -g @playwright/cli@0.1.11 + run: npm install -g @playwright/cli@0.1.13 - name: Install Playwright CLI skills run: playwright-cli install --skills - name: Parse integrity filter lists diff --git a/.github/workflows/spec-librarian.md b/.github/workflows/spec-librarian.md index 4858a47eac2..cf8769d8aa3 100644 --- a/.github/workflows/spec-librarian.md +++ b/.github/workflows/spec-librarian.md @@ -91,6 +91,9 @@ You are the Package Specification Librarian — a meticulous documentation audit Perform a comprehensive daily audit of all Go package specifications under `pkg/`. Create an issue if problems are found that require human or agent intervention. +**🚨 MANDATORY: You MUST call either `noop` or `create_issue` before exiting, regardless of outcome.** +This workflow has `strict: true` and `create-issue` as its only write safe output. If no issue is needed, call `noop` as your LAST action before finishing. + ## Phase 1: Inventory All Packages and Specifications Use the `coverage-checker` agent. It returns JSON with `total_packages`, `packages_with_specs`, diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml index 148eac21a88..e0f677f6f36 100644 --- a/.github/workflows/unbloat-docs.lock.yml +++ b/.github/workflows/unbloat-docs.lock.yml @@ -528,7 +528,7 @@ jobs: - name: Install Claude Code CLI run: npm install -g @anthropic-ai/claude-code@2.1.133 - name: Install Playwright CLI - run: npm install -g @playwright/cli@0.1.11 + run: npm install -g @playwright/cli@0.1.13 - name: Install Playwright CLI skills run: playwright-cli install --skills - name: Determine automatic lockdown mode for GitHub MCP Server diff --git a/.github/workflows/visual-regression-checker.lock.yml b/.github/workflows/visual-regression-checker.lock.yml index ea750b7e90d..a9e935eb20e 100644 --- a/.github/workflows/visual-regression-checker.lock.yml +++ b/.github/workflows/visual-regression-checker.lock.yml @@ -468,7 +468,7 @@ jobs: - name: Install AWF binary run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 - name: Install Playwright CLI - run: npm install -g @playwright/cli@0.1.11 + run: npm install -g @playwright/cli@0.1.13 - name: Install Playwright CLI skills run: playwright-cli install --skills - name: Determine automatic lockdown mode for GitHub MCP Server diff --git a/.github/workflows/weekly-editors-health-check.lock.yml b/.github/workflows/weekly-editors-health-check.lock.yml index 517e05b369b..3025ffa67d0 100644 --- a/.github/workflows/weekly-editors-health-check.lock.yml +++ b/.github/workflows/weekly-editors-health-check.lock.yml @@ -427,7 +427,7 @@ jobs: - name: Install AWF binary run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 - name: Install Playwright CLI - run: npm install -g @playwright/cli@0.1.11 + run: npm install -g @playwright/cli@0.1.13 - name: Install Playwright CLI skills run: playwright-cli install --skills - name: Determine automatic lockdown mode for GitHub MCP Server diff --git a/actions/setup/js/claude_harness.cjs b/actions/setup/js/claude_harness.cjs index d88009c788f..c838800d8bf 100644 --- a/actions/setup/js/claude_harness.cjs +++ b/actions/setup/js/claude_harness.cjs @@ -69,6 +69,13 @@ const RATE_LIMIT_ERROR_PATTERN = /rate_limit_error|429 Too Many Requests/i; // condition — --continue cannot recover it because no deferred tool marker was written. const MAX_TURNS_EXIT_PATTERN = /"subtype"\s*:\s*"error_max_turns"/; +// Pattern to detect a "no deferred tool marker" error from Claude Code. +// This occurs when --continue is attempted but the session either was never deferred, +// the deferred marker is stale (tool already ran), or it falls outside the tail-scan +// window. Retrying with --continue will always produce the same instant failure, so +// this is a deterministic terminal condition that must not be retried. +const NO_DEFERRED_MARKER_PATTERN = /No deferred tool marker found/i; + /** * Emit a timestamped diagnostic log line to stderr. * All driver messages are prefixed with "[claude-harness]" so they are easy to @@ -110,6 +117,19 @@ function isMaxTurnsExit(output) { return MAX_TURNS_EXIT_PATTERN.test(output); } +/** + * Determines if the collected output contains a "no deferred tool marker" error. + * This occurs when Claude Code is invoked with --continue but the session was never + * deferred, the deferred marker is stale (tool already ran), or it falls outside the + * tail-scan window. Each retry with --continue will instantly produce the same error, + * so this is a deterministic terminal condition that must not be retried. + * @param {string} output - Collected stdout+stderr from the process + * @returns {boolean} + */ +function isNoDeferredMarkerError(output) { + return NO_DEFERRED_MARKER_PATTERN.test(output); +} + /** * Resolve --prompt-file arguments for the initial Claude run. * Strips the --prompt-file pair from args and appends the file content @@ -262,12 +282,14 @@ async function main() { const isOverloaded = isOverloadedError(result.output); const isRateLimit = isRateLimitError(result.output); const isMaxTurns = isMaxTurnsExit(result.output); + const isNoDeferredMarker = isNoDeferredMarkerError(result.output); log( `attempt ${attempt + 1} failed:` + ` exitCode=${result.exitCode}` + ` isOverloadedError=${isOverloaded}` + ` isRateLimitError=${isRateLimit}` + ` isMaxTurnsExit=${isMaxTurns}` + + ` isNoDeferredMarkerError=${isNoDeferredMarker}` + ` hasOutput=${result.hasOutput}` + ` retriesRemaining=${MAX_RETRIES - attempt}` ); @@ -281,6 +303,15 @@ async function main() { break; } + // "No deferred tool marker found" is a deterministic terminal condition: the session + // was never deferred, the marker is stale (tool already ran), or it falls outside the + // tail-scan window. Retrying with --continue always produces the same instant failure, + // so we stop immediately to avoid wasting retry budget and masking the real cause. + if (isNoDeferredMarker) { + log(`attempt ${attempt + 1}: no deferred tool marker — not retriable via --continue`); + break; + } + // Retry when the session was partially executed (has output). // Use --continue so Claude Code can resume from its saved session state. if (attempt < MAX_RETRIES && result.hasOutput) { @@ -311,6 +342,7 @@ if (typeof module !== "undefined" && module.exports) { resolveClaudePromptFileArgs, stripPromptFileArgs, isMaxTurnsExit, + isNoDeferredMarkerError, }; } diff --git a/actions/setup/js/claude_harness.test.cjs b/actions/setup/js/claude_harness.test.cjs index 1c07db71be6..9830c2ee686 100644 --- a/actions/setup/js/claude_harness.test.cjs +++ b/actions/setup/js/claude_harness.test.cjs @@ -5,7 +5,7 @@ import os from "os"; import path from "path"; const require = createRequire(import.meta.url); -const { resolveClaudePromptFileArgs, stripPromptFileArgs, isMaxTurnsExit } = require("./claude_harness.cjs"); +const { resolveClaudePromptFileArgs, stripPromptFileArgs, isMaxTurnsExit, isNoDeferredMarkerError } = require("./claude_harness.cjs"); describe("claude_harness.cjs", () => { describe("resolveClaudePromptFileArgs", () => { @@ -107,4 +107,39 @@ describe("claude_harness.cjs", () => { expect(isMaxTurnsExit('{"type":"result","subtype":"success","is_error":false}')).toBe(false); }); }); + + describe("isNoDeferredMarkerError", () => { + it("returns true for the canonical no-deferred-marker error message", () => { + const output = + "Error: No deferred tool marker found in the resumed session. " + + "Either the session was not deferred, the marker is stale (tool already ran), " + + "or it exceeds the tail-scan window. Provide a prompt to continue the conversation."; + expect(isNoDeferredMarkerError(output)).toBe(true); + }); + + it("returns true for mixed-case variant", () => { + expect(isNoDeferredMarkerError("no deferred tool marker found")).toBe(true); + }); + + it("returns true when the error appears inside a larger log block", () => { + const output = "[claude-harness] 2026-05-07T05:00:00.000Z attempt 1 failed: exitCode=1\n" + "Error: No deferred tool marker found in the resumed session.\n" + "[claude-harness] done: exitCode=1"; + expect(isNoDeferredMarkerError(output)).toBe(true); + }); + + it("returns false for an overloaded_error output", () => { + expect(isNoDeferredMarkerError('{"type":"error","error":{"type":"overloaded_error","message":"Overloaded"}}')).toBe(false); + }); + + it("returns false for a max_turns exit output", () => { + expect(isNoDeferredMarkerError('{"type":"result","subtype":"error_max_turns","is_error":true}')).toBe(false); + }); + + it("returns false for an empty string", () => { + expect(isNoDeferredMarkerError("")).toBe(false); + }); + + it("returns false for a successful result output", () => { + expect(isNoDeferredMarkerError('{"type":"result","subtype":"success","is_error":false}')).toBe(false); + }); + }); }); diff --git a/actions/setup/js/runtime_import.cjs b/actions/setup/js/runtime_import.cjs index d7f5376e20a..b98739219e5 100644 --- a/actions/setup/js/runtime_import.cjs +++ b/actions/setup/js/runtime_import.cjs @@ -146,6 +146,17 @@ const ALLOWED_EXPRESSIONS = [ function isSafeExpression(expr) { const trimmed = expr.trim(); + // Expressions containing line terminators are never safe. + // A newline inside an expression can split the operator regex matching and + // cause compound expressions like "safe == 'x' &\n 'payload' || 'default'" + // to appear safe via the comparison extractor even though the full expression + // is not. Cover all JavaScript line terminator characters: LF, CR, LS (U+2028), + // and PS (U+2029). Check the original `expr` (before trimming) so that + // leading/trailing line terminators like "\ngithub.repository\n" are also caught. + if (/[\n\r\u2028\u2029]/.test(expr)) { + return false; + } + // Block dangerous JavaScript built-in property names const DANGEROUS_PROPS = [ "constructor", @@ -341,16 +352,11 @@ function isSafeExpression(expr) { // This check only runs for expressions that have no top-level || or && operators (since those // cases are fully handled above), preventing a partially-validated compound expression from // sneaking through via the comparison path. - // Extract each property access on the left side of a comparison operator and verify it is in - // the allowed list. This mirrors the Go comparisonExtractionRegex logic. - const compExtractRegex = /([a-zA-Z_][a-zA-Z0-9_.]*)\s*(?:==|!=|<=?|>=?)\s*/g; - const comparisonProps = []; - let compMatch; - while ((compMatch = compExtractRegex.exec(trimmed)) !== null) { - comparisonProps.push(compMatch[1].trim()); - } - if (comparisonProps.length > 0 && comparisonProps.every(prop => isSafeExpression(prop))) { - return true; + const comparisonMatch = trimmed.match(/^(.+?)\s*(?:==|!=|<=?|>=?)\s*(.+)$/); + if (comparisonMatch) { + const leftExpr = comparisonMatch[1].trim(); + const rightExpr = comparisonMatch[2].trim(); + return leftExpr.length > 0 && rightExpr.length > 0 && isSafeExpression(leftExpr) && isSafeExpression(rightExpr); } return false; diff --git a/actions/setup/js/runtime_import.test.cjs b/actions/setup/js/runtime_import.test.cjs index 21024df7a39..e6da281d181 100644 --- a/actions/setup/js/runtime_import.test.cjs +++ b/actions/setup/js/runtime_import.test.cjs @@ -327,10 +327,13 @@ describe("runtime_import", () => { expect(isSafeExpression("github.event.inputs.enforce_all == 'true'")).toBe(!0); expect(isSafeExpression("github.actor == 'octocat'")).toBe(!0); expect(isSafeExpression("inputs.mode != 'dry-run'")).toBe(!0); + expect(isSafeExpression("github.actor == github.repository")).toBe(!0); }); it("should reject comparison expressions with unsafe properties", () => { expect(isSafeExpression("secrets.TOKEN == 'value'")).toBe(!1); expect(isSafeExpression("vars.SECRET == 'value'")).toBe(!1); + expect(isSafeExpression("github.actor == secrets.TOKEN")).toBe(!1); + expect(isSafeExpression("github.actor == 'value' |\u000f secrets.TOKEN")).toBe(!1); }); it("should allow AND compound expressions without literals", () => { expect(isSafeExpression("github.actor && github.repository")).toBe(!0); @@ -1056,9 +1059,9 @@ describe("runtime_import", () => { expect(isSafeExpression("vars.MY_VAR")).toBe(false); }); - it("should handle whitespace", () => { + it("should handle surrounding spaces but reject line terminators", () => { expect(isSafeExpression(" github.actor ")).toBe(true); - expect(isSafeExpression("\ngithub.repository\n")).toBe(true); + expect(isSafeExpression("\ngithub.repository\n")).toBe(false); }); }); diff --git a/actions/setup/js/safe_outputs_tools.json b/actions/setup/js/safe_outputs_tools.json index d2b12e12c36..7f6fba91d07 100644 --- a/actions/setup/js/safe_outputs_tools.json +++ b/actions/setup/js/safe_outputs_tools.json @@ -1162,7 +1162,7 @@ }, "value": { "type": "string", - "description": "Field value to set. For single-select fields, this must match an existing option name. For date fields, use YYYY-MM-DD." + "description": "Field value to set. For single-select fields, this must match an existing option name (e.g., \"P1\" or \"High\"). For date fields, use format: YYYY-MM-DD (for example, 2026-05-08)." }, "secrecy": { "type": "string", diff --git a/actions/setup/js/set_issue_field.cjs b/actions/setup/js/set_issue_field.cjs index 398234a2f47..0452982240b 100644 --- a/actions/setup/js/set_issue_field.cjs +++ b/actions/setup/js/set_issue_field.cjs @@ -360,7 +360,7 @@ async function main(config = {}) { const hasConflictingFieldIdentifiers = Boolean(fieldNodeId && fieldName && resolvedFieldByName && resolvedField && resolvedFieldByName.id !== resolvedField.id); if (hasConflictingFieldIdentifiers) { - const error = `field_name ${JSON.stringify(fieldName)} resolves to ${JSON.stringify(resolvedFieldByName.id)}, but field_node_id was ${JSON.stringify(fieldNodeId)}. Provide only one identifier or make them match.`; + const error = `field_name ${JSON.stringify(fieldName)} resolves to ${JSON.stringify(resolvedFieldByName?.id)}, but field_node_id was ${JSON.stringify(fieldNodeId)}. Provide only one identifier or make them match.`; core.error(error); return { success: false, error }; } diff --git a/docs/src/content/docs/reference/effective-tokens-specification.md b/docs/src/content/docs/reference/effective-tokens-specification.md index 4027eb2102b..849142fbf07 100644 --- a/docs/src/content/docs/reference/effective-tokens-specification.md +++ b/docs/src/content/docs/reference/effective-tokens-specification.md @@ -318,12 +318,12 @@ Extensions MUST NOT alter the core ET definition or the default weight values wi | Requirement | Test ID | Level | Status | |---|---|---|---| -| Per-invocation base weighted tokens | T-ET-001–004 | 1 | Required | -| Per-invocation ET computation | T-ET-002 | 1 | Required | -| Multi-invocation aggregation | T-ET-010–012 | 2 | Required | -| Execution graph node schema | T-ET-020–022 | 2 | Required | -| Summary reporting | T-ET-030–031 | 3 | Required | -| Custom weight disclosure | T-ET-004 | 1 | Required | +| Per-invocation base weighted tokens | T-ET-001–004 | 1 | Implemented | +| Per-invocation ET computation | T-ET-002 | 1 | Implemented | +| Multi-invocation aggregation | T-ET-010–012 | 2 | Implemented | +| Execution graph node schema | T-ET-020–022 | 2 | Implemented | +| Summary reporting | T-ET-030–031 | 3 | Implemented | +| Custom weight disclosure | T-ET-004 | 1 | Implemented | | Versioning of weights/multipliers | — | 3 | Recommended | | Partial visibility flagging | — | 2 | Recommended | @@ -478,6 +478,8 @@ This file is embedded at compile time into the `gh-aw` binary using a Go `//go:e **R-REG-008**: When adding support for a new model, maintainers MUST register the model in `pkg/cli/data/model_multipliers.json` with a concrete numeric multiplier before release. If calibration is incomplete, the model MUST be omitted from the registry and the implementation fallback behavior in R-REG-005 applies. +**R-REG-009**: When a model is scheduled for removal from the registry, it MUST remain in `pkg/cli/data/model_multipliers.json` with a `deprecated` marker in a comment or companion metadata field for at least one minor version before it is deleted. Implementations SHOULD emit a warning when a `deprecated` model is encountered at runtime, advising callers to migrate to a supported model. A model entry MUST NOT be silently removed between consecutive minor versions; removal without the one-version deprecation notice is a breaking change and MUST be accompanied by a major version bump of the registry `version` field. + ### Registry Versioning The `version` field in `model_multipliers.json` corresponds to the registry schema version, not the gh-aw binary version. Implementations SHOULD include the registry version in all ET summary reports to enable historical reconstruction. @@ -492,8 +494,9 @@ To keep specification and implementation synchronized: 1. Update this specification's registry requirements when adding, removing, or re-scaling model multipliers. 2. Update `pkg/cli/data/model_multipliers.json` in the same change. -3. Verify loading and fallback behavior in `pkg/cli/effective_tokens_test.go` (`TestModelMultipliersJSONEmbedded`, `TestResolveEffectiveWeightsDefault`, and inventory checks). -4. Run `make build` so the embedded registry is rebuilt into the `gh-aw` binary. +3. When deprecating a model, add a `deprecated` comment alongside the entry and keep it in the registry for at least one minor version before removal (R-REG-009). Update the registry `version` field on removal. +4. Verify loading and fallback behavior in `pkg/cli/effective_tokens_test.go` (`TestModelMultipliersJSONEmbedded`, `TestResolveEffectiveWeightsDefault`, and inventory checks). +5. Run `make build` so the embedded registry is rebuilt into the `gh-aw` binary. Conforming releases SHOULD include a test assertion for newly added model multipliers to ensure implementation-registry parity. @@ -516,8 +519,10 @@ Conforming releases SHOULD include a test assertion for newly added model multip ### Version 0.3.0 (Draft) -- **Added**: Model Multiplier Registry section with normative requirements R-REG-001 through R-REG-006 +- **Added**: Model Multiplier Registry section with normative requirements R-REG-001 through R-REG-009 +- **Added**: R-REG-009: model deprecation/sunset lifecycle norm (models must carry a `deprecated` marker for one minor version before removal) - **Added**: Compliance test skeleton file `pkg/cli/effective_tokens_compliance_test.go` with Go test stubs for T-ET-001..T-ET-031 +- **Updated**: Compliance checklist §10.2 status column from "Required" to "Implemented" for all test IDs T-ET-001–T-ET-031 (all tests now implemented and passing) - **Audit (Appendix C — Security)**: Verified Appendix C requirements against `pkg/cli/effective_tokens.go` and `pkg/cli/data/model_multipliers.json`. Findings: - _Sensitive usage patterns_ (Appendix C §1): Per-invocation token data is not exposed directly by the CLI; only aggregate `TotalEffectiveTokens` is surfaced in the audit output. Access control is delegated to GitHub repository permissions. **No gaps found.** - _Aggregate vs. detailed data separation_ (Appendix C §2): The `TokenUsageSummary.ByModel` map contains per-model breakdowns but is only logged at DEBUG level, not included in default CLI output. **No gaps found.** diff --git a/docs/src/content/docs/reference/frontmatter-hash-specification.md b/docs/src/content/docs/reference/frontmatter-hash-specification.md index 80034b3d313..658edd7b563 100644 --- a/docs/src/content/docs/reference/frontmatter-hash-specification.md +++ b/docs/src/content/docs/reference/frontmatter-hash-specification.md @@ -223,6 +223,8 @@ The current Go implementation (`pkg/parser/frontmatter_hash.go`) uses a **text-b **Sync status** (verified 2026-05-06): The Go implementation is consistent with the JavaScript implementation in `actions/setup/js/` for the text-based approach. Both produce identical hashes for the same input. The field-selection model in Section 2 documents the _logical_ intent; the text-based implementation is the authoritative runtime behavior until a future revision aligns them. +**Resolution** (2026-05-08): The project officially adopts the **text-based approach** as the authoritative runtime behavior (option b). Section 2 ("Field Selection") documents the intended logical model for future alignment, but is non-normative until a dedicated migration milestone is scheduled. No immediate changes to the Go or JavaScript implementations are required. A future v2.0.0 revision of this specification MAY align both implementations to the field-selection model if selective field exclusion becomes a concrete requirement; that revision MUST include updated cross-language test vectors and a migration guide. Until then, implementations MUST continue to use the text-based approach and MUST NOT selectively exclude fields from the hash input. + - Use `crypto/sha256` for hashing (`crypto/sha256.Sum256`) - Use `hex.EncodeToString()` for hexadecimal encoding @@ -292,6 +294,29 @@ Very large frontmatter payloads can cause excessive memory use and hash-computat --- +## Sync Notes + +This section maps the frontmatter hash specification to the source files that implement it. Use this mapping to verify that specification changes are reflected in both implementations. + +| Component | File(s) | +|-----------|---------| +| Go hash computation | `pkg/parser/frontmatter_hash.go` (`computeFrontmatterHashTextBased`, `computeFrontmatterHashTextBasedWithReader`) | +| JavaScript hash computation | `actions/setup/js/frontmatter_hash.cjs` | +| Cross-language test | `pkg/parser/frontmatter_hash_cross_language_test.go` | +| Text normalization | `pkg/parser/frontmatter_hash.go` (`normalizeFrontmatterText`) | +| Import processing | `pkg/parser/frontmatter_hash.go` (`processImportsTextBased`) | + +**After any change to the hash algorithm:** +1. Update the Go implementation in `pkg/parser/frontmatter_hash.go` +2. Update the JavaScript implementation in `actions/setup/js/frontmatter_hash.cjs` +3. Run the cross-language test: `go test ./pkg/parser/ -run TestFrontmatterHash` +4. Run `make recompile` to regenerate all lock files with fresh hashes +5. Verify cross-language consistency for the test cases listed in Section 5 + +**Runtime behavior**: text-based approach is authoritative (see Implementation Notes § Resolution). + +--- + ## Security Considerations - The hash is **not cryptographically secure** for authentication (no HMAC/signing) diff --git a/docs/src/content/docs/reference/mcp-scripts-specification.md b/docs/src/content/docs/reference/mcp-scripts-specification.md index 37678cfe985..28d570be016 100644 --- a/docs/src/content/docs/reference/mcp-scripts-specification.md +++ b/docs/src/content/docs/reference/mcp-scripts-specification.md @@ -1382,6 +1382,50 @@ sandbox with the following constraints: tool's `env:` field. No other environment variables from the runner are forwarded into the Go sandbox. +### Norms Audit (2026-05-08) + +The following is an audit of normative coverage for tool execution ordering, retry semantics, +and error propagation — three areas that implementations commonly need explicit guidance on. + +#### Tool Execution Ordering + +**Status: Explicitly specified — no gap.** + +Section 5.1 specifies stateless, session-independent invocation. Each tool call is +independent with no defined ordering dependency across calls. The MCP protocol layer handles +queueing. No additional ordering norm is required. + +#### Retry Semantics + +**Status: Not specified — gap confirmed.** + +The specification does not define retry behavior for transient failures (e.g., container +startup failures, intermittent network errors for shell tools with network access, or +Node.js process crashes). The current behavior is implementation-defined. + +_Recommendation for a future revision_: Add a normative subsection (e.g., §5.7 Retry +Semantics) specifying: (a) tools MUST NOT be automatically retried by the MCP server +without explicit caller instruction; (b) transient container startup failures SHOULD surface +as a `-32603` execution error so the caller (agent) may decide whether to retry; (c) +idempotency of tool invocations is the caller's responsibility. + +#### Error Propagation + +**Status: Partially specified — minor gap.** + +Section 5.3 specifies JSON-RPC error codes for missing tool (`-32601`), invalid parameters +(`-32602`), and execution/internal errors (`-32603`). Timeout errors are covered in §5.6 +(code `-32603`). However, the specification does not distinguish between: + +- **Recoverable errors**: transient failures where the caller might retry (e.g., timeout, + container startup failure) +- **Non-recoverable errors**: permanent failures where retry would not help (e.g., syntax + error in tool script, missing dependency) + +_Recommendation for a future revision_: Extend §5.3 to include a `recoverable` boolean in +the error `data` field, allowing agents to make informed retry decisions without parsing +error messages. + --- ## References diff --git a/docs/src/content/docs/reference/playwright.md b/docs/src/content/docs/reference/playwright.md index f79195ac512..d71c496b6aa 100644 --- a/docs/src/content/docs/reference/playwright.md +++ b/docs/src/content/docs/reference/playwright.md @@ -76,10 +76,10 @@ The `version` field controls different things depending on the mode: tools: playwright: mode: cli - version: "0.1.11" # @playwright/cli npm package version (default) + version: "0.1.13" # @playwright/cli npm package version (default) ``` -**Default** (CLI mode): `0.1.11` +**Default** (CLI mode): `0.1.13` **MCP mode** (deprecated) — pins the Playwright browser Docker image version: diff --git a/docs/src/content/docs/setup/quick-start.mdx b/docs/src/content/docs/setup/quick-start.mdx index d299caed594..d0433cc70b1 100644 --- a/docs/src/content/docs/setup/quick-start.mdx +++ b/docs/src/content/docs/setup/quick-start.mdx @@ -30,6 +30,7 @@ Before installing, ensure you have: - **GitHub Repository** - A repository where you have write access - **GitHub Actions** enabled - Check in [Settings → Actions](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository) - **GitHub CLI** (`gh`) v2.0.0+ - [Install here](https://cli.github.com). Check version: `gh --version` +- **Logged in to GitHub CLI** - Verify with `gh auth status` and run `gh auth login --scopes repo,workflow` if needed - **Operating System**: Linux, macOS, or Windows with WSL ### Step 1 - Install the extension @@ -68,9 +69,16 @@ This will take you through an interactive process to: 1. **Check prerequisites** - Verify repository permissions. 2. **Select an AI Engine** - Choose between Copilot, Claude, Codex, or Gemini. 3. **Set up the required secret** - [`COPILOT_GITHUB_TOKEN`](/gh-aw/reference/auth/#copilot_github_token) (a separate GitHub token with Copilot access — distinct from the default `GITHUB_TOKEN`), [`ANTHROPIC_API_KEY`](/gh-aw/reference/auth/#anthropic_api_key), [`OPENAI_API_KEY`](/gh-aw/reference/auth/#openai_api_key), or [`GEMINI_API_KEY`](/gh-aw/reference/auth/#gemini_api_key). See [Authentication](/gh-aw/reference/auth/) for setup instructions. -4. **Add the workflow** - Adds the workflow file (`.md`) and its compiled lock file (`.lock.yml`) to `.github/workflows/`. +4. **Add the workflow** - Adds the workflow file (`.md`) and its generated GitHub Actions lock file (`.lock.yml`) to `.github/workflows/`. 5. **Optionally trigger an initial run** - Starts the workflow immediately. +> [!NOTE] +> **Setting up `COPILOT_GITHUB_TOKEN`?** +> 1. [Create a fine-grained PAT](https://github.com/settings/personal-access-tokens/new) under your user account. +> 2. Under **Permissions → Account permissions**, set **Copilot Requests** to **Read**, then generate the token. +> 3. Add it as a repository secret from your repository root with `gh secret set COPILOT_GITHUB_TOKEN < /path/to/token.txt`, or use the GitHub UI. See [Authentication](/gh-aw/reference/auth/#copilot_github_token) for more detail. +> + > [!TIP] > **Having trouble?** Check your [repository secrets](/gh-aw/reference/auth/), see the [FAQ](/gh-aw/reference/faq/) and [Common Issues](/gh-aw/troubleshooting/common-issues/). @@ -99,7 +107,9 @@ With GitHub Agentic Workflows, you are in control! Your repository automation is To customize it now: > [!NOTE] -> Each workflow file has two parts. The **frontmatter** (the configuration block between the `---` markers at the top) controls when the workflow runs, which AI engine powers it, and what tools it can access — changes here require recompiling (step 3). The **body** below contains your natural language task description and instructions — changes here take effect immediately on the next run. +> Workflows have two files: the `.md` file you edit and a generated `.lock.yml` file that GitHub Actions runs. `gh aw compile` regenerates the `.lock.yml` file from your markdown whenever you change the workflow configuration. +> +> Within the `.md` file, the **frontmatter** (the configuration block between the `---` markers at the top) controls when the workflow runs, which AI engine powers it, and what tools it can access — changes here require recompiling. The **body** below contains your natural language task description and instructions — changes here take effect immediately on the next run. 1. Open the workflow markdown file located at `.github/workflows/daily-repo-status.md` in your repository. diff --git a/pkg/actionpins/actionpins.go b/pkg/actionpins/actionpins.go index b88c33f5157..461074fbfcc 100644 --- a/pkg/actionpins/actionpins.go +++ b/pkg/actionpins/actionpins.go @@ -259,6 +259,17 @@ func isValidFullSHA(s string) bool { return gitutil.IsValidFullSHA(s) } +// findVersionBySHA returns the version string for a given SHA in the embedded pins +// for the specified repo. Returns "" if no matching pin is found. +func findVersionBySHA(repo, sha string) string { + for _, pin := range GetActionPinsByRepo(repo) { + if pin.SHA == sha { + return pin.Version + } + } + return "" +} + // findCompatiblePin returns the first pin whose version is semver-compatible with // the requested version, or ActionPin{}, false if no compatible pin is found. func findCompatiblePin(pins []ActionPin, version string) (ActionPin, bool) { @@ -307,7 +318,8 @@ func ResolveActionPin(actionRepo, version string, ctx *PinContext) (string, erro sha, err := ctx.Resolver.ResolveSHA(cmp.Or(ctx.Ctx, context.Background()), actionRepo, version) if err == nil && sha != "" { log.Printf("Dynamic resolution succeeded: %s@%s → %s", actionRepo, version, sha) - result := FormatPinnedActionReference(actionRepo, sha, version) + resolvedVersion := findVersionBySHA(actionRepo, sha) + result := formatPinnedActionWithResolution(actionRepo, sha, version, resolvedVersion) log.Printf("Returning pinned reference: %s", result) return result, nil } diff --git a/pkg/actionpins/actionpins_internal_test.go b/pkg/actionpins/actionpins_internal_test.go index b43a07896d9..bc479aad5ed 100644 --- a/pkg/actionpins/actionpins_internal_test.go +++ b/pkg/actionpins/actionpins_internal_test.go @@ -61,6 +61,70 @@ func TestInitWarnings_InitializesAndPreservesMap(t *testing.T) { }) } +func TestFormatPinnedActionWithResolution_ConsistentVersionComment(t *testing.T) { + tests := []struct { + name string + repo string + sha string + sourceVersion string + resolvedVersion string + expected string + }{ + { + name: "shows only source version when resolvedVersion is empty", + repo: "actions/checkout", + sha: "abc123", + sourceVersion: "v4", + resolvedVersion: "", + expected: "actions/checkout@abc123 # v4", + }, + { + name: "shows only version when source equals resolved", + repo: "actions/checkout", + sha: "abc123", + sourceVersion: "v4.1.2", + resolvedVersion: "v4.1.2", + expected: "actions/checkout@abc123 # v4.1.2", + }, + { + name: "shows both versions when source differs from resolved", + repo: "actions/checkout", + sha: "abc123", + sourceVersion: "v4", + resolvedVersion: "v4.1.2", + expected: "actions/checkout@abc123 # v4.1.2 (source v4)", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := formatPinnedActionWithResolution(tt.repo, tt.sha, tt.sourceVersion, tt.resolvedVersion) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestFindVersionBySHA_ReturnsVersionForKnownSHA(t *testing.T) { + t.Run("returns version for a known SHA in embedded data", func(t *testing.T) { + pins := GetActionPinsByRepo("actions/checkout") + require.NotEmpty(t, pins, "prerequisite: embedded pins must exist for actions/checkout") + + knownPin := pins[0] + version := findVersionBySHA("actions/checkout", knownPin.SHA) + assert.Equal(t, knownPin.Version, version, "should return the version for a known SHA") + }) + + t.Run("returns empty string for unknown SHA", func(t *testing.T) { + version := findVersionBySHA("actions/checkout", "0000000000000000000000000000000000000000") + assert.Empty(t, version, "should return empty string for unknown SHA") + }) + + t.Run("returns empty string for unknown repo", func(t *testing.T) { + version := findVersionBySHA("does-not-exist/unknown", "abc123") + assert.Empty(t, version, "should return empty string for unknown repo") + }) +} + func TestGetContainerPin_ReturnsPinnedImage(t *testing.T) { pin, ok := GetContainerPin("node:lts-alpine") require.True(t, ok, "Expected embedded container pin for node:lts-alpine") diff --git a/pkg/actionpins/spec_test.go b/pkg/actionpins/spec_test.go index 39c03ed3c28..957ffeb00a8 100644 --- a/pkg/actionpins/spec_test.go +++ b/pkg/actionpins/spec_test.go @@ -3,6 +3,7 @@ package actionpins_test import ( + "context" "strings" "testing" @@ -270,6 +271,62 @@ func TestSpec_PublicAPI_ResolveActionPin_EmbeddedMatch(t *testing.T) { assert.Contains(t, result, latestPin.SHA, "resolved reference should contain the pin SHA") } +// testSHAResolver is a fake SHAResolver used in tests. +type testSHAResolver struct { + sha string + err error +} + +func (r *testSHAResolver) ResolveSHA(_ context.Context, _, _ string) (string, error) { + return r.sha, r.err +} + +// TestSpec_DynamicResolution_VersionCommentConsistency validates that when dynamic resolution +// succeeds and the returned SHA matches an embedded pin, the version comment includes both +// the resolved version and the source version — consistent with the embedded-fallback path. +func TestSpec_DynamicResolution_VersionCommentConsistency(t *testing.T) { + known := "actions/checkout" + latestPin, ok := actionpins.GetLatestActionPinByRepo(known) + require.True(t, ok, "prerequisite: known repo must be in embedded data") + + t.Run("shows resolved version and source version when SHA matches embedded pin", func(t *testing.T) { + // Simulate dynamic resolution returning the same SHA as the embedded pin, + // but requested with a shorter version tag (e.g. "v4" instead of "v4.1.2"). + sourceVersion := "v4" + ctx := &actionpins.PinContext{ + Resolver: &testSHAResolver{sha: latestPin.SHA}, + Warnings: make(map[string]bool), + } + result, err := actionpins.ResolveActionPin(known, sourceVersion, ctx) + require.NoError(t, err) + assert.Contains(t, result, latestPin.SHA, "result should contain the resolved SHA") + assert.Contains(t, result, latestPin.Version, "result should contain the resolved version") + assert.Contains(t, result, sourceVersion, "result should contain the source version") + }) + + t.Run("shows only source version when SHA is not in embedded pins", func(t *testing.T) { + unknownSHA := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + sourceVersion := "v4" + ctx := &actionpins.PinContext{ + Resolver: &testSHAResolver{sha: unknownSHA}, + Warnings: make(map[string]bool), + } + result, err := actionpins.ResolveActionPin(known, sourceVersion, ctx) + require.NoError(t, err) + assert.Contains(t, result, unknownSHA, "result should contain the resolved SHA") + assert.Contains(t, result, sourceVersion, "result should contain the source version") + }) + + t.Run("skips version comment when version is already a SHA", func(t *testing.T) { + ctx := &actionpins.PinContext{Warnings: make(map[string]bool)} + result, err := actionpins.ResolveActionPin(known, latestPin.SHA, ctx) + require.NoError(t, err) + assert.Contains(t, result, latestPin.SHA, "result should contain the SHA") + // Resolver is not called for SHA inputs; only version comment content matters + }) +} + + // TestSpec_PublicAPI_GetActionPins_SPEC_MISMATCH documents a spec-implementation gap. // SPEC_MISMATCH: The README specifies GetActionPins() []ActionPin ("Returns all loaded pins") // but this function is not implemented. Only GetActionPinsByRepo(repo string) is available. diff --git a/pkg/cli/data/model_multipliers.json b/pkg/cli/data/model_multipliers.json index cf015e1d0c3..6b5b989c506 100644 --- a/pkg/cli/data/model_multipliers.json +++ b/pkg/cli/data/model_multipliers.json @@ -1,6 +1,6 @@ { "version": "1", - "description": "Effective Tokens (ET) computation data per the gh-aw Effective Tokens Specification v0.2.0. Token class weights are applied first to normalize across token classes, then the per-model multiplier scales the result relative to the reference model.", + "description": "Effective Tokens (ET) computation data per the gh-aw Effective Tokens Specification v0.2.0. Token class weights are applied first to normalize across token classes, then the per-model multiplier scales the result relative to the reference model. Model lifecycle: deprecated models must carry a deprecated marker for one minor version before removal (R-REG-009).", "reference_model": "claude-sonnet-4.5", "token_class_weights": { "input": 1.0, diff --git a/pkg/cli/mcp_argument_validation.go b/pkg/cli/mcp_argument_validation.go index 365f4bca76c..b5fb9778bb0 100644 --- a/pkg/cli/mcp_argument_validation.go +++ b/pkg/cli/mcp_argument_validation.go @@ -22,6 +22,7 @@ import ( "strings" "github.com/github/gh-aw/pkg/logger" + "github.com/modelcontextprotocol/go-sdk/jsonrpc" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -74,7 +75,10 @@ func argumentValidationMiddleware(toolParams map[string]toolParamEntry) mcp.Midd // Determine the tool name from the request so we can look up valid params. toolName := extractMCPToolName(req) - validParams := toolParams[toolName] + validParams, ok := toolParams[toolName] + if !ok { + return nil, newMCPError(jsonrpc.CodeMethodNotFound, fmt.Sprintf("unknown MCP tool: %q", toolName), nil) + } mcpArgValidationLog.Printf("Intercepted unknown param error: tool=%s, unknown_params=%v", toolName, unknownParams) diff --git a/pkg/cli/mcp_argument_validation_test.go b/pkg/cli/mcp_argument_validation_test.go index d6d46fcf7d0..91444ded156 100644 --- a/pkg/cli/mcp_argument_validation_test.go +++ b/pkg/cli/mcp_argument_validation_test.go @@ -6,6 +6,7 @@ import ( "context" "testing" + "github.com/modelcontextprotocol/go-sdk/jsonrpc" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -274,6 +275,31 @@ func TestArgumentValidationMiddleware_PassesThroughSuccessResults(t *testing.T) assert.False(t, toolResult.IsError) } +// TestArgumentValidationMiddleware_UnknownToolReturnsMethodNotFound verifies +// that an unregistered tool name is reported as method-not-found. +func TestArgumentValidationMiddleware_UnknownToolReturnsMethodNotFound(t *testing.T) { + toolParams := map[string]toolParamEntry{ + "compile": {"workflows"}, + } + middleware := argumentValidationMiddleware(toolParams) + + rawErr := `validating "arguments": validating root: unexpected additional properties ["workflow-name"]` + handler := middleware(func(_ context.Context, _ string, _ mcp.Request) (mcp.Result, error) { + return &mcp.CallToolResult{ + IsError: true, + Content: []mcp.Content{&mcp.TextContent{Text: rawErr}}, + }, nil + }) + + result, err := handler(context.Background(), "tools/call", fakeToolCallRequest("not-a-real-tool")) + assert.Nil(t, result, "unknown tool should not return a result payload") + require.Error(t, err, "unknown tool should return a JSON-RPC error") + + var rpcErr *jsonrpc.Error + require.ErrorAs(t, err, &rpcErr, "error should be a JSON-RPC error") + assert.Equal(t, int64(jsonrpc.CodeMethodNotFound), rpcErr.Code, "unknown tool should use method-not-found code") +} + // TestArgumentValidationMiddleware_PassesThroughNonToolCallMethods verifies // that the middleware ignores methods other than "tools/call". func TestArgumentValidationMiddleware_PassesThroughNonToolCallMethods(t *testing.T) { diff --git a/pkg/cli/mcp_server.go b/pkg/cli/mcp_server.go index f6abb492ff1..090d1264c60 100644 --- a/pkg/cli/mcp_server.go +++ b/pkg/cli/mcp_server.go @@ -12,6 +12,9 @@ import ( // execCmdFunc is the type for the command execution function passed to tool registrations. type execCmdFunc func(ctx context.Context, args ...string) *exec.Cmd +// MCP tool handlers in this package return (*mcp.CallToolResult, any, error). +// The second return value is a reserved SDK extension slot and must be nil for now. + // createMCPServer creates and configures the MCP server with all tools func createMCPServer(cmdPath string, actor string, validateActor bool, manifestCacheFile string) *mcp.Server { // Helper function to execute command with proper path diff --git a/pkg/cli/mcp_server_tools_test.go b/pkg/cli/mcp_server_tools_test.go index a354b6c81ab..98c06bf5e51 100644 --- a/pkg/cli/mcp_server_tools_test.go +++ b/pkg/cli/mcp_server_tools_test.go @@ -261,12 +261,12 @@ func TestMCPServer_ToolIcons(t *testing.T) { // Expected icons for each tool expectedIcons := map[string]string{ "status": "📊", - "compile": "🔨", - "logs": "📜", + "compile": "📋", + "logs": "📝", "audit": "🔍", - "audit-diff": "🔍", + "audit-diff": "🔎", "checks": "✅", - "mcp-inspect": "🔎", + "mcp-inspect": "🔬", "add": "➕", "update": "🔄", "fix": "🔧", diff --git a/pkg/cli/mcp_tools_privileged.go b/pkg/cli/mcp_tools_privileged.go index e95a54d3d53..a4e9b0d9350 100644 --- a/pkg/cli/mcp_tools_privileged.go +++ b/pkg/cli/mcp_tools_privileged.go @@ -86,7 +86,7 @@ The continuation field includes all necessary parameters (before_run_id, etc.) t from where the previous request stopped due to timeout.`, InputSchema: logsSchema, Icons: []mcp.Icon{ - {Source: "📜"}, + {Source: "📝"}, }, }, func(ctx context.Context, req *mcp.CallToolRequest, args logsArgs) (*mcp.CallToolResult, any, error) { // Check actor permissions first @@ -463,7 +463,7 @@ then produces a diff showing: Returns JSON describing the differences between the base run and each comparison run.`, InputSchema: schema, Icons: []mcp.Icon{ - {Source: "🔍"}, + {Source: "🔎"}, }, }, func(ctx context.Context, req *mcp.CallToolRequest, args auditDiffArgs) (*mcp.CallToolResult, any, error) { if err := checkActorPermission(ctx, actor, validateActor, "audit-diff"); err != nil { diff --git a/pkg/cli/mcp_tools_readonly.go b/pkg/cli/mcp_tools_readonly.go index 451647bed6f..45f8adef0fd 100644 --- a/pkg/cli/mcp_tools_readonly.go +++ b/pkg/cli/mcp_tools_readonly.go @@ -124,7 +124,7 @@ Returns JSON array with validation results for each workflow: - compiled_file: Path to the generated .lock.yml file`, InputSchema: compileSchema, Icons: []mcp.Icon{ - {Source: "🔨"}, + {Source: "📋"}, }, }, func(ctx context.Context, req *mcp.CallToolRequest, args compileArgs) (*mcp.CallToolResult, any, error) { // Check for cancellation before starting @@ -300,7 +300,7 @@ Returns formatted text output showing: - Secret availability status (if GitHub token is available) - Detailed tool information when tool parameter is specified`, Icons: []mcp.Icon{ - {Source: "🔎"}, + {Source: "🔬"}, }, }, func(ctx context.Context, req *mcp.CallToolRequest, args mcpInspectArgs) (*mcp.CallToolResult, any, error) { // Check for cancellation before starting diff --git a/pkg/constants/version_constants.go b/pkg/constants/version_constants.go index f3c929f990b..224b103a468 100644 --- a/pkg/constants/version_constants.go +++ b/pkg/constants/version_constants.go @@ -107,7 +107,7 @@ const DefaultPlaywrightMCPVersion Version = "0.0.75" // DefaultPlaywrightCLIVersion is the default version of the @playwright/cli package // Used when tools.playwright.mode is "cli" to install the CLI tool instead of the MCP server. -const DefaultPlaywrightCLIVersion Version = "0.1.11" +const DefaultPlaywrightCLIVersion Version = "0.1.13" // DefaultPlaywrightBrowserVersion is the default version of the Playwright browser Docker image const DefaultPlaywrightBrowserVersion Version = "v1.59.1" diff --git a/pkg/parser/runtime_import_test.go b/pkg/parser/runtime_import_test.go index c4b275e2dcb..318732a62f5 100644 --- a/pkg/parser/runtime_import_test.go +++ b/pkg/parser/runtime_import_test.go @@ -158,6 +158,18 @@ func TestRuntimeImportExpressionValidation(t *testing.T) { expectSafe: false, description: "secrets.TOKEN in OR right side must be blocked", }, + { + name: "unsafe comparison with secrets on right", + expression: "github.actor == secrets.TOKEN", + expectSafe: false, + description: "secrets.TOKEN on comparison RHS must be blocked", + }, + { + name: "malformed comparison with trailing unsafe content", + expression: "github.actor == 'value' |\x0f secrets.TOKEN", + expectSafe: false, + description: "Malformed comparisons must not pass via partial comparison matching", + }, { name: "unsafe compound with secrets", expression: "secrets.TOKEN == 'x' && github.actor || github.repository", diff --git a/pkg/parser/testdata/fuzz/FuzzRuntimeImportExpressionValidation/newline_bypass b/pkg/parser/testdata/fuzz/FuzzRuntimeImportExpressionValidation/newline_bypass new file mode 100644 index 00000000000..534c34f95ac --- /dev/null +++ b/pkg/parser/testdata/fuzz/FuzzRuntimeImportExpressionValidation/newline_bypass @@ -0,0 +1,2 @@ +go test fuzz v1 +string("github.event.inputs.enforce_all == 'github.etrue' &\n 'full-sweep (enforce_all)' || 'round-robin'") diff --git a/pkg/workflow/expression_safety_test.go b/pkg/workflow/expression_safety_test.go index cc4410ce324..d13f2284097 100644 --- a/pkg/workflow/expression_safety_test.go +++ b/pkg/workflow/expression_safety_test.go @@ -231,6 +231,12 @@ func TestValidateExpressionSafetyEdgeCases(t *testing.T) { expectError: true, description: "Expressions with functions should be unauthorized unless the base expression is allowed", }, + { + name: "malformed_comparison_with_trailing_unsafe_content", + content: "Bad: ${{ github.actor == 'value' |\x0f secrets.TOKEN }}", + expectError: true, + description: "Malformed comparisons must not pass via partial comparison matching", + }, { name: "multiline_expression", content: "Multi:\n${{ github.workflow\n}}", diff --git a/pkg/workflow/expression_safety_validation.go b/pkg/workflow/expression_safety_validation.go index e855dab0de9..f8487408552 100644 --- a/pkg/workflow/expression_safety_validation.go +++ b/pkg/workflow/expression_safety_validation.go @@ -30,9 +30,9 @@ var ( awInputsRegex = regexp.MustCompile(`^github\.aw\.inputs\.[a-zA-Z0-9_-]+$`) awImportInputsRegex = regexp.MustCompile(`^github\.aw\.import-inputs\.[a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)?$`) envRegex = regexp.MustCompile(`^env\.[a-zA-Z0-9_-]+$`) - // comparisonExtractionRegex extracts property accesses from comparison expressions - // Matches patterns like "github.workflow == 'value'" and extracts "github.workflow" - comparisonExtractionRegex = regexp.MustCompile(`([a-zA-Z_][a-zA-Z0-9_.]*)\s*(?:==|!=|<|>|<=|>=)\s*`) + // comparisonExpressionPattern matches a full comparison expression so both sides can be + // validated recursively instead of allowing a safe-looking prefix to bypass validation. + comparisonExpressionPattern = regexp.MustCompile(`^(.+?)\s*(?:==|!=|<|>|<=|>=)\s*(.+)$`) // orExpressionPattern matches "left || right" for fallback literal/expression checking orExpressionPattern = regexp.MustCompile(`^(.+?)\s*\|\|\s*(.+)$`) ) @@ -250,40 +250,17 @@ func validateSingleExpression(expression string, opts ExpressionValidationOption } } - // Try to extract and validate property accesses from comparison expressions + // Validate both sides of comparison expressions recursively. if !allowed { - matches := comparisonExtractionRegex.FindAllStringSubmatch(expression, -1) - if len(matches) > 0 { - allPropertiesAllowed := true - for _, match := range matches { - if len(match) > 1 { - property := strings.TrimSpace(match[1]) - propertyAllowed := false - - if opts.NeedsStepsRe.MatchString(property) { - propertyAllowed = true - } else if opts.InputsRe.MatchString(property) { - propertyAllowed = true - } else if opts.WorkflowCallInputsRe.MatchString(property) { - propertyAllowed = true - } else if opts.AwInputsRe.MatchString(property) { - propertyAllowed = true - } else if opts.AwImportInputsRe != nil && opts.AwImportInputsRe.MatchString(property) { - propertyAllowed = true - } else if opts.EnvRe.MatchString(property) { - propertyAllowed = true - } else if _, ok := constants.AllowedExpressionsSet[property]; ok { - propertyAllowed = true - } - - if !propertyAllowed { - allPropertiesAllowed = false - break - } - } - } - - if allPropertiesAllowed && len(matches) > 0 { + comparisonMatch := comparisonExpressionPattern.FindStringSubmatch(expression) + if len(comparisonMatch) > 2 { + leftExpr := strings.TrimSpace(comparisonMatch[1]) + rightExpr := strings.TrimSpace(comparisonMatch[2]) + leftErr := validateSingleExpression(leftExpr, opts) + rightErr := validateSingleExpression(rightExpr, opts) + if leftExpr != "" && rightExpr != "" && + leftErr == nil && !containsExpressionInList(opts.UnauthorizedExpressions, leftExpr) && + rightErr == nil && !containsExpressionInList(opts.UnauthorizedExpressions, rightExpr) { allowed = true } } diff --git a/pkg/workflow/js/safe_outputs_tools.json b/pkg/workflow/js/safe_outputs_tools.json index ea91de1d0a1..d72e00d3679 100644 --- a/pkg/workflow/js/safe_outputs_tools.json +++ b/pkg/workflow/js/safe_outputs_tools.json @@ -1311,7 +1311,7 @@ }, "value": { "type": "string", - "description": "Field value to set. For single-select fields, this must match an existing option name. For date fields, use YYYY-MM-DD." + "description": "Field value to set. For single-select fields, this must match an existing option name (e.g., \"P1\" or \"High\"). For date fields, use format: YYYY-MM-DD (for example, 2026-05-08)." }, "secrecy": { "type": "string", diff --git a/pkg/workflow/sanitize_incoming_text_fuzz_test.go b/pkg/workflow/sanitize_incoming_text_fuzz_test.go index 133482b59ee..2fa7f8a7a9a 100644 --- a/pkg/workflow/sanitize_incoming_text_fuzz_test.go +++ b/pkg/workflow/sanitize_incoming_text_fuzz_test.go @@ -1,4 +1,4 @@ -//go:build !integration +//go:build integration package workflow @@ -180,6 +180,13 @@ func isWordChar(b byte) bool { return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') } +func isExpectedError(err error) bool { + if err == nil { + return true + } + return strings.Contains(err.Error(), "exit status") +} + // sanitizeIncomingTextTestInput represents the JSON input for the fuzz test harness type sanitizeIncomingTextTestInput struct { Text string `json:"text"` diff --git a/pkg/workflow/sanitize_label_fuzz_test.go b/pkg/workflow/sanitize_label_fuzz_test.go index 57ed59c39e9..da56c6d4442 100644 --- a/pkg/workflow/sanitize_label_fuzz_test.go +++ b/pkg/workflow/sanitize_label_fuzz_test.go @@ -188,6 +188,10 @@ type sanitizeLabelContentTestResult struct { Error *string `json:"error"` } +func isWordChar(b byte) bool { + return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') +} + // runSanitizeLabelContentTest runs the JavaScript sanitize_label_content test harness func runSanitizeLabelContentTest(text string) (*sanitizeLabelContentTestResult, error) { // Prepare input JSON diff --git a/pkg/workflow/sanitize_output_fuzz_test.go b/pkg/workflow/sanitize_output_fuzz_test.go index 83fbaf5e6e8..372e0b458bc 100644 --- a/pkg/workflow/sanitize_output_fuzz_test.go +++ b/pkg/workflow/sanitize_output_fuzz_test.go @@ -1,4 +1,4 @@ -//go:build !integration +//go:build integration package workflow diff --git a/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/playwright-cli-mode.golden b/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/playwright-cli-mode.golden index 924f3017bc6..50a94a09f08 100644 --- a/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/playwright-cli-mode.golden +++ b/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/playwright-cli-mode.golden @@ -337,7 +337,7 @@ jobs: - name: Install AWF binary run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 - name: Install Playwright CLI - run: npm install -g @playwright/cli@0.1.11 + run: npm install -g @playwright/cli@0.1.13 - name: Install Playwright CLI skills run: playwright-cli install --skills - name: Determine automatic lockdown mode for GitHub MCP Server diff --git a/scratchpad/dev.md b/scratchpad/dev.md index 391ae88aacb..89b6251b58c 100644 --- a/scratchpad/dev.md +++ b/scratchpad/dev.md @@ -1,7 +1,7 @@ # Developer Instructions -**Version**: 9.2 -**Last Updated**: 2026-05-07 +**Version**: 9.3 +**Last Updated**: 2026-05-08 **Purpose**: Consolidated development guidelines for GitHub Agentic Workflows This document consolidates specifications from the scratchpad directory into unified developer instructions. It provides architecture patterns, security guidelines, code organization rules, and testing practices. @@ -1843,6 +1843,19 @@ Compiled `workflow_dispatch` workflows now automatically propagate `aw_context` The `activation` artifact now uploads `aw_info.json` alongside `prompt.txt`. This makes run info and workflow overview data available to prompt generation and logging earlier in the job lifecycle (previously only available after the agent job). +**`engine.mcp.tool-timeout` rendered as numeric `toolTimeout` seconds** (PR #31007): + +The `engine.mcp.tool-timeout` frontmatter value is parsed from a Go duration string and rendered as numeric `toolTimeout` seconds in MCP gateway config JSON (`pkg/workflow/mcp_renderer.go` uses `durationStringToSeconds` before emitting `toolTimeout`). `session-timeout` remains a string field. + +```yaml +engine: + mcp: + session-timeout: "2m" # rendered as gateway.sessionTimeout: "2m" (string) + tool-timeout: 2m # rendered as gateway.toolTimeout: 120 (integer seconds) +``` + +Empty `tool-timeout` falls back to the MCP gateway default (`toolTimeout: 60` seconds). + **GHE Support** (`configure_gh_for_ghe.sh`): Workflows that call `gh` CLI commands on GitHub Enterprise Server domains should source `configure_gh_for_ghe.sh` before any `gh` calls. The script auto-detects the correct GHE host from environment variables (`GITHUB_SERVER_URL`, `GITHUB_ENTERPRISE_HOST`, `GITHUB_HOST`, or `GH_HOST`): @@ -2960,6 +2973,7 @@ These files are loaded automatically by compatible AI tools (e.g., GitHub Copilo --- **Document History**: +- v9.3 (2026-05-08): Maintenance tone scan — 0 tone issues found across all 63 spec files. Documented 1 new feature from PR #31007 in Workflow Patterns: `engine.mcp.tool-timeout` now parses Go duration strings and renders as numeric gateway `toolTimeout` seconds (contrasted with string `session-timeout` rendering). Coverage: 63 spec files (no new files). - v9.2 (2026-05-07): Maintenance tone scan — fixed 1 tone issue: `workflow-refactoring-patterns.md` (4 fixes: removed gamified "+X points" scoring from Benefits headings). Documented 3 new features from PR #30800: (1) `mcp-gateway.opentelemetry.headers` now accepts string-only format (migration from object format); (2) `aw_context` caller metadata propagation for compiled `workflow_dispatch` workflows (dispatch traceability via `aw_info.json`); (3) activation artifact now includes `aw_info.json` alongside `prompt.txt`. Coverage: 63 spec files (no new files). - v9.1 (2026-05-06): Maintenance tone scan — 0 tone issues found across all 63 spec files. No new spec files since v9.0. Coverage: 63 spec files (no new files). - v9.0 (2026-05-04): Maintenance tone scan — fixed 1 tone issue: `serena-tools-quick-reference.md` (1 fix: "12.32 KB (2.89% of all data) - highly efficient"→"12.32 KB (2.89% of all data)"). Documented PR #30072 engine domain registry pattern: updated Engine Interface Architecture section ("Adding a new engine" note about `engineDefaultDomains` map in `domains.go`), updated `adding-new-engines.md` with new "Firewall Domain Registration" pattern (Phase 1 checklist item + full code example for `engineDefaultDomains` map and model-specific domain functions). Coverage: 64 spec files (no new files).