From e76fea8c787b4ddaf6ce638fb8558d5ae95ec95c Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Thu, 18 Jun 2026 15:36:55 -0700 Subject: [PATCH 1/7] fix(containers): apt install fallback to archive.ubuntu.com The agent and squid Dockerfiles rewrite the apt mirror to azure.archive.ubuntu.com (faster on Azure-hosted GitHub runners). The existing fallback to archive.ubuntu.com only triggered when 'apt-get update' reported 'Failed to fetch' (metadata failures). When the Azure mirror's metadata was reachable but the package (.deb) downloads timed out during 'apt-get install', the retry path re-ran apt_update_retry (which succeeded, leaving sources pointed at azure) and retried the install against the same failing mirror, causing slow retries and ultimately a hard build failure. Extract the mirror rewrite into a shared force_archive_mirror helper, have apt_update_retry reuse it, and add apt_install_retry / apt_upgrade_retry that force the archive.ubuntu.com mirror before retrying. This covers the install and upgrade phases, not just update, so a flaky Azure mirror no longer fails the build (archive.ubuntu.com is already in the firewall allowlist). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- containers/agent/Dockerfile | 109 ++++++++++++++++++++++++------------ containers/squid/Dockerfile | 29 ++++++---- 2 files changed, 93 insertions(+), 45 deletions(-) diff --git a/containers/agent/Dockerfile b/containers/agent/Dockerfile index 386e157a5..c09713b50 100644 --- a/containers/agent/Dockerfile +++ b/containers/agent/Dockerfile @@ -32,13 +32,8 @@ RUN if getent hosts azure.archive.ubuntu.com >/dev/null 2>&1; then \ # Note: Some packages may already exist in runner-like base images, apt handles this gracefully # apt_update_retry: retries up to 3 times with backoff; if all fail, reverts to archive.ubuntu.com RUN set -eux; \ - apt_update_retry() { \ - local i; for i in 1 2 3; do \ - rm -rf /var/lib/apt/lists/* && apt-get update 2>&1 | tee /tmp/apt-update.log && \ - if ! grep -q "Failed to fetch" /tmp/apt-update.log; then return 0; fi; \ - echo "apt-get update attempt $i/3 had fetch failures, retrying in $((i*10))s..." >&2; sleep $((i*10)); \ - done; \ - echo "All apt-get update retries failed, falling back to archive.ubuntu.com..." >&2; \ + force_archive_mirror() { \ + echo "Falling back to archive.ubuntu.com mirror..." >&2; \ if [ -f /etc/apt/sources.list ]; then \ sed -i 's|http://azure.archive.ubuntu.com|http://archive.ubuntu.com|g' /etc/apt/sources.list; \ sed -i 's|http://security.ubuntu.com|http://archive.ubuntu.com|g' /etc/apt/sources.list 2>/dev/null || true; \ @@ -49,12 +44,24 @@ RUN set -eux; \ fi; \ rm -rf /var/lib/apt/lists/* && apt-get update; \ }; \ + apt_update_retry() { \ + local i; for i in 1 2 3; do \ + rm -rf /var/lib/apt/lists/* && apt-get update 2>&1 | tee /tmp/apt-update.log && \ + if ! grep -q "Failed to fetch" /tmp/apt-update.log; then return 0; fi; \ + echo "apt-get update attempt $i/3 had fetch failures, retrying in $((i*10))s..." >&2; sleep $((i*10)); \ + done; \ + echo "All apt-get update retries failed, falling back to archive.ubuntu.com..." >&2; \ + force_archive_mirror; \ + }; \ + apt_install_retry() { \ + apt-get install -y --no-install-recommends "$@" && return 0; \ + echo "apt-get install failed (likely mirror fetch timeout), forcing archive.ubuntu.com and retrying..." >&2; \ + force_archive_mirror; \ + apt-get install -y --no-install-recommends "$@"; \ + }; \ PKGS="iptables curl ca-certificates git gh gnupg dnsutils net-tools netcat-openbsd gosu libcap2-bin"; \ apt_update_retry && \ - ( apt-get install -y --no-install-recommends $PKGS || \ - (echo "apt-get install failed, retrying with fresh package index..." && \ - apt_update_retry && \ - apt-get install -y --no-install-recommends $PKGS) ) && \ + apt_install_retry $PKGS && \ # Prefer system binaries over runner toolcache (e.g., act images) for Node checks. export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH" && \ # Install Node.js 22 from NodeSource @@ -83,15 +90,11 @@ RUN set -eux; \ # These packages are commonly needed by workflows and avoid agents spending time installing them manually # See: https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2204-Readme.md RUN set -eux; \ - apt_update_retry() { \ - local i; for i in 1 2 3; do \ - rm -rf /var/lib/apt/lists/* && apt-get update 2>&1 | tee /tmp/apt-update.log && \ - if ! grep -q "Failed to fetch" /tmp/apt-update.log; then return 0; fi; \ - echo "apt-get update attempt $i/3 had fetch failures, retrying in $((i*10))s..." >&2; sleep $((i*10)); \ - done; \ - echo "All apt-get update retries failed, falling back to archive.ubuntu.com..." >&2; \ + force_archive_mirror() { \ + echo "Falling back to archive.ubuntu.com mirror..." >&2; \ if [ -f /etc/apt/sources.list ]; then \ sed -i 's|http://azure.archive.ubuntu.com|http://archive.ubuntu.com|g' /etc/apt/sources.list; \ + sed -i 's|http://security.ubuntu.com|http://archive.ubuntu.com|g' /etc/apt/sources.list 2>/dev/null || true; \ fi; \ if [ -d /etc/apt/sources.list.d ]; then \ find /etc/apt/sources.list.d -name '*.sources' -exec \ @@ -99,26 +102,34 @@ RUN set -eux; \ fi; \ rm -rf /var/lib/apt/lists/* && apt-get update; \ }; \ + apt_update_retry() { \ + local i; for i in 1 2 3; do \ + rm -rf /var/lib/apt/lists/* && apt-get update 2>&1 | tee /tmp/apt-update.log && \ + if ! grep -q "Failed to fetch" /tmp/apt-update.log; then return 0; fi; \ + echo "apt-get update attempt $i/3 had fetch failures, retrying in $((i*10))s..." >&2; sleep $((i*10)); \ + done; \ + echo "All apt-get update retries failed, falling back to archive.ubuntu.com..." >&2; \ + force_archive_mirror; \ + }; \ + apt_install_retry() { \ + apt-get install -y --no-install-recommends "$@" && return 0; \ + echo "apt-get install failed (likely mirror fetch timeout), forcing archive.ubuntu.com and retrying..." >&2; \ + force_archive_mirror; \ + apt-get install -y --no-install-recommends "$@"; \ + }; \ PARITY_PKGS="libgdiplus libev-dev libssl-dev php-intl php-gd"; \ apt_update_retry && \ - ( apt-get install -y --no-install-recommends $PARITY_PKGS || \ - (echo "apt-get install failed, retrying with fresh package index..." && \ - apt_update_retry && \ - apt-get install -y --no-install-recommends $PARITY_PKGS) ) && \ + apt_install_retry $PARITY_PKGS && \ rm -rf /var/lib/apt/lists/* # Upgrade all packages to pick up security patches # Addresses CVE-2023-44487 (HTTP/2 Rapid Reset) and other known vulnerabilities # Retry logic handles transient mirror sync failures during apt-get update -RUN apt_update_retry() { \ - local i; for i in 1 2 3; do \ - rm -rf /var/lib/apt/lists/* && apt-get update 2>&1 | tee /tmp/apt-update.log && \ - if ! grep -q "Failed to fetch" /tmp/apt-update.log; then return 0; fi; \ - echo "apt-get update attempt $i/3 had fetch failures, retrying in $((i*10))s..." >&2; sleep $((i*10)); \ - done; \ - echo "All apt-get update retries failed, falling back to archive.ubuntu.com..." >&2; \ +RUN force_archive_mirror() { \ + echo "Falling back to archive.ubuntu.com mirror..." >&2; \ if [ -f /etc/apt/sources.list ]; then \ sed -i 's|http://azure.archive.ubuntu.com|http://archive.ubuntu.com|g' /etc/apt/sources.list; \ + sed -i 's|http://security.ubuntu.com|http://archive.ubuntu.com|g' /etc/apt/sources.list 2>/dev/null || true; \ fi; \ if [ -d /etc/apt/sources.list.d ]; then \ find /etc/apt/sources.list.d -name '*.sources' -exec \ @@ -126,10 +137,22 @@ RUN apt_update_retry() { \ fi; \ rm -rf /var/lib/apt/lists/* && apt-get update; \ }; \ + apt_update_retry() { \ + local i; for i in 1 2 3; do \ + rm -rf /var/lib/apt/lists/* && apt-get update 2>&1 | tee /tmp/apt-update.log && \ + if ! grep -q "Failed to fetch" /tmp/apt-update.log; then return 0; fi; \ + echo "apt-get update attempt $i/3 had fetch failures, retrying in $((i*10))s..." >&2; sleep $((i*10)); \ + done; \ + echo "All apt-get update retries failed, falling back to archive.ubuntu.com..." >&2; \ + force_archive_mirror; \ + }; \ + apt_upgrade_retry() { \ + apt-get upgrade -y && return 0; \ + echo "apt-get upgrade failed (likely mirror fetch timeout), forcing archive.ubuntu.com and retrying..." >&2; \ + force_archive_mirror && apt-get upgrade -y; \ + }; \ apt_update_retry && \ - apt-get upgrade -y && rm -rf /var/lib/apt/lists/* || \ - (echo "apt-get upgrade failed, retrying with fresh package index..." && \ - apt_update_retry && apt-get upgrade -y && rm -rf /var/lib/apt/lists/*) + apt_upgrade_retry && rm -rf /var/lib/apt/lists/* # Create non-root user with UID/GID matching host user # This allows the user command to run with appropriate permissions @@ -171,11 +194,27 @@ RUN chmod +x /usr/local/bin/setup-iptables.sh /usr/local/bin/entrypoint.sh /usr/ # __fprintf_chk) so the resulting .so loads on musl-based hosts (Alpine/ARC runners) COPY one-shot-token/one-shot-token.c /tmp/one-shot-token.c RUN set -eux; \ + force_archive_mirror() { \ + echo "Falling back to archive.ubuntu.com mirror..." >&2; \ + if [ -f /etc/apt/sources.list ]; then \ + sed -i 's|http://azure.archive.ubuntu.com|http://archive.ubuntu.com|g' /etc/apt/sources.list; \ + sed -i 's|http://security.ubuntu.com|http://archive.ubuntu.com|g' /etc/apt/sources.list 2>/dev/null || true; \ + fi; \ + if [ -d /etc/apt/sources.list.d ]; then \ + find /etc/apt/sources.list.d -name '*.sources' -exec \ + sed -i 's|http://azure.archive.ubuntu.com|http://archive.ubuntu.com|g' {} + 2>/dev/null || true; \ + fi; \ + rm -rf /var/lib/apt/lists/* && apt-get update; \ + }; \ + apt_install_retry() { \ + apt-get install -y --no-install-recommends "$@" && return 0; \ + echo "apt-get install failed (likely mirror fetch timeout), forcing archive.ubuntu.com and retrying..." >&2; \ + force_archive_mirror; \ + apt-get install -y --no-install-recommends "$@"; \ + }; \ BUILD_PKGS="gcc libc6-dev binutils"; \ apt-get update && \ - ( apt-get install -y --no-install-recommends $BUILD_PKGS || \ - (rm -rf /var/lib/apt/lists/* && apt-get update && \ - apt-get install -y --no-install-recommends $BUILD_PKGS) ) && \ + apt_install_retry $BUILD_PKGS && \ gcc -shared -fPIC -fvisibility=hidden -O2 -Wall -s \ -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \ -o /usr/local/lib/one-shot-token.so /tmp/one-shot-token.c -ldl -lpthread && \ diff --git a/containers/squid/Dockerfile b/containers/squid/Dockerfile index 2ce30a67b..b5121b3d5 100644 --- a/containers/squid/Dockerfile +++ b/containers/squid/Dockerfile @@ -22,15 +22,11 @@ RUN if getent hosts azure.archive.ubuntu.com >/dev/null 2>&1; then \ # Install additional tools for debugging, healthcheck, and SSL Bump # apt_update_retry: retries up to 3 times with backoff; if all fail, reverts to archive.ubuntu.com RUN set -eux; \ - apt_update_retry() { \ - local i; for i in 1 2 3; do \ - rm -rf /var/lib/apt/lists/* && apt-get update 2>&1 | tee /tmp/apt-update.log && \ - if ! grep -q "Failed to fetch" /tmp/apt-update.log; then return 0; fi; \ - echo "apt-get update attempt $i/3 had fetch failures, retrying in $((i*10))s..." >&2; sleep $((i*10)); \ - done; \ - echo "All apt-get update retries failed, falling back to archive.ubuntu.com..." >&2; \ + force_archive_mirror() { \ + echo "Falling back to archive.ubuntu.com mirror..." >&2; \ if [ -f /etc/apt/sources.list ]; then \ sed -i 's|http://azure.archive.ubuntu.com|http://archive.ubuntu.com|g' /etc/apt/sources.list; \ + sed -i 's|http://security.ubuntu.com|http://archive.ubuntu.com|g' /etc/apt/sources.list 2>/dev/null || true; \ fi; \ if [ -d /etc/apt/sources.list.d ]; then \ find /etc/apt/sources.list.d -name '*.sources' -exec \ @@ -38,12 +34,25 @@ RUN set -eux; \ fi; \ rm -rf /var/lib/apt/lists/* && apt-get update; \ }; \ + apt_update_retry() { \ + local i; for i in 1 2 3; do \ + rm -rf /var/lib/apt/lists/* && apt-get update 2>&1 | tee /tmp/apt-update.log && \ + if ! grep -q "Failed to fetch" /tmp/apt-update.log; then return 0; fi; \ + echo "apt-get update attempt $i/3 had fetch failures, retrying in $((i*10))s..." >&2; sleep $((i*10)); \ + done; \ + echo "All apt-get update retries failed, falling back to archive.ubuntu.com..." >&2; \ + force_archive_mirror; \ + }; \ + apt_install_retry() { \ + apt-get install -y --no-install-recommends "$@" && return 0; \ + echo "apt-get install failed (likely mirror fetch timeout), forcing archive.ubuntu.com and retrying..." >&2; \ + force_archive_mirror; \ + apt-get install -y --no-install-recommends "$@"; \ + }; \ PKGS="curl dnsutils net-tools netcat-openbsd openssl squid-openssl"; \ apt_update_retry && \ apt-get install -y --only-upgrade gpgv && \ - ( apt-get install -y --no-install-recommends $PKGS || \ - (apt_update_retry && \ - apt-get install -y --no-install-recommends $PKGS) ) && \ + apt_install_retry $PKGS && \ rm -rf /var/lib/apt/lists/* # Create log directory and SSL database directory, ensure proxy user owns them From 46082383b1fb27572cb846a43aed97982ce9dda1 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Thu, 18 Jun 2026 15:47:52 -0700 Subject: [PATCH 2/7] fix(containers): harden apt retry helpers per review Address review feedback on the apt mirror fallback helpers: - force_archive_mirror: also rewrite security.ubuntu.com (not just azure.archive.ubuntu.com) inside deb822 /etc/apt/sources.list.d/*.sources entries, matching the /etc/apt/sources.list branch. Otherwise, if the initial Azure-mirror rewrite never ran (e.g. DNS failure) and the base image uses .sources files, the fallback would leave security.ubuntu.com in place and apt-get update could keep failing. - apt_update_retry: stop masking apt-get update's exit code. The previous 'apt-get update 2>&1 | tee' pipeline returned tee's status (0) under /bin/sh (no pipefail), so a non-"Failed to fetch" failure (e.g. a dpkg lock error) was treated as success and never retried. Redirect to a log file, capture the real exit code, and retry/fall back when either the command fails or "Failed to fetch" appears. Applied consistently across all apt blocks in both Dockerfiles. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- containers/agent/Dockerfile | 45 ++++++++++++++++++++++++++----------- containers/squid/Dockerfile | 14 ++++++++---- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/containers/agent/Dockerfile b/containers/agent/Dockerfile index c09713b50..1e1bb1e4d 100644 --- a/containers/agent/Dockerfile +++ b/containers/agent/Dockerfile @@ -40,15 +40,21 @@ RUN set -eux; \ fi; \ if [ -d /etc/apt/sources.list.d ]; then \ find /etc/apt/sources.list.d -name '*.sources' -exec \ - sed -i 's|http://azure.archive.ubuntu.com|http://archive.ubuntu.com|g' {} + 2>/dev/null || true; \ + sed -i -e 's|http://azure.archive.ubuntu.com|http://archive.ubuntu.com|g' \ + -e 's|http://security.ubuntu.com|http://archive.ubuntu.com|g' {} + 2>/dev/null || true; \ fi; \ rm -rf /var/lib/apt/lists/* && apt-get update; \ }; \ apt_update_retry() { \ local i; for i in 1 2 3; do \ - rm -rf /var/lib/apt/lists/* && apt-get update 2>&1 | tee /tmp/apt-update.log && \ - if ! grep -q "Failed to fetch" /tmp/apt-update.log; then return 0; fi; \ - echo "apt-get update attempt $i/3 had fetch failures, retrying in $((i*10))s..." >&2; sleep $((i*10)); \ + rm -rf /var/lib/apt/lists/*; \ + if apt-get update > /tmp/apt-update.log 2>&1; then \ + cat /tmp/apt-update.log; \ + if ! grep -q "Failed to fetch" /tmp/apt-update.log; then return 0; fi; \ + else \ + cat /tmp/apt-update.log; \ + fi; \ + echo "apt-get update attempt $i/3 failed or had fetch failures, retrying in $((i*10))s..." >&2; sleep $((i*10)); \ done; \ echo "All apt-get update retries failed, falling back to archive.ubuntu.com..." >&2; \ force_archive_mirror; \ @@ -98,15 +104,21 @@ RUN set -eux; \ fi; \ if [ -d /etc/apt/sources.list.d ]; then \ find /etc/apt/sources.list.d -name '*.sources' -exec \ - sed -i 's|http://azure.archive.ubuntu.com|http://archive.ubuntu.com|g' {} + 2>/dev/null || true; \ + sed -i -e 's|http://azure.archive.ubuntu.com|http://archive.ubuntu.com|g' \ + -e 's|http://security.ubuntu.com|http://archive.ubuntu.com|g' {} + 2>/dev/null || true; \ fi; \ rm -rf /var/lib/apt/lists/* && apt-get update; \ }; \ apt_update_retry() { \ local i; for i in 1 2 3; do \ - rm -rf /var/lib/apt/lists/* && apt-get update 2>&1 | tee /tmp/apt-update.log && \ - if ! grep -q "Failed to fetch" /tmp/apt-update.log; then return 0; fi; \ - echo "apt-get update attempt $i/3 had fetch failures, retrying in $((i*10))s..." >&2; sleep $((i*10)); \ + rm -rf /var/lib/apt/lists/*; \ + if apt-get update > /tmp/apt-update.log 2>&1; then \ + cat /tmp/apt-update.log; \ + if ! grep -q "Failed to fetch" /tmp/apt-update.log; then return 0; fi; \ + else \ + cat /tmp/apt-update.log; \ + fi; \ + echo "apt-get update attempt $i/3 failed or had fetch failures, retrying in $((i*10))s..." >&2; sleep $((i*10)); \ done; \ echo "All apt-get update retries failed, falling back to archive.ubuntu.com..." >&2; \ force_archive_mirror; \ @@ -133,15 +145,21 @@ RUN force_archive_mirror() { \ fi; \ if [ -d /etc/apt/sources.list.d ]; then \ find /etc/apt/sources.list.d -name '*.sources' -exec \ - sed -i 's|http://azure.archive.ubuntu.com|http://archive.ubuntu.com|g' {} + 2>/dev/null || true; \ + sed -i -e 's|http://azure.archive.ubuntu.com|http://archive.ubuntu.com|g' \ + -e 's|http://security.ubuntu.com|http://archive.ubuntu.com|g' {} + 2>/dev/null || true; \ fi; \ rm -rf /var/lib/apt/lists/* && apt-get update; \ }; \ apt_update_retry() { \ local i; for i in 1 2 3; do \ - rm -rf /var/lib/apt/lists/* && apt-get update 2>&1 | tee /tmp/apt-update.log && \ - if ! grep -q "Failed to fetch" /tmp/apt-update.log; then return 0; fi; \ - echo "apt-get update attempt $i/3 had fetch failures, retrying in $((i*10))s..." >&2; sleep $((i*10)); \ + rm -rf /var/lib/apt/lists/*; \ + if apt-get update > /tmp/apt-update.log 2>&1; then \ + cat /tmp/apt-update.log; \ + if ! grep -q "Failed to fetch" /tmp/apt-update.log; then return 0; fi; \ + else \ + cat /tmp/apt-update.log; \ + fi; \ + echo "apt-get update attempt $i/3 failed or had fetch failures, retrying in $((i*10))s..." >&2; sleep $((i*10)); \ done; \ echo "All apt-get update retries failed, falling back to archive.ubuntu.com..." >&2; \ force_archive_mirror; \ @@ -202,7 +220,8 @@ RUN set -eux; \ fi; \ if [ -d /etc/apt/sources.list.d ]; then \ find /etc/apt/sources.list.d -name '*.sources' -exec \ - sed -i 's|http://azure.archive.ubuntu.com|http://archive.ubuntu.com|g' {} + 2>/dev/null || true; \ + sed -i -e 's|http://azure.archive.ubuntu.com|http://archive.ubuntu.com|g' \ + -e 's|http://security.ubuntu.com|http://archive.ubuntu.com|g' {} + 2>/dev/null || true; \ fi; \ rm -rf /var/lib/apt/lists/* && apt-get update; \ }; \ diff --git a/containers/squid/Dockerfile b/containers/squid/Dockerfile index b5121b3d5..86b39e4a0 100644 --- a/containers/squid/Dockerfile +++ b/containers/squid/Dockerfile @@ -30,15 +30,21 @@ RUN set -eux; \ fi; \ if [ -d /etc/apt/sources.list.d ]; then \ find /etc/apt/sources.list.d -name '*.sources' -exec \ - sed -i 's|http://azure.archive.ubuntu.com|http://archive.ubuntu.com|g' {} + 2>/dev/null || true; \ + sed -i -e 's|http://azure.archive.ubuntu.com|http://archive.ubuntu.com|g' \ + -e 's|http://security.ubuntu.com|http://archive.ubuntu.com|g' {} + 2>/dev/null || true; \ fi; \ rm -rf /var/lib/apt/lists/* && apt-get update; \ }; \ apt_update_retry() { \ local i; for i in 1 2 3; do \ - rm -rf /var/lib/apt/lists/* && apt-get update 2>&1 | tee /tmp/apt-update.log && \ - if ! grep -q "Failed to fetch" /tmp/apt-update.log; then return 0; fi; \ - echo "apt-get update attempt $i/3 had fetch failures, retrying in $((i*10))s..." >&2; sleep $((i*10)); \ + rm -rf /var/lib/apt/lists/*; \ + if apt-get update > /tmp/apt-update.log 2>&1; then \ + cat /tmp/apt-update.log; \ + if ! grep -q "Failed to fetch" /tmp/apt-update.log; then return 0; fi; \ + else \ + cat /tmp/apt-update.log; \ + fi; \ + echo "apt-get update attempt $i/3 failed or had fetch failures, retrying in $((i*10))s..." >&2; sleep $((i*10)); \ done; \ echo "All apt-get update retries failed, falling back to archive.ubuntu.com..." >&2; \ force_archive_mirror; \ From a94888249b6ed7a047d4a4b3956d55e23f6ccc9a Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Thu, 18 Jun 2026 15:51:32 -0700 Subject: [PATCH 3/7] test(cli-proxy): raise timeout for real-gh runGhCommand tests The runGhCommand tests spawn the real `gh` binary, so they are subject to runner contention. Jest's 5s default could fire before the helper's own 30s COMMAND_TIMEOUT_MS, producing a spurious "Exceeded timeout of 5000 ms" failure on a slow runner (observed on Node 22). Give these tests a 30s timeout to match the internal command timeout. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- containers/cli-proxy/server.test.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/containers/cli-proxy/server.test.js b/containers/cli-proxy/server.test.js index aaf6256ca..f56b0d409 100644 --- a/containers/cli-proxy/server.test.js +++ b/containers/cli-proxy/server.test.js @@ -270,6 +270,10 @@ describe('buildExecEnv', () => { }); describe('runGhCommand', () => { + // These tests spawn the real `gh` binary, so they are sensitive to runner + // contention. Jest's 5s default is too tight under load; give them headroom. + const REAL_GH_TIMEOUT_MS = 30000; + it('should return stdout, stderr, and exitCode on success', async () => { const result = await runGhCommand(['--version'], process.env, null); expect(result).toHaveProperty('stdout'); @@ -277,12 +281,12 @@ describe('runGhCommand', () => { expect(result).toHaveProperty('exitCode'); expect(typeof result.stdout).toBe('string'); expect(typeof result.exitCode).toBe('number'); - }); + }, REAL_GH_TIMEOUT_MS); it('should return non-zero exitCode for invalid gh subcommand', async () => { const result = await runGhCommand(['__nonexistent_subcommand__'], process.env, null); expect(result.exitCode).not.toBe(0); - }); + }, REAL_GH_TIMEOUT_MS); it('should return non-zero exitCode when gh binary is not found', async () => { // Temporarily remove gh from PATH @@ -295,5 +299,5 @@ describe('runGhCommand', () => { } finally { process.env.PATH = savedPath; } - }); + }, REAL_GH_TIMEOUT_MS); }); From cde25ba72df14d3ffa999e2632c1550552e229c0 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Thu, 18 Jun 2026 17:25:54 -0700 Subject: [PATCH 4/7] chore(api-proxy): temporary raw /responses capture for cache probe NOT FOR MERGE. Captures raw OpenAI /responses upstream request bodies and usage-bearing SSE lines into /responses-debug/ so we can determine from CI artifacts why codex runs intermittently report cache_read_tokens=0: whether the prompt prefix is byte-stable across turns and whether input_tokens_details.cached_tokens is absent vs literally 0. Bodies contain the prompt but no credentials (API key rides in the Authorization header, never captured). Revert before any real merge. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- containers/api-proxy/Dockerfile | 3 +- .../api-proxy/_debug-responses-capture.js | 133 ++++++++++++++++++ containers/api-proxy/proxy-request.js | 2 + containers/api-proxy/token-tracker-http.js | 2 + 4 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 containers/api-proxy/_debug-responses-capture.js diff --git a/containers/api-proxy/Dockerfile b/containers/api-proxy/Dockerfile index 5d77393ee..801cda2b9 100644 --- a/containers/api-proxy/Dockerfile +++ b/containers/api-proxy/Dockerfile @@ -29,7 +29,8 @@ COPY server.js logging.js metrics.js rate-limiter.js \ deprecated-header-tracker.js billing-headers.js upstream-response.js \ anthropic-cache.js otel.js otel-exporters.js otel-serialization.js \ token-budget-log.js blocked-request-diagnostics.js \ - provider-env-constants.js ./ + provider-env-constants.js \ + _debug-responses-capture.js ./ COPY guards/ ./guards/ COPY providers/ ./providers/ COPY transforms/ ./transforms/ diff --git a/containers/api-proxy/_debug-responses-capture.js b/containers/api-proxy/_debug-responses-capture.js new file mode 100644 index 000000000..4bc47c007 --- /dev/null +++ b/containers/api-proxy/_debug-responses-capture.js @@ -0,0 +1,133 @@ +'use strict'; + +// ────────────────────────────────────────────────────────────────────────── +// TEMPORARY DEBUG CAPTURE — DO NOT MERGE. +// +// Captures the raw OpenAI /responses upstream request bodies and the raw +// usage-bearing SSE lines so we can determine, from real CI artifacts, why +// codex runs report cache_read_tokens=0 (is the prompt prefix cache-stable +// across turns? is input_tokens_details.cached_tokens absent or literally 0?). +// +// Output: ${AWF_TOKEN_LOG_DIR or /var/log/api-proxy}/responses-debug/ +// - -.request.json (full upstream request body) +// - usage-lines.jsonl (every SSE line that carried "usage") +// - index.jsonl (per-request prefix fingerprint summary) +// +// Bodies for /responses contain the prompt but NOT credentials (the API key +// rides in the Authorization header, which is never captured here). This file +// and its two call sites must be reverted before any real merge. +// ────────────────────────────────────────────────────────────────────────── + +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); + +const BASE_DIR = process.env.AWF_TOKEN_LOG_DIR || '/var/log/api-proxy'; +const OUT_DIR = path.join(BASE_DIR, 'responses-debug'); + +let seq = 0; +let ready = false; + +function ensureDir() { + if (ready) return true; + try { + fs.mkdirSync(OUT_DIR, { recursive: true }); + ready = true; + } catch { + ready = false; + } + return ready; +} + +function isResponsesPath(p) { + if (typeof p !== 'string') return false; + const pathOnly = p.split('?')[0]; + return /^\/?(?:v\d+\/)?responses(?:\/|$)/.test(pathOnly); +} + +function sha256(buf) { + return crypto.createHash('sha256').update(buf).digest('hex'); +} + +/** + * Summarize an OpenAI Responses request body for cache-prefix analysis: + * the leading content that determines the cache-routing hash (first ~256 + * tokens ≈ first ~1KB of the rendered instructions / first input item). + */ +function summarizeBody(text) { + const out = { + bytes: Buffer.byteLength(text, 'utf8'), + body_sha256: sha256(text), + }; + try { + const json = JSON.parse(text); + out.model = json.model ?? null; + out.store = json.store ?? null; + out.prompt_cache_key = json.prompt_cache_key ?? null; + out.has_previous_response_id = json.previous_response_id != null; + out.stream = json.stream ?? null; + // `instructions` is the static system prefix; capture a stable fingerprint + // plus a short head/tail so we can eyeball any dynamic content. + if (typeof json.instructions === 'string') { + out.instructions_len = json.instructions.length; + out.instructions_sha256 = sha256(json.instructions); + out.instructions_head = json.instructions.slice(0, 400); + out.instructions_tail = json.instructions.slice(-200); + } + if (Array.isArray(json.input)) { + out.input_count = json.input.length; + const first = json.input[0]; + out.input0 = first ? JSON.stringify(first).slice(0, 600) : null; + out.input0_sha256 = first ? sha256(JSON.stringify(first)) : null; + if (Array.isArray(json.tools)) { + out.tools_count = json.tools.length; + out.tools_sha256 = sha256(JSON.stringify(json.tools)); + } + } + // First ~1KB of the canonicalized prompt-bearing fields, which is what the + // cache-routing prefix hash is computed over. + const prefixSource = (json.instructions || '') + '\u0000' + + (Array.isArray(json.input) ? JSON.stringify(json.input[0] || '') : ''); + out.prefix_1k_sha256 = sha256(prefixSource.slice(0, 1024)); + } catch (err) { + out.parse_error = String(err && err.message); + } + return out; +} + +function captureRequest(upstreamPath, body) { + try { + if (!isResponsesPath(upstreamPath)) return; + if (!ensureDir()) return; + const text = Buffer.isBuffer(body) ? body.toString('utf8') : String(body || ''); + const n = ++seq; + const reqId = (typeof arguments[2] === 'string' && arguments[2]) || `req${n}`; + fs.writeFileSync( + path.join(OUT_DIR, `${String(n).padStart(2, '0')}-${reqId}.request.json`), + text, + ); + const summary = { seq: n, request_id: reqId, path: upstreamPath, ts: new Date().toISOString(), ...summarizeBody(text) }; + fs.appendFileSync(path.join(OUT_DIR, 'index.jsonl'), JSON.stringify(summary) + '\n'); + } catch { + /* never break the proxy on debug failure */ + } +} + +function captureUsageLine(requestId, line) { + try { + if (typeof line !== 'string') return; + if (!line.includes('"usage"') && !line.includes('input_tokens_details') && + !line.includes('response.completed') && !line.includes('response.done')) { + return; + } + if (!ensureDir()) return; + fs.appendFileSync( + path.join(OUT_DIR, 'usage-lines.jsonl'), + JSON.stringify({ request_id: requestId, ts: new Date().toISOString(), line }) + '\n', + ); + } catch { + /* never break the proxy on debug failure */ + } +} + +module.exports = { captureRequest, captureUsageLine, isResponsesPath }; diff --git a/containers/api-proxy/proxy-request.js b/containers/api-proxy/proxy-request.js index 82632a238..cce5a1731 100644 --- a/containers/api-proxy/proxy-request.js +++ b/containers/api-proxy/proxy-request.js @@ -15,6 +15,7 @@ const metrics = require('./metrics'); const rateLimiter = require('./rate-limiter'); const { buildUpstreamPath, shouldStripHeader } = require('./proxy-utils'); const { injectSteeringMessage } = require('./body-transform'); +const _debugCapture = require('./_debug-responses-capture'); // TEMPORARY DEBUG — DO NOT MERGE const { maybeStripLearnedHeaderValues, resetDeprecatedHeaderValuesForTests, @@ -323,6 +324,7 @@ function sendUpstreamRequest(requestHeaders, { hasRetried = false, modelNotSupportedRetryCount = 0, }) { + _debugCapture.captureRequest(upstreamPath, body, requestId); // TEMPORARY DEBUG — DO NOT MERGE const options = { hostname: targetHost, port: 443, path: upstreamPath, method: req.method, headers: requestHeaders, diff --git a/containers/api-proxy/token-tracker-http.js b/containers/api-proxy/token-tracker-http.js index f3fdbf104..d948b7878 100644 --- a/containers/api-proxy/token-tracker-http.js +++ b/containers/api-proxy/token-tracker-http.js @@ -35,6 +35,7 @@ const { diag, } = require('./token-persistence'); const { warnCacheReadRollupMismatch, mergeBudgetFields } = require('./token-tracker-shared'); +const _debugCapture = require('./_debug-responses-capture'); // TEMPORARY DEBUG — DO NOT MERGE // Max response body to buffer for non-streaming usage extraction (5 MB). // Responses larger than this are still forwarded but usage is not extracted. @@ -90,6 +91,7 @@ function createChunkHandler(state, { requestId, provider }) { const dataLines = parseSseDataLines(complete); for (const line of dataLines) { + _debugCapture.captureUsageLine(requestId, line); // TEMPORARY DEBUG — DO NOT MERGE const { usage, model } = extractUsageFromSseLine(line); if (model && !state.streamingModel) state.streamingModel = model; if (usage) { From a74ceee4f18d6f8656f83acf67d82dd75aaa2c80 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Thu, 18 Jun 2026 17:42:06 -0700 Subject: [PATCH 5/7] Revert "chore(api-proxy): temporary raw /responses capture for cache probe" This reverts commit cde25ba72df14d3ffa999e2632c1550552e229c0. --- containers/api-proxy/Dockerfile | 3 +- .../api-proxy/_debug-responses-capture.js | 133 ------------------ containers/api-proxy/proxy-request.js | 2 - containers/api-proxy/token-tracker-http.js | 2 - 4 files changed, 1 insertion(+), 139 deletions(-) delete mode 100644 containers/api-proxy/_debug-responses-capture.js diff --git a/containers/api-proxy/Dockerfile b/containers/api-proxy/Dockerfile index 801cda2b9..5d77393ee 100644 --- a/containers/api-proxy/Dockerfile +++ b/containers/api-proxy/Dockerfile @@ -29,8 +29,7 @@ COPY server.js logging.js metrics.js rate-limiter.js \ deprecated-header-tracker.js billing-headers.js upstream-response.js \ anthropic-cache.js otel.js otel-exporters.js otel-serialization.js \ token-budget-log.js blocked-request-diagnostics.js \ - provider-env-constants.js \ - _debug-responses-capture.js ./ + provider-env-constants.js ./ COPY guards/ ./guards/ COPY providers/ ./providers/ COPY transforms/ ./transforms/ diff --git a/containers/api-proxy/_debug-responses-capture.js b/containers/api-proxy/_debug-responses-capture.js deleted file mode 100644 index 4bc47c007..000000000 --- a/containers/api-proxy/_debug-responses-capture.js +++ /dev/null @@ -1,133 +0,0 @@ -'use strict'; - -// ────────────────────────────────────────────────────────────────────────── -// TEMPORARY DEBUG CAPTURE — DO NOT MERGE. -// -// Captures the raw OpenAI /responses upstream request bodies and the raw -// usage-bearing SSE lines so we can determine, from real CI artifacts, why -// codex runs report cache_read_tokens=0 (is the prompt prefix cache-stable -// across turns? is input_tokens_details.cached_tokens absent or literally 0?). -// -// Output: ${AWF_TOKEN_LOG_DIR or /var/log/api-proxy}/responses-debug/ -// - -.request.json (full upstream request body) -// - usage-lines.jsonl (every SSE line that carried "usage") -// - index.jsonl (per-request prefix fingerprint summary) -// -// Bodies for /responses contain the prompt but NOT credentials (the API key -// rides in the Authorization header, which is never captured here). This file -// and its two call sites must be reverted before any real merge. -// ────────────────────────────────────────────────────────────────────────── - -const fs = require('fs'); -const path = require('path'); -const crypto = require('crypto'); - -const BASE_DIR = process.env.AWF_TOKEN_LOG_DIR || '/var/log/api-proxy'; -const OUT_DIR = path.join(BASE_DIR, 'responses-debug'); - -let seq = 0; -let ready = false; - -function ensureDir() { - if (ready) return true; - try { - fs.mkdirSync(OUT_DIR, { recursive: true }); - ready = true; - } catch { - ready = false; - } - return ready; -} - -function isResponsesPath(p) { - if (typeof p !== 'string') return false; - const pathOnly = p.split('?')[0]; - return /^\/?(?:v\d+\/)?responses(?:\/|$)/.test(pathOnly); -} - -function sha256(buf) { - return crypto.createHash('sha256').update(buf).digest('hex'); -} - -/** - * Summarize an OpenAI Responses request body for cache-prefix analysis: - * the leading content that determines the cache-routing hash (first ~256 - * tokens ≈ first ~1KB of the rendered instructions / first input item). - */ -function summarizeBody(text) { - const out = { - bytes: Buffer.byteLength(text, 'utf8'), - body_sha256: sha256(text), - }; - try { - const json = JSON.parse(text); - out.model = json.model ?? null; - out.store = json.store ?? null; - out.prompt_cache_key = json.prompt_cache_key ?? null; - out.has_previous_response_id = json.previous_response_id != null; - out.stream = json.stream ?? null; - // `instructions` is the static system prefix; capture a stable fingerprint - // plus a short head/tail so we can eyeball any dynamic content. - if (typeof json.instructions === 'string') { - out.instructions_len = json.instructions.length; - out.instructions_sha256 = sha256(json.instructions); - out.instructions_head = json.instructions.slice(0, 400); - out.instructions_tail = json.instructions.slice(-200); - } - if (Array.isArray(json.input)) { - out.input_count = json.input.length; - const first = json.input[0]; - out.input0 = first ? JSON.stringify(first).slice(0, 600) : null; - out.input0_sha256 = first ? sha256(JSON.stringify(first)) : null; - if (Array.isArray(json.tools)) { - out.tools_count = json.tools.length; - out.tools_sha256 = sha256(JSON.stringify(json.tools)); - } - } - // First ~1KB of the canonicalized prompt-bearing fields, which is what the - // cache-routing prefix hash is computed over. - const prefixSource = (json.instructions || '') + '\u0000' + - (Array.isArray(json.input) ? JSON.stringify(json.input[0] || '') : ''); - out.prefix_1k_sha256 = sha256(prefixSource.slice(0, 1024)); - } catch (err) { - out.parse_error = String(err && err.message); - } - return out; -} - -function captureRequest(upstreamPath, body) { - try { - if (!isResponsesPath(upstreamPath)) return; - if (!ensureDir()) return; - const text = Buffer.isBuffer(body) ? body.toString('utf8') : String(body || ''); - const n = ++seq; - const reqId = (typeof arguments[2] === 'string' && arguments[2]) || `req${n}`; - fs.writeFileSync( - path.join(OUT_DIR, `${String(n).padStart(2, '0')}-${reqId}.request.json`), - text, - ); - const summary = { seq: n, request_id: reqId, path: upstreamPath, ts: new Date().toISOString(), ...summarizeBody(text) }; - fs.appendFileSync(path.join(OUT_DIR, 'index.jsonl'), JSON.stringify(summary) + '\n'); - } catch { - /* never break the proxy on debug failure */ - } -} - -function captureUsageLine(requestId, line) { - try { - if (typeof line !== 'string') return; - if (!line.includes('"usage"') && !line.includes('input_tokens_details') && - !line.includes('response.completed') && !line.includes('response.done')) { - return; - } - if (!ensureDir()) return; - fs.appendFileSync( - path.join(OUT_DIR, 'usage-lines.jsonl'), - JSON.stringify({ request_id: requestId, ts: new Date().toISOString(), line }) + '\n', - ); - } catch { - /* never break the proxy on debug failure */ - } -} - -module.exports = { captureRequest, captureUsageLine, isResponsesPath }; diff --git a/containers/api-proxy/proxy-request.js b/containers/api-proxy/proxy-request.js index cce5a1731..82632a238 100644 --- a/containers/api-proxy/proxy-request.js +++ b/containers/api-proxy/proxy-request.js @@ -15,7 +15,6 @@ const metrics = require('./metrics'); const rateLimiter = require('./rate-limiter'); const { buildUpstreamPath, shouldStripHeader } = require('./proxy-utils'); const { injectSteeringMessage } = require('./body-transform'); -const _debugCapture = require('./_debug-responses-capture'); // TEMPORARY DEBUG — DO NOT MERGE const { maybeStripLearnedHeaderValues, resetDeprecatedHeaderValuesForTests, @@ -324,7 +323,6 @@ function sendUpstreamRequest(requestHeaders, { hasRetried = false, modelNotSupportedRetryCount = 0, }) { - _debugCapture.captureRequest(upstreamPath, body, requestId); // TEMPORARY DEBUG — DO NOT MERGE const options = { hostname: targetHost, port: 443, path: upstreamPath, method: req.method, headers: requestHeaders, diff --git a/containers/api-proxy/token-tracker-http.js b/containers/api-proxy/token-tracker-http.js index d948b7878..f3fdbf104 100644 --- a/containers/api-proxy/token-tracker-http.js +++ b/containers/api-proxy/token-tracker-http.js @@ -35,7 +35,6 @@ const { diag, } = require('./token-persistence'); const { warnCacheReadRollupMismatch, mergeBudgetFields } = require('./token-tracker-shared'); -const _debugCapture = require('./_debug-responses-capture'); // TEMPORARY DEBUG — DO NOT MERGE // Max response body to buffer for non-streaming usage extraction (5 MB). // Responses larger than this are still forwarded but usage is not extracted. @@ -91,7 +90,6 @@ function createChunkHandler(state, { requestId, provider }) { const dataLines = parseSseDataLines(complete); for (const line of dataLines) { - _debugCapture.captureUsageLine(requestId, line); // TEMPORARY DEBUG — DO NOT MERGE const { usage, model } = extractUsageFromSseLine(line); if (model && !state.streamingModel) state.streamingModel = model; if (usage) { From 01f48297e7ee4c870ffc5d50c68affdfe09d8138 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Thu, 18 Jun 2026 17:42:33 -0700 Subject: [PATCH 6/7] fix(api-proxy): read cached_tokens from Responses input_tokens_details OpenAI Responses API streaming usage reports prompt-cache hits as usage.input_tokens_details.cached_tokens (a plain object with a numeric field). extractCacheReadTokens only handled the array/details form of input_tokens_details, so codex /responses runs recorded cache_read=0 even when upstream returned 20k+ cached tokens, tripping the verify_token_usage smoke check. Add a direct numeric branch for input_tokens_details.cached_tokens and a regression test using the ground-truth shape captured from CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- containers/api-proxy/token-parsers.js | 5 ++++ .../api-proxy/token-tracker.parsing.test.js | 30 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/containers/api-proxy/token-parsers.js b/containers/api-proxy/token-parsers.js index 6cc721749..8988682c9 100644 --- a/containers/api-proxy/token-parsers.js +++ b/containers/api-proxy/token-parsers.js @@ -75,6 +75,11 @@ function extractCacheReadTokens(usage) { return usage.prompt_tokens_details.cached_tokens; } + // OpenAI Responses API: usage.input_tokens_details.cached_tokens (plain object form) + if (usage.input_tokens_details && typeof usage.input_tokens_details.cached_tokens === 'number') { + return usage.input_tokens_details.cached_tokens; + } + const tokenContainers = [ usage.prompt_tokens_details, usage.input_tokens_details, diff --git a/containers/api-proxy/token-tracker.parsing.test.js b/containers/api-proxy/token-tracker.parsing.test.js index f9e728e97..4f4a31bc0 100644 --- a/containers/api-proxy/token-tracker.parsing.test.js +++ b/containers/api-proxy/token-tracker.parsing.test.js @@ -184,6 +184,36 @@ describe('extractUsageFromJson', () => { }); }); + test('extracts OpenAI Responses API cached tokens from input_tokens_details.cached_tokens (object form)', () => { + // Ground-truth shape emitted by codex /responses streaming (see issue #5203): + // usage.input_tokens_details is a plain object with a numeric cached_tokens field, + // not prompt_tokens_details and not an array/details form. + const body = Buffer.from(JSON.stringify({ + type: 'response.completed', + response: { + id: 'resp_cache_obj', + model: 'gpt-5.4-mini', + usage: { + input_tokens: 20439, + output_tokens: 168, + total_tokens: 20607, + input_tokens_details: { + cached_tokens: 19968, + }, + }, + }, + })); + + const result = extractUsageFromJson(body); + expect(result.model).toBe('gpt-5.4-mini'); + expect(result.usage).toEqual({ + input_tokens: 20439, + output_tokens: 168, + total_tokens: 20607, + cache_read_input_tokens: 19968, + }); + }); + test('extracts cache_read tokens from token_type entries in usage details', () => { const body = Buffer.from(JSON.stringify({ type: 'response.completed', From fb26e75eb5df86b3325dbb1265a20d055895d4ae Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Thu, 18 Jun 2026 18:31:11 -0700 Subject: [PATCH 7/7] fix(smoke-claude): resolve runtime-import so agent gets its task MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Smoke Claude agent was receiving a prompt with a literal, unexpanded {{#runtime-import .github/workflows/smoke-claude.md}} directive, so it had no task and called noop — failing the pull_request add_comment post-check. Root cause: gh-aw only emits the 'Interpolate variables and render templates' step (interpolate_prompt.cjs, which resolves runtime-import) when the prompt body contains a GitHub Actions expression. smoke-claude's prompt body had none (smoke-codex/smoke-copilot have them and work). Add a load-bearing ${{ github.run_id }} reference (in an explanatory HTML comment, invisible to the agent) so the interpolate step is emitted. Recompiled with gh-aw v0.79.6 (the repo's canonical version, 39/42 locks) and re-ran postprocess-smoke-workflows. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/aw/actions-lock.json | 5 +++ .github/workflows/smoke-claude.lock.yml | 56 ++++++++++++++++--------- .github/workflows/smoke-claude.md | 9 ++++ 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json index 8cfc3fe28..136c69577 100644 --- a/.github/aw/actions-lock.json +++ b/.github/aw/actions-lock.json @@ -80,6 +80,11 @@ "version": "v4.1.0", "sha": "d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5" }, + "github/gh-aw-actions/setup@v0.79.6": { + "repo": "github/gh-aw-actions/setup", + "version": "v0.79.6", + "sha": "5c2fe865bb4dc46e1450f6ee0d0541d759aea73a" + }, "github/gh-aw/actions/setup-cli@v0.79.6": { "repo": "github/gh-aw/actions/setup-cli", "version": "v0.79.6", diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index 84c3b258a..497086848 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -1,7 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"1931d05a82aa65b2b1d5af50c9dcde1453044c61ac1c0718031eb2eca5c6b046","body_hash":"6e05820005e43b82d8112bc60ced8e13336596ae671ecac69e6c5ac691485b71","compiler_version":"v0.79.8","agent_id":"claude","agent_model":"claude-haiku-4-5","engine_versions":{"claude":"2.1.168"}} -# gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"c0338fef4749d08c21f8f975fb0e37efa17dda47","version":"v0.79.8"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.1","digest":"sha256:287fad0236959f3b3d9936ea1ef8d5b4f135ef2a5f5789713495cbbef191e60c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.1@sha256:287fad0236959f3b3d9936ea1ef8d5b4f135ef2a5f5789713495cbbef191e60c"}]} -# This file was automatically generated by gh-aw (v0.79.8). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md -# +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"1931d05a82aa65b2b1d5af50c9dcde1453044c61ac1c0718031eb2eca5c6b046","body_hash":"61fdfb929477edfef0935407ef5e3016122fdda0a2bc1fb9e82755c7dbbeb886","compiler_version":"v0.79.6","agent_id":"claude","agent_model":"claude-haiku-4-5","engine_versions":{"claude":"2.1.168"}} +# gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"5c2fe865bb4dc46e1450f6ee0d0541d759aea73a","version":"v0.79.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.1","digest":"sha256:287fad0236959f3b3d9936ea1ef8d5b4f135ef2a5f5789713495cbbef191e60c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.1@sha256:287fad0236959f3b3d9936ea1ef8d5b4f135ef2a5f5789713495cbbef191e60c"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -16,6 +14,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # +# This file was automatically generated by gh-aw (v0.79.6). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -37,7 +36,7 @@ # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 +# - github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 # # Container images used: # - ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 @@ -90,9 +89,9 @@ jobs: comment_id: ${{ steps.add-comment.outputs.comment-id }} comment_repo: ${{ steps.add-comment.outputs.comment-repo }} comment_url: ${{ steps.add-comment.outputs.comment-url }} - daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }} - daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }} - daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }} + daily_effective_workflow_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_effective_workflow_exceeded == 'true' }} + daily_effective_workflow_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_effective_workflow_threshold || '' }} + daily_effective_workflow_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_effective_workflow_total_effective_tokens || '' }} engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} label_command: ${{ steps.get_trigger_label.outputs.label_name }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} @@ -105,7 +104,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -124,7 +123,7 @@ jobs: GH_AW_INFO_MODEL: "claude-haiku-4-5" GH_AW_INFO_VERSION: "2.1.168" GH_AW_INFO_AGENT_VERSION: "2.1.168" - GH_AW_INFO_CLI_VERSION: "v0.79.8" + GH_AW_INFO_CLI_VERSION: "v0.79.6" GH_AW_INFO_WORKFLOW_NAME: "Smoke Claude" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" @@ -205,7 +204,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.79.8" + GH_AW_COMPILED_VERSION: "v0.79.6" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -238,6 +237,7 @@ jobs: env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} # poutine:ignore untrusted_checkout_exec run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" @@ -260,10 +260,23 @@ jobs: {{#runtime-import .github/workflows/smoke-claude.md}} GH_AW_PROMPT_f5afdc504a9fb9eb_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: "claude" + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); + await main(); - name: Substitute placeholders uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' with: script: | @@ -276,6 +289,7 @@ jobs: return await substitutePlaceholders({ file: process.env.GH_AW_PROMPT, substitutions: { + GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST } }); @@ -297,6 +311,7 @@ jobs: include-hidden-files: true path: | /tmp/gh-aw/aw_info.json + /tmp/gh-aw/model_multipliers.json /tmp/gh-aw/models.json /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/aw-prompts/prompt-template.txt @@ -310,7 +325,7 @@ jobs: agent: needs: activation - if: needs.activation.outputs.daily_ai_credits_exceeded != 'true' + if: needs.activation.outputs.daily_effective_workflow_exceeded != 'true' runs-on: ubuntu-latest permissions: contents: read @@ -344,7 +359,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -790,6 +805,7 @@ jobs: (umask 177 && touch /tmp/gh-aw/agent-stdio.log) GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}" printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.githubusercontent.com\",\"anthropic.com\",\"api.anthropic.com\",\"api.github.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"cdn.playwright.dev\",\"codeload.github.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"files.pythonhosted.org\",\"ghcr.io\",\"github-cloud.githubusercontent.com\",\"github-cloud.s3.amazonaws.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"lfs.github.com\",\"objects.githubusercontent.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"playwright.download.prss.microsoft.com\",\"ppa.launchpad.net\",\"pypi.org\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"sentry.io\",\"statsig.anthropic.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":2,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + GH_AW_MODEL_MULTIPLIERS_PATH="/tmp/gh-aw/model_multipliers.json" node "${RUNNER_TEMP}/gh-aw/actions/merge_awf_model_multipliers.cjs" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" @@ -822,7 +838,7 @@ jobs: GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.79.8 + GH_AW_VERSION: v0.79.6 GITHUB_AW: true GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md GITHUB_WORKSPACE: ${{ github.workspace }} @@ -998,7 +1014,7 @@ jobs: - verify_token_usage if: > always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || - needs.activation.outputs.stale_lock_file_failed == 'true' || needs.activation.outputs.daily_ai_credits_exceeded == 'true') + needs.activation.outputs.stale_lock_file_failed == 'true' || needs.activation.outputs.daily_effective_workflow_exceeded == 'true') runs-on: ubuntu-slim permissions: contents: read @@ -1017,7 +1033,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1155,9 +1171,9 @@ jobs: GH_AW_ENGINE_API_HOSTS: "api.anthropic.com" GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} - GH_AW_DAILY_AI_CREDITS_EXCEEDED: ${{ needs.activation.outputs.daily_ai_credits_exceeded }} - GH_AW_DAILY_AI_CREDITS_TOTAL_EFFECTIVE_TOKENS: ${{ needs.activation.outputs.daily_ai_credits_total_effective_tokens }} - GH_AW_DAILY_AI_CREDITS_THRESHOLD: ${{ needs.activation.outputs.daily_ai_credits_threshold }} + GH_AW_DAILY_EFFECTIVE_WORKFLOW_EXCEEDED: ${{ needs.activation.outputs.daily_effective_workflow_exceeded }} + GH_AW_DAILY_EFFECTIVE_WORKFLOW_TOTAL_EFFECTIVE_TOKENS: ${{ needs.activation.outputs.daily_effective_workflow_total_effective_tokens }} + GH_AW_DAILY_EFFECTIVE_WORKFLOW_THRESHOLD: ${{ needs.activation.outputs.daily_effective_workflow_threshold }} GH_AW_SAFE_OUTPUT_MESSAGES: "{\"runSuccess\":\"✅ [{workflow_name}]({run_url}) passed\",\"runFailure\":\"❌ [{workflow_name}]({run_url}) {status}\"}" GH_AW_GROUP_REPORTS: "false" GH_AW_FAILURE_REPORT_AS_ISSUE: "true" @@ -1227,7 +1243,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} diff --git a/.github/workflows/smoke-claude.md b/.github/workflows/smoke-claude.md index 43f3eb940..5b1b1c538 100644 --- a/.github/workflows/smoke-claude.md +++ b/.github/workflows/smoke-claude.md @@ -136,6 +136,15 @@ post-steps: # Smoke Test: Claude Engine Validation + + All data is pre-computed. Read `/tmp/gh-aw/agent/final-result.json` (one bash call: `cat /tmp/gh-aw/agent/final-result.json`). The JSON contains: `result` (PASS/FAIL), `api_status`, `gh_check`, `file_status`, `event`, `pr_number`.