From a02918eb2bbd1d6ac6f1458fe6a2324ff55b782f Mon Sep 17 00:00:00 2001 From: aaronjmars <61592645+aaronjmars@users.noreply.github.com> Date: Fri, 2 Oct 2026 10:28:06 -0400 Subject: [PATCH] fix(aeon.yml): stop duplicate gateway route annotations on claude runs A claude run showed "Using Claude Code subscription (CLAUDE_CODE_OAUTH_TOKEN)" twice plus "Routing attempt via 'claude'": - The second "Using ..." came from the post-run "Analyze skill output" (quality scorer) step, which re-sources scripts/llm-gateway.sh for its own claude call. "Convert feed outputs" does the same. Both now source it with AEON_GATEWAY_QUIET=1, so the route prints as a plain log line there. Warnings and errors are never quieted. - "Routing attempt via ''" was annotated on every run even with a single candidate. The first attempt now logs it plainly; only a real failover is annotated ("Failing over to ''"). The cascade comment in the run step is trimmed so the step stays under GitHub's 21000-char expression cap (20445 vs 20550 on main). --- .github/workflows/aeon.yml | 21 ++++++++--------- scripts/llm-gateway.sh | 38 +++++++++++++++++++------------ scripts/tests/test_llm_gateway.sh | 18 +++++++++++++++ 3 files changed, 50 insertions(+), 27 deletions(-) diff --git a/.github/workflows/aeon.yml b/.github/workflows/aeon.yml index 38886ba83f9..9dc24b45f7c 100644 --- a/.github/workflows/aeon.yml +++ b/.github/workflows/aeon.yml @@ -1097,18 +1097,15 @@ jobs: if [ "${#MCP_FLAGS[@]}" -gt 0 ]; then RH_ARGS+=(--mcp-config .mcp.json); fi if [ "$HARNESS" = "claude" ]; then - # Cascade: try each provider in priority order; fall over only on a - # provider-shaped failure, never a timeout or skill-side failure (a - # retry re-runs the whole skill; scripts/gateway-failover.sh). Each - # attempt is an isolated subshell that sets up its provider env (+ - # optional Langfuse), THEN runs `run-harness claude` (which invokes - # `claude -p` under that env). Gateway/Langfuse notices go to the step - # log (>&2); only run-harness's stdout envelope is captured. A hard - # gateway setup failure `exit 1`s the subshell → this attempt fails → - # fall over, exactly as the old inline `claude -p` cascade did. + # Cascade: try each provider in order; fall over only on a provider-shaped + # failure, never a timeout or skill failure (scripts/gateway-failover.sh). + # Each attempt is a subshell that sets up its provider env (+ Langfuse), + # then runs `run-harness claude`; only its stdout envelope is captured. A + # gateway setup `exit 1` fails the attempt and falls over. Only a failover + # is annotated; the first route is the gateway's own notice. USED_GATEWAY="" for cand in $CANDIDATES; do - echo "::notice::Routing attempt via '$cand'" + if [ "$cand" = "${CANDIDATES%% *}" ]; then echo "Routing attempt via '$cand'"; else echo "::notice::Failing over to '$cand'"; fi rm -f /tmp/harness-stderr.txt; RC=0 CLAUDE_OUTPUT=$( GATEWAY="$cand" @@ -1647,7 +1644,7 @@ jobs: # so this is a no-op there and correct for the added harnesses.) if [ "$HARNESS" = "claude" ]; then GATEWAY="${{ steps.run.outputs.GATEWAY }}" - source "${GITHUB_WORKSPACE}/scripts/llm-gateway.sh" + AEON_GATEWAY_QUIET=1 source "${GITHUB_WORKSPACE}/scripts/llm-gateway.sh" AEON_OTEL_COMPONENT=scorer source "${GITHUB_WORKSPACE}/scripts/langfuse-otel.sh" fi @@ -1906,7 +1903,7 @@ jobs: # GATEWAY may be empty when the Run step failed early — the script # treats that as `direct`, matching the old inline behavior. GATEWAY="${{ steps.run.outputs.GATEWAY }}" - source "${GITHUB_WORKSPACE}/scripts/llm-gateway.sh" + AEON_GATEWAY_QUIET=1 source "${GITHUB_WORKSPACE}/scripts/llm-gateway.sh" AEON_OTEL_COMPONENT=feed source "${GITHUB_WORKSPACE}/scripts/langfuse-otel.sh" for pending in "${AEON_PENDING_DIR:-${RUNNER_TEMP:-/tmp}/aeon-pending}"/.pending-*.md; do [ -f "$pending" ] || continue diff --git a/scripts/llm-gateway.sh b/scripts/llm-gateway.sh index ee2ca388bf8..cf5e115312e 100644 --- a/scripts/llm-gateway.sh +++ b/scripts/llm-gateway.sh @@ -34,6 +34,14 @@ CCR_PORT="${CCR_PORT:-3456}" HIVEMINDOS_DEFAULT_MODEL="inclusionai/ling-3.0-flash" +# Route notices. The Run step sources this file once per attempt; post-run steps +# (scorer, feed convert) source it again for their own claude call and set +# AEON_GATEWAY_QUIET=1, so the route prints as a plain log line there instead of a +# second, identical run annotation. Warnings and errors are never quieted. +gw_notice() { + if [ -n "${AEON_GATEWAY_QUIET:-}" ]; then echo "gateway: $*"; else echo "::notice::$*"; fi +} + require_secret() { if [ -z "${!1:-}" ]; then echo "::error::gateway.provider=${GATEWAY} requires the $1 secret but it is not set" >&2 @@ -88,7 +96,7 @@ start_ccr_sidecar() { elif [ "$extra_tf" = "cleancache" ]; then # 2.x's cleancache transformer has no 3.x counterpart and no job left: 3.x # drops cache_control when it translates to chat-completions. - echo "::notice::VENICE_CLEANCACHE is a no-op on claude-code-router 3.x (cache markers are dropped upstream)" >&2 + gw_notice "VENICE_CLEANCACHE is a no-op on claude-code-router 3.x (cache markers are dropped upstream)" >&2 fi # AEON_GATEWAY_DRY_RUN prints what this sidecar WOULD run and returns, so the @@ -216,7 +224,7 @@ if [ -z "${GATEWAY:-}" ] || [ "${GATEWAY}" = "auto" ]; then if [ -n "${AEON_LIST_CANDIDATES:-}" ]; then printf '%s\n' "$AEON_CANDIDATES"; exit 0; fi # Single-shot: set up the first present provider (preserves prior behavior). GATEWAY="${AEON_CANDIDATES%% *}" - echo "::notice::gateway=auto resolved to '${GATEWAY}'" + gw_notice "gateway=auto resolved to '${GATEWAY}'" fi # --- route ------------------------------------------------------------------ @@ -225,16 +233,16 @@ case "${GATEWAY:-direct}" in claude) # NATIVE — Claude Code subscription (OAuth token) require_secret CLAUDE_CODE_OAUTH_TOKEN unset ANTHROPIC_API_KEY # prefer the subscription token over a pay-go key - echo "::notice::Using Claude Code subscription (CLAUDE_CODE_OAUTH_TOKEN)" + gw_notice "Using Claude Code subscription (CLAUDE_CODE_OAUTH_TOKEN)" ;; anthropic) # NATIVE — pay-as-you-go Anthropic API key (or compatible endpoint) require_secret ANTHROPIC_API_KEY unset CLAUDE_CODE_OAUTH_TOKEN if [ -n "${ANTHROPIC_BASE_URL:-}" ]; then - echo "::notice::Using Anthropic-compatible API at ${ANTHROPIC_BASE_URL}" + gw_notice "Using Anthropic-compatible API at ${ANTHROPIC_BASE_URL}" else - echo "::notice::Using direct Anthropic API (ANTHROPIC_API_KEY)" + gw_notice "Using direct Anthropic API (ANTHROPIC_API_KEY)" fi ;; @@ -243,7 +251,7 @@ case "${GATEWAY:-direct}" in export ANTHROPIC_BASE_URL="https://llm.bankr.bot" export ANTHROPIC_AUTH_TOKEN="$BANKR_LLM_KEY" unset ANTHROPIC_API_KEY CLAUDE_CODE_OAUTH_TOKEN - echo "::notice::Routing through Bankr Gateway (https://llm.bankr.bot)" + gw_notice "Routing through Bankr Gateway (https://llm.bankr.bot)" ;; openrouter) # NATIVE - Anthropic "skin", carries Opus 5.5 + Sonnet 5.5 + Haiku @@ -270,7 +278,7 @@ case "${GATEWAY:-direct}" in # OPENROUTER_SITE_URL / OPENROUTER_APP_TITLE. export ANTHROPIC_CUSTOM_HEADERS="HTTP-Referer: ${OPENROUTER_SITE_URL:-https://aeon.fun} X-Title: ${OPENROUTER_APP_TITLE:-Aeon}" - echo "::notice::Routing through OpenRouter (Anthropic-native) as ${MODEL}" + gw_notice "Routing through OpenRouter (Anthropic-native) as ${MODEL}" ;; usepod) # NATIVE — token lives in the URL path; base URL IS a secret @@ -284,7 +292,7 @@ X-Title: ${OPENROUTER_APP_TITLE:-Aeon}" if [ -n "${USEPOD_MODEL:-}" ]; then MODEL="$USEPOD_MODEL"; fi if [ -n "${USEPOD_MODEL_SONNET:-}" ]; then export ANTHROPIC_DEFAULT_SONNET_MODEL="$USEPOD_MODEL_SONNET"; fi if [ -n "${USEPOD_MODEL_HAIKU:-}" ]; then export ANTHROPIC_DEFAULT_HAIKU_MODEL="$USEPOD_MODEL_HAIKU"; fi - echo "::notice::Routing through UsePod (Anthropic-native marketplace)" + gw_notice "Routing through UsePod (Anthropic-native marketplace)" ;; grok) # NATIVE — xAI's Anthropic-compatible API (Claude Code → api.x.ai) @@ -306,7 +314,7 @@ X-Title: ${OPENROUTER_APP_TITLE:-Aeon}" export ANTHROPIC_DEFAULT_SONNET_MODEL="$grok_model" export ANTHROPIC_DEFAULT_HAIKU_MODEL="$grok_model" MODEL="$grok_model" - echo "::notice::Routing through xAI (Anthropic-compatible) as ${grok_model} @ ${ANTHROPIC_BASE_URL}" + gw_notice "Routing through xAI (Anthropic-compatible) as ${grok_model} @ ${ANTHROPIC_BASE_URL}" ;; glm) # NATIVE — Z.AI's Anthropic-compatible API (Claude Code → api.z.ai) @@ -341,7 +349,7 @@ X-Title: ${OPENROUTER_APP_TITLE:-Aeon}" export ANTHROPIC_DEFAULT_SONNET_MODEL="$glm_model" export ANTHROPIC_DEFAULT_HAIKU_MODEL="$glm_model" MODEL="$glm_model" - echo "::notice::Routing through Z.AI (Anthropic-compatible) as ${glm_model} @ ${ANTHROPIC_BASE_URL}" + gw_notice "Routing through Z.AI (Anthropic-compatible) as ${glm_model} @ ${ANTHROPIC_BASE_URL}" ;; surplus) # SIDECAR — OpenAI-compatible (dot-form ids); carries the full catalog @@ -357,7 +365,7 @@ X-Title: ${OPENROUTER_APP_TITLE:-Aeon}" start_ccr_sidecar surplus \ "https://www.surplusintelligence.ai/api/inference/v1/chat/completions" \ "$SURPLUS_API_KEY" "$surplus_model" - echo "::notice::Routing through Surplus via claude-code-router (${surplus_model})" + gw_notice "Routing through Surplus via claude-code-router (${surplus_model})" ;; venice) # SIDECAR - OpenAI-compatible (dash-form ids); carries Opus 5.5, no haiku @@ -384,7 +392,7 @@ X-Title: ${OPENROUTER_APP_TITLE:-Aeon}" start_ccr_sidecar venice \ "${VENICE_BASE_URL:-https://api.venice.ai/api/v1/chat/completions}" \ "$VENICE_API_KEY" "$venice_model" "${VENICE_CLEANCACHE:+cleancache}" - echo "::notice::Routing through Venice via claude-code-router (${venice_model} @ ${VENICE_BASE_URL:-https://api.venice.ai/api/v1/chat/completions})" + gw_notice "Routing through Venice via claude-code-router (${venice_model} @ ${VENICE_BASE_URL:-https://api.venice.ai/api/v1/chat/completions})" ;; hivemindos) # SIDECAR — HivemindOS Models: OpenAI-compatible, billed to a credit balance @@ -413,14 +421,14 @@ X-Title: ${OPENROUTER_APP_TITLE:-Aeon}" start_ccr_sidecar hivemindos \ "${HIVEMINDOS_BASE_URL:-https://hivemindos-paid-agent-gateway.hivemindos.workers.dev/api/paid-agents/default}/chat/completions" \ "$HIVEMINDOS_CREDIT_TOKEN" "$hivemindos_model" "hivemindos" - echo "::notice::Routing through HivemindOS Models via claude-code-router (${hivemindos_model})" + gw_notice "Routing through HivemindOS Models via claude-code-router (${hivemindos_model})" ;; direct|"") # NATIVE — Anthropic API or an Anthropic-compatible endpoint if [ -n "${ANTHROPIC_BASE_URL:-}" ]; then - echo "::notice::Using Anthropic-compatible API at ${ANTHROPIC_BASE_URL}" + gw_notice "Using Anthropic-compatible API at ${ANTHROPIC_BASE_URL}" else - echo "::notice::Using direct Anthropic API" + gw_notice "Using direct Anthropic API" fi ;; diff --git a/scripts/tests/test_llm_gateway.sh b/scripts/tests/test_llm_gateway.sh index b7a3012ad48..8048eefb7d3 100755 --- a/scripts/tests/test_llm_gateway.sh +++ b/scripts/tests/test_llm_gateway.sh @@ -231,6 +231,24 @@ hm_src() { ) && pass "gateway=auto resolves to hivemindos on the credit token alone" \ || bad "gateway=auto resolves to hivemindos on the credit token alone" +# Route notice: an annotation on the Run step, a plain log line when a post-run step +# re-sources the gateway with AEON_GATEWAY_QUIET=1 (no duplicate run annotation). +# shellcheck disable=SC1090 +out=$( export GATEWAY=claude CLAUDE_CODE_OAUTH_TOKEN=tok; source "$GW" ) +echo "$out" | grep -q '^::notice::Using Claude Code subscription' \ + && pass "route notice is an annotation by default" \ + || bad "route notice is an annotation by default (got: $out)" +# shellcheck disable=SC1090 +out=$( export GATEWAY=claude CLAUDE_CODE_OAUTH_TOKEN=tok AEON_GATEWAY_QUIET=1; source "$GW" ) +{ echo "$out" | grep -q '^gateway: Using Claude Code subscription' && ! echo "$out" | grep -q '::notice::'; } \ + && pass "AEON_GATEWAY_QUIET=1 logs the route without an annotation" \ + || bad "AEON_GATEWAY_QUIET=1 logs the route without an annotation (got: $out)" +# shellcheck disable=SC1090 +out=$( export GATEWAY=claude AEON_GATEWAY_QUIET=1; unset CLAUDE_CODE_OAUTH_TOKEN; source "$GW" 2>&1 ) +echo "$out" | grep -q '^::error::' \ + && pass "AEON_GATEWAY_QUIET=1 never quiets errors" \ + || bad "AEON_GATEWAY_QUIET=1 never quiets errors (got: $out)" + echo if [ "$fail" -eq 0 ]; then echo "All llm-gateway tests passed."; else echo "Some tests FAILED."; fi exit "$fail"