From dfcb9ceaec99ddd442cc33d2915e5c918ec9c7d6 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Wed, 1 Jul 2026 15:13:27 -0700 Subject: [PATCH 1/3] fix(ci): make postprocess install-step swap tolerate trailing flags gh-aw v0.82+ emits the AWF install step with a trailing flag, e.g. `install_awf_binary.sh v0.27.16 --rootless`. The postprocess regex anchored the version to an immediate newline (`v[0-9.]+\n`), so the install-step replacement silently no-op'd while the `--skip-pull -> --build-local` rewrite still fired. This left every non-release lock file in a broken state: standalone bundle install + `--build-local`, which awf rejects at runtime: The --build-local flag requires a full repository checkout. It is not supported with the standalone bundle. Widen the regex to `v[0-9.]+[^\n]*\n` so trailing flags are tolerated, add a `--rootless` regression test, and recompile + postprocess all workflows so smoke/build-test suites build the firewall containers from source locally again. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-test.lock.yml | 32 +++++++++++++++-- .../workflows/ci-cd-gaps-assessment.lock.yml | 34 ++++++++++++++++-- .github/workflows/ci-doctor.lock.yml | 32 +++++++++++++++-- .../workflows/claude-token-optimizer.lock.yml | 32 +++++++++++++++-- .../claude-token-usage-analyzer.lock.yml | 34 ++++++++++++++++-- .../cli-flag-consistency-checker.lock.yml | 34 ++++++++++++++++-- .../config-consistency-auditor.lock.yml | 34 ++++++++++++++++-- .github/workflows/contribution-check.lock.yml | 32 +++++++++++++++-- .../copilot-token-optimizer.lock.yml | 32 +++++++++++++++-- .../copilot-token-usage-analyzer.lock.yml | 34 ++++++++++++++++-- .../dependency-security-monitor.lock.yml | 34 ++++++++++++++++-- .github/workflows/doc-maintainer.lock.yml | 34 ++++++++++++++++-- .../duplicate-code-detector.lock.yml | 34 ++++++++++++++++-- .github/workflows/export-audit.lock.yml | 32 +++++++++++++++-- .../firewall-issue-dispatcher.lock.yml | 34 ++++++++++++++++-- .../issue-duplication-detector.lock.yml | 32 +++++++++++++++-- .github/workflows/issue-monster.lock.yml | 34 ++++++++++++++++-- .../model-api-mapping-updater.lock.yml | 34 ++++++++++++++++-- .../pelis-agent-factory-advisor.lock.yml | 34 ++++++++++++++++-- .github/workflows/plan.lock.yml | 32 +++++++++++++++-- .github/workflows/red-team-benchmark.lock.yml | 34 ++++++++++++++++-- .../workflows/refactoring-scanner.lock.yml | 34 ++++++++++++++++-- .github/workflows/schema-sync.lock.yml | 34 ++++++++++++++++-- .../workflows/secret-digger-claude.lock.yml | 32 +++++++++++++++-- .../workflows/secret-digger-codex.lock.yml | 32 +++++++++++++++-- .../workflows/secret-digger-copilot.lock.yml | 32 +++++++++++++++-- .github/workflows/security-guard.lock.yml | 32 +++++++++++++++-- .github/workflows/security-review.lock.yml | 34 ++++++++++++++++-- ...self-hosted-runner-doctor-updater.lock.yml | 36 ++++++++++++++++--- .../self-hosted-runner-doctor.lock.yml | 34 ++++++++++++++++-- .github/workflows/smoke-chroot.lock.yml | 32 +++++++++++++++-- .github/workflows/smoke-claude.lock.yml | 34 ++++++++++++++++-- .github/workflows/smoke-codex.lock.yml | 34 ++++++++++++++++-- .../smoke-copilot-byok-aoai-apikey.lock.yml | 32 +++++++++++++++-- .../smoke-copilot-byok-aoai-entra.lock.yml | 34 ++++++++++++++++-- .github/workflows/smoke-copilot-byok.lock.yml | 34 ++++++++++++++++-- .github/workflows/smoke-copilot-pat.lock.yml | 34 ++++++++++++++++-- .github/workflows/smoke-copilot.lock.yml | 34 ++++++++++++++++-- .github/workflows/smoke-gemini.lock.yml | 34 ++++++++++++++++-- .github/workflows/smoke-otel-tracing.lock.yml | 34 ++++++++++++++++-- .github/workflows/smoke-services.lock.yml | 34 ++++++++++++++++-- .../workflows/test-coverage-improver.lock.yml | 32 +++++++++++++++-- .../workflows/test-coverage-reporter.lock.yml | 34 ++++++++++++++++-- .../test-hard-cap-ai-credits.lock.yml | 32 +++++++++++++++-- .../workflows/update-release-notes.lock.yml | 32 +++++++++++++++-- .../ci/postprocess-smoke-workflows.test.ts | 9 ++++- scripts/ci/postprocess-smoke-workflows.ts | 4 ++- 47 files changed, 1390 insertions(+), 121 deletions(-) diff --git a/.github/workflows/build-test.lock.yml b/.github/workflows/build-test.lock.yml index 6cebb0318..86bcea118 100644 --- a/.github/workflows/build-test.lock.yml +++ b/.github/workflows/build-test.lock.yml @@ -523,8 +523,36 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.16 --rootless + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '24' + package-manager-cache: false + - name: Install awf dependencies + run: npm ci + - name: Build awf + run: npm run build + - name: Install awf binary (local) + run: | + WORKSPACE_PATH="${GITHUB_WORKSPACE:-$(pwd)}" + NODE_BIN="$(command -v node)" + if [ ! -d "$WORKSPACE_PATH" ]; then + echo "Workspace path not found: $WORKSPACE_PATH" + exit 1 + fi + if [ ! -x "$NODE_BIN" ]; then + echo "Node binary not found: $NODE_BIN" + exit 1 + fi + if [ ! -d "/usr/local/bin" ]; then + echo "/usr/local/bin is missing" + exit 1 + fi + sudo tee /usr/local/bin/awf > /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < { it('should match unquoted /opt/gh-aw path', () => { @@ -49,6 +49,13 @@ describe('installStepRegex', () => { expect(installStepRegex.test(input)).toBe(true); }); + it('should match version followed by trailing --rootless flag (gh-aw v0.82+)', () => { + const input = + ' - name: Install AWF binary\n' + + ' run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.16 --rootless\n'; + expect(installStepRegex.test(input)).toBe(true); + }); + it('should not match step with wrong name', () => { const input = ' - name: Install something else\n' + diff --git a/scripts/ci/postprocess-smoke-workflows.ts b/scripts/ci/postprocess-smoke-workflows.ts index f5ccf27c4..f73be7902 100644 --- a/scripts/ci/postprocess-smoke-workflows.ts +++ b/scripts/ci/postprocess-smoke-workflows.ts @@ -46,8 +46,10 @@ const workflowPaths = fs.readdirSync(workflowsDir) // - "Install awf binary" or "Install AWF binary" step at any indent level // - run command invoking install_awf_binary.sh with a version // - path may or may not be double-quoted (newer gh-aw compilers quote it) +// - the version may be followed by trailing flags (e.g. --rootless) that newer +// gh-aw compilers append; tolerate anything up to the end of the line const installStepRegex = - /^(\s*)- name: Install [Aa][Ww][Ff] binary\n\1\s*run: bash "?(?:\/opt\/gh-aw|\$\{RUNNER_TEMP\}\/gh-aw)\/actions\/install_awf_binary\.sh"? v[0-9.]+\n/m; + /^(\s*)- name: Install [Aa][Ww][Ff] binary\n\1\s*run: bash "?(?:\/opt\/gh-aw|\$\{RUNNER_TEMP\}\/gh-aw)\/actions\/install_awf_binary\.sh"? v[0-9.]+[^\n]*\n/m; const installStepRegexGlobal = new RegExp(installStepRegex.source, 'gm'); function buildLocalInstallSteps(indent: string): string { From 84da964d72f0a177946bd72b6b95efd26deed38e Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Wed, 1 Jul 2026 15:27:35 -0700 Subject: [PATCH 2/3] fix(ci): declare smoke-services containers in frontmatter gh-aw v0.82 moved AWF network/host config out of CLI flags and into the awf-config.json (--config), and added native support for a top-level `services:` frontmatter section. The compiler now emits the GitHub Actions `services:` block into the agent job AND auto-derives `--allow-host-service-ports` from the declared port mappings. The old smoke-services post-processing (inject a services block + swap a `--enable-host-access` CLI flag) silently no-op'd against the new output: the agent job gained an `if:` line that broke the injection regex, and the `--enable-host-access` flag no longer exists. Result: smoke-services ran with no service containers and no host-service-port access. - Declare redis + postgres under a native `services:` section in smoke-services.md. - Remove the entire smoke-services special-case from postprocess-smoke-workflows.ts (now handled by the compiler). - Recompile + postprocess (also drops a stale codex env_key in secret-digger-codex that a prior recompile left behind). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../workflows/secret-digger-codex.lock.yml | 1 - .github/workflows/smoke-services.lock.yml | 19 ++- .github/workflows/smoke-services.md | 23 ++++ scripts/ci/postprocess-smoke-workflows.ts | 108 +----------------- 4 files changed, 46 insertions(+), 105 deletions(-) diff --git a/.github/workflows/secret-digger-codex.lock.yml b/.github/workflows/secret-digger-codex.lock.yml index d1c87ec42..066fc7eb7 100644 --- a/.github/workflows/secret-digger-codex.lock.yml +++ b/.github/workflows/secret-digger-codex.lock.yml @@ -1600,7 +1600,6 @@ jobs: [model_providers.openai-proxy] name = "OpenAI AWF proxy" base_url = "http://172.30.0.30:10000" - env_key = "OPENAI_API_KEY" supports_websockets = false [shell_environment_policy] inherit = "core" diff --git a/.github/workflows/smoke-services.lock.yml b/.github/workflows/smoke-services.lock.yml index dbce61b44..8f6d31287 100644 --- a/.github/workflows/smoke-services.lock.yml +++ b/.github/workflows/smoke-services.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"a4f69d3359b8f1f3bf493eb04b362746f5be3d3ae65fadb7cd042d74ceda6c33","body_hash":"bd3776b2df359abde3c1fa62396b06521da480fac038fbff9a3c68808f53e534","compiler_version":"v0.82.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"c999d2e131e024fe550c4f11bed299da30b78906c95053150dfc86a0b5950c65","body_hash":"bd3776b2df359abde3c1fa62396b06521da480fac038fbff9a3c68808f53e534","compiler_version":"v0.82.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"dadd6a0a0f4d1087fbd3e115776f28471388d6db","version":"v0.82.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.16","digest":"sha256:94bbf52b74d38e8117387e93e698f79d678dd3879faa0e57f2ea128eda8fb507","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.16@sha256:94bbf52b74d38e8117387e93e698f79d678dd3879faa0e57f2ea128eda8fb507"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16","digest":"sha256:bbad2f109b97a4b3375ad371a5300d42bc9251dad61cd7bc66380cad8501cf94","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16@sha256:bbad2f109b97a4b3375ad371a5300d42bc9251dad61cd7bc66380cad8501cf94"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.16","digest":"sha256:483d6a8086752a02d581d7a42629b741e3f2fa9f3a6a10320590cf881638dad3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.16@sha256:483d6a8086752a02d581d7a42629b741e3f2fa9f3a6a10320590cf881638dad3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.32","digest":"sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.32@sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.5.0","digest":"sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4","pinned_image":"ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4"}]} # This file was automatically generated by gh-aw (v0.82.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -412,6 +412,21 @@ jobs: needs: activation if: needs.activation.outputs.daily_ai_credits_exceeded != 'true' runs-on: ubuntu-latest + services: + postgres: + env: + POSTGRES_DB: smoketest + POSTGRES_PASSWORD: testpass + POSTGRES_USER: postgres + image: postgres:15-alpine + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + ports: + - 5432:5432 + redis: + image: redis:7-alpine + options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 + ports: + - 6379:6379 permissions: actions: read contents: read @@ -864,7 +879,7 @@ jobs: fi fi # shellcheck disable=SC1003,SC2016,SC2086 - awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --session-state-dir /tmp/gh-aw/sandbox/agent/session-state --build-local \ + awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --allow-host-service-ports "${{ job.services['postgres'].ports['5432'] }},${{ job.services['redis'].ports['6379'] }}" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --session-state-dir /tmp/gh-aw/sandbox/agent/session-state --build-local \ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 diff --git a/.github/workflows/smoke-services.md b/.github/workflows/smoke-services.md index 18b2692b0..40ee29f62 100644 --- a/.github/workflows/smoke-services.md +++ b/.github/workflows/smoke-services.md @@ -16,6 +16,29 @@ permissions: actions: read name: Smoke Services engine: copilot +services: + redis: + image: redis:7-alpine + ports: + - 6379:6379 + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + postgres: + image: postgres:15-alpine + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: testpass + POSTGRES_DB: smoketest + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 network: allowed: - defaults diff --git a/scripts/ci/postprocess-smoke-workflows.ts b/scripts/ci/postprocess-smoke-workflows.ts index f73be7902..eb09ef797 100644 --- a/scripts/ci/postprocess-smoke-workflows.ts +++ b/scripts/ci/postprocess-smoke-workflows.ts @@ -572,108 +572,12 @@ for (const workflowPath of workflowPaths) { } } - // For smoke-services: inject GitHub Actions services block (Redis + PostgreSQL) into the - // agent job and replace --enable-host-access with --allow-host-service-ports 6379,5432. - // The gh-aw compiler does not natively support GitHub Actions `services:` in the - // frontmatter, so we inject them via post-processing. These services are required for - // the smoke test to connect to Redis and PostgreSQL via host.docker.internal. - const isServicesSmoke = workflowPath.includes('smoke-services.lock.yml'); - if (isServicesSmoke) { - // Inject services block after the agent job's "runs-on: ubuntu-latest" line. - // The agent job uses `needs: activation` (single value) to distinguish it from the - // detection job which uses a multi-line `needs:` array. - const agentJobServicesBlock = - ' services:\n' + - ' redis:\n' + - ' image: redis:7-alpine\n' + - ' ports:\n' + - ' - 6379:6379\n' + - ' options: >-\n' + - ' --health-cmd "redis-cli ping"\n' + - ' --health-interval 10s\n' + - ' --health-timeout 5s\n' + - ' --health-retries 5\n' + - ' postgres:\n' + - ' image: postgres:15-alpine\n' + - ' env:\n' + - ' POSTGRES_USER: postgres\n' + - ' POSTGRES_PASSWORD: testpass\n' + - ' POSTGRES_DB: smoketest\n' + - ' ports:\n' + - ' - 5432:5432\n' + - ' options: >-\n' + - ' --health-cmd pg_isready\n' + - ' --health-interval 10s\n' + - ' --health-timeout 5s\n' + - ' --health-retries 5\n'; - - // Match the agent job's needs/runs-on block (unique pattern: single-value needs) - // followed immediately by permissions or services. Use flexible whitespace to - // tolerate compiler indentation changes and handle both fresh and already-processed files. - // The agent job has `needs: activation` (single string value); the detection job uses - // a multi-value array (`needs:\n - activation\n - agent`), making this unique. - const agentJobNeedsRunsOnRegex = - /^( {2}agent:\n {4}needs: activation\n {4}runs-on: ubuntu-latest\n)( {4}permissions:)/m; - const agentJobWithServicesRegex = - /^( {2}agent:\n {4}needs: activation\n {4}runs-on: ubuntu-latest\n {4}services:)/m; - - if (!agentJobWithServicesRegex.test(content)) { - if (agentJobNeedsRunsOnRegex.test(content)) { - // No services block yet — inject it - content = content.replace( - agentJobNeedsRunsOnRegex, - `$1${agentJobServicesBlock}$2` - ); - modified = true; - console.log(` Injected services block (Redis + PostgreSQL) into agent job`); - } else { - console.warn( - ` WARNING: Could not find agent job pattern to inject services block. ` + - `The compiled lock file may have changed structure. Manual review required.` - ); - } - } else { - console.log(` Services block already present in agent job`); - } - - // Replace --enable-host-access with --allow-host-service-ports 6379,5432 - // only in the agent job's awf invocation (not the detection job). - // The agent job's command is identifiable by its long --allow-domains list enclosed - // in single quotes (the detection job uses a shorter unquoted domain list). We match - // only within a single line and bound the match with the later --build-local flag to - // avoid cross-line over-matching. - // --allow-domains '...' --enable-host-access --build-local - const agentJobEnableHostAccessRegex = - /(--allow-domains '[^']*' [^\n]* )--enable-host-access( --build-local)/; - const agentJobHostServicePortsRegex = - /(--allow-domains '[^']*' [^\n]* )--allow-host-service-ports 6379,5432( --build-local)/; - - if (!agentJobHostServicePortsRegex.test(content)) { - if (agentJobEnableHostAccessRegex.test(content)) { - const matchCount = (content.match(new RegExp(agentJobEnableHostAccessRegex.source, 'g')) || []).length; - if (matchCount > 1) { - console.warn( - ` WARNING: Found ${matchCount} matches for agent job --enable-host-access pattern. ` + - `Only the first will be replaced. Manual review recommended.` - ); - } - content = content.replace( - agentJobEnableHostAccessRegex, - `$1--allow-host-service-ports 6379,5432$2` - ); - modified = true; - console.log(` Replaced --enable-host-access with --allow-host-service-ports 6379,5432 in agent job`); - } else { - console.warn( - ` WARNING: Could not find --enable-host-access in agent job awf command. ` + - `The compiled lock file may have changed structure. Manual review required.` - ); - } - } else { - console.log(` --allow-host-service-ports 6379,5432 already present in agent job`); - } - } - + // NOTE: smoke-services no longer needs post-processing to add its Redis/PostgreSQL + // service containers or host-service-port access. gh-aw v0.82+ natively supports a + // top-level `services:` frontmatter section: the compiler emits the GitHub Actions + // `services:` block into the agent job AND auto-derives --allow-host-service-ports + // (persisted as security.allowHostServicePorts in awf-config.json) from the declared + // port mappings. The services are declared directly in smoke-services.md. // The step downloads a private action but is never used in these jobs, // causing 401 Unauthorized failures when permissions: {} is set. From 54cbf13293c2663c87c8cadf9ff35bba86de3cdd Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Wed, 1 Jul 2026 15:39:50 -0700 Subject: [PATCH 3/3] fix(ci): collapse duplicate consecutive Setup Node.js steps The local-build install steps inject a Setup Node.js step; some workflows already emit an identical one immediately before, yielding duplicate consecutive steps. Add a backreference-guarded post-pass that collapses only byte-identical consecutive blocks, so workflows without one keep the injected step. Resolves the Copilot review nit on smoke-claude/smoke-gemini. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/red-team-benchmark.lock.yml | 5 --- .../workflows/secret-digger-claude.lock.yml | 10 ------ .github/workflows/smoke-claude.lock.yml | 5 --- .github/workflows/smoke-gemini.lock.yml | 5 --- .../ci/postprocess-smoke-workflows.test.ts | 35 ++++++++++++++++++- scripts/ci/postprocess-smoke-workflows.ts | 14 ++++++++ 6 files changed, 48 insertions(+), 26 deletions(-) diff --git a/.github/workflows/red-team-benchmark.lock.yml b/.github/workflows/red-team-benchmark.lock.yml index 94da323ce..57157599e 100644 --- a/.github/workflows/red-team-benchmark.lock.yml +++ b/.github/workflows/red-team-benchmark.lock.yml @@ -508,11 +508,6 @@ jobs: with: node-version: '24' package-manager-cache: false - - name: Setup Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: '24' - package-manager-cache: false - name: Install awf dependencies run: npm ci - name: Build awf diff --git a/.github/workflows/secret-digger-claude.lock.yml b/.github/workflows/secret-digger-claude.lock.yml index 2fa081ef7..35b012b65 100644 --- a/.github/workflows/secret-digger-claude.lock.yml +++ b/.github/workflows/secret-digger-claude.lock.yml @@ -428,11 +428,6 @@ jobs: with: node-version: '24' package-manager-cache: false - - name: Setup Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: '24' - package-manager-cache: false - name: Install awf dependencies run: npm ci - name: Build awf @@ -1360,11 +1355,6 @@ jobs: with: node-version: '24' package-manager-cache: false - - name: Setup Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: '24' - package-manager-cache: false - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index d1e43bbc3..5f4197fea 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -478,11 +478,6 @@ jobs: with: node-version: '24' package-manager-cache: false - - name: Setup Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: '24' - package-manager-cache: false - name: Install awf dependencies run: npm ci - name: Build awf diff --git a/.github/workflows/smoke-gemini.lock.yml b/.github/workflows/smoke-gemini.lock.yml index 58ad355ef..e894c8834 100644 --- a/.github/workflows/smoke-gemini.lock.yml +++ b/.github/workflows/smoke-gemini.lock.yml @@ -501,11 +501,6 @@ jobs: with: node-version: '24' package-manager-cache: false - - name: Setup Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: '24' - package-manager-cache: false - name: Install awf dependencies run: npm ci - name: Build awf diff --git a/scripts/ci/postprocess-smoke-workflows.test.ts b/scripts/ci/postprocess-smoke-workflows.test.ts index 5e324c4c6..60d613103 100644 --- a/scripts/ci/postprocess-smoke-workflows.test.ts +++ b/scripts/ci/postprocess-smoke-workflows.test.ts @@ -73,7 +73,40 @@ describe('installStepRegex', () => { }); }); -// ── Cache-memory security hardening regex tests ─────────────────────────── +// ── Duplicate Setup Node.js collapse regex test ─────────────────────────── +// Mirrors duplicateSetupNodeRegex in postprocess-smoke-workflows.ts. The +// backreference guarantees only byte-identical consecutive blocks collapse. +const duplicateSetupNodeRegex = + /^( {6}- name: Setup Node\.js\n {8}uses: actions\/setup-node@[0-9a-f]+ # v[0-9.]+\n {8}with:\n {10}node-version: '[^']*'\n {10}package-manager-cache: false\n)\1/m; + +describe('duplicateSetupNodeRegex', () => { + const block = [ + " - name: Setup Node.js", + " uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0", + " with:", + " node-version: '24'", + " package-manager-cache: false", + "", + ].join("\n"); + + it('collapses two consecutive identical Setup Node.js blocks into one', () => { + const input = block + block + " - name: Install awf dependencies\n"; + const output = input.replace(duplicateSetupNodeRegex, '$1'); + expect(output).toBe(block + " - name: Install awf dependencies\n"); + }); + + it('does not collapse a single Setup Node.js block', () => { + const input = block + " - name: Install awf dependencies\n"; + expect(duplicateSetupNodeRegex.test(input)).toBe(false); + }); + + it('does not collapse consecutive Setup Node.js blocks that differ', () => { + const differing = block.replace("node-version: '24'", "node-version: '20'"); + const input = block + differing + " - name: Install awf dependencies\n"; + expect(duplicateSetupNodeRegex.test(input)).toBe(false); + }); +}); + // Mirrors the patterns in postprocess-smoke-workflows.ts. // If those patterns change, these tests will catch regressions. diff --git a/scripts/ci/postprocess-smoke-workflows.ts b/scripts/ci/postprocess-smoke-workflows.ts index eb09ef797..8731f3bca 100644 --- a/scripts/ci/postprocess-smoke-workflows.ts +++ b/scripts/ci/postprocess-smoke-workflows.ts @@ -355,6 +355,20 @@ for (const workflowPath of workflowPaths) { console.log(` Replaced ${matches.length} awf install step(s) with local build`); } + // Collapse a duplicate "Setup Node.js" step: buildLocalInstallSteps injects a + // Setup Node.js step (needed for workflows the compiler leaves without one), but + // some workflows already emit an identical Setup Node.js immediately before the + // install step, producing two consecutive identical steps. The backreference only + // matches byte-identical consecutive blocks, so this never removes a differing or + // required step. Loop until stable in case of >2 repeats. + const duplicateSetupNodeRegex = + /^( {6}- name: Setup Node\.js\n {8}uses: actions\/setup-node@[0-9a-f]+ # v[0-9.]+\n {8}with:\n {10}node-version: '[^']*'\n {10}package-manager-cache: false\n)\1/m; + while (duplicateSetupNodeRegex.test(content)) { + content = content.replace(duplicateSetupNodeRegex, '$1'); + modified = true; + console.log(` Collapsed duplicate consecutive Setup Node.js step`); + } + // Ensure a "Checkout repository" step exists before "Install awf dependencies" // in every job. The gh-aw compiler may add jobs (e.g. detection) that reference // install_awf_binary.sh but don't include a checkout step. After we replace the