Skip to content

feat(routing): add bounded router and metered classifier path - #8918

Merged
lpcox merged 5 commits into
mainfrom
copilot/add-router-client-metered-classifier-path
Sep 23, 2026
Merged

lpcox merged 5 commits into
mainfrom
copilot/add-router-client-metered-classifier-path

Conversation

Copilot AI commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Adds the inert routing transport and classifier execution path needed before route selection is wired into runs. Router and provider inference calls are bounded, trusted, and metered without creating a second credentialed upstream path.

  • Router transport

    • Add HTTP-only router client for health, capabilities, classification, and route endpoints.
    • Enforce request/response size limits, cancellation, per-attempt timeout, response contracts, and fixed endpoint methods.
    • Add three-attempt planner retry policy with deadline-aware backoff and transport-only retries.
  • Classifier execution

    • Build deterministic non-streaming native Copilot responses and chat/completions requests.
    • Preflight authoritative context capacity before provider spend.
    • Extract only unambiguous classifier output.
    • Execute through proxyRequest, preserving existing credentials, headers, guards, rate limits, and accounting.
  • Trusted internal context

    • Mark classifier requests in-process only; no header or request-body input can set the purpose.
    • Suppress steering, private blocked-request diagnostics, cache-miss counting, and provider retry behavior where appropriate.
    • Keep effective-token and AI-credit accounting active and persist request purpose when present.
req.awfRequestContext = Object.freeze({
  purpose: 'routing_classification',
  signal,
});

Copilot AI and others added 3 commits September 23, 2026 15:57
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Copilot AI changed the title [WIP] Add router client and metered classifier path for routing feat(routing): add bounded router and metered classifier path Sep 23, 2026
Copilot AI requested a review from lpcox September 23, 2026 16:07
@lpcox
lpcox marked this pull request as ready for review September 23, 2026 16:19
Copilot AI balanced review requested due to automatic review settings September 23, 2026 16:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Router retries miss common transport errors, SSE observer exceptions can crash the proxy, and the provider executor lacks direct coverage.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity · 1 Low severity

Open (3)
What changed in this PR

Adds bounded router communication and a metered Copilot classifier path using the existing API proxy.

Changes:

  • Adds router clients, retry planning, and classifier request handling.
  • Preserves proxy guards, cancellation, accounting, and trusted request context.
  • Extends token telemetry with routing purpose and SSE observation.
File Description
body-handler.js Suppresses steering for routing requests.
proxy-guards.js Exposes guard checks and suppresses classifier diagnostics.
proxy-request.js Preserves structured transform errors.
routing-classifier.js Builds and validates bounded classifier requests.
routing-classifier.test.js Tests classifier construction and extraction.
routing-planner.js Adds deadline-aware retry orchestration.
routing-planner.test.js Tests retry and cancellation behavior.
routing-provider-executor.js Executes classification through the shared proxy.
routing-router-client.js Adds bounded HTTP router transport.
routing-router-client.test.js Tests router endpoints and failures.
token-budget-log.js Adjusts classifier budget accounting.
token-persistence.js Persists request purpose.
token-tracker-http.js Propagates purpose and SSE observations.
upstream-http.js Propagates classifier cancellation upstream.
upstream-response.js Disables retries and diagnostics for classifiers.
upstream-token.js Threads routing metadata through accounting.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.


const dataLines = parseSseDataLines(complete);
for (const line of dataLines) {
if (typeof onSseData === 'function') onSseData(line);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hook isn't external input. Only trusted in-process code sets req.awfRouting, which comes in the next issue, and nothing sets it in this PR. Wrapping it in a try/catch would silently drop a terminal SSE failure and let a routed stream finish as a success, which is the opposite of failing closed. The contract is that onSseData must never throw. Its implementation in the next issue parses inside a try/catch and reports through recordFailure, which guards its own logging and exits 78 if it can't publish. That requirement is now written into the next issue. If the hook did throw, the proxy would exit uncleanly, and the host treats that as a routing failure (78), so a crash still fails closed. I'd leave this as is.

Comment thread containers/api-proxy/routing-planner.js Outdated

const PLANNER_ATTEMPT_TIMEOUT_MS = 5_000;
const PLANNER_MAX_ATTEMPTS = 3;
const RETRYABLE_ERROR_CODES = new Set(['ECONNREFUSED', 'ECONNRESET', 'EHOSTUNREACH', 'ENETUNREACH', 'ETIMEDOUT']);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed for EPIPE and EAI_AGAIN. EPIPE is the router closing the socket mid-write, like ECONNRESET, and EAI_AGAIN is a temporary failure in Docker's embedded DNS. Please add both, with cases in the isRetryablePlannerError tests.

I'd keep ENOTFOUND terminal. The router is reached through a fixed alias on an internal network, and the proxy only starts after the router's health check passes, so a missing name means misconfiguration. Retrying it would spend the deadline and report a configuration error as router_unavailable.

return { ...result, terminal: { code, detail: `Classifier execution was rejected with ${code}` } };
}

function createRoutingProviderExecutor({ getCopilotAdapter, proxyRequest, checkRateLimit, getGuardChecks = getCurrentGuardChecks }) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. This is the main coverage the PR drops compared with the issue. Please add containers/api-proxy/routing-provider-executor.test.js. The cases below pass against this PR's executor as written, and they cover each point in the comment.

'use strict';

const { createRoutingProviderExecutor, MAX_CLASSIFIER_RESPONSE_BYTES } = require('./routing-provider-executor');

function makeAdapter() {
  return {
    name: 'copilot',
    isEnabled: () => true,
    getRoutingProviderIdentity: () => 'github-copilot',
    getTargetHost: () => 'api.githubcopilot.com',
    getAuthHeaders: jest.fn(() => ({ Authorization: 'Bearer secret' })),
    getBasePath: () => '',
    getRequestSigner: () => null,
    getTargetScheme: () => 'https',
  };
}

function makeRequest() {
  return {
    purpose: 'routing_classification',
    path: '/responses',
    body: { model: 'gpt-5.4', instructions: 'Classify only.', input: [{ role: 'user', content: 'Fix this.' }], tools: [], stream: false },
  };
}

function respond(statusCode, body) {
  return (_req, res) => {
    res.writeHead(statusCode, { 'Content-Type': 'application/json' });
    res.end(body);
  };
}

describe('createRoutingProviderExecutor', () => {
  it('calls the existing proxy path with trusted in-memory context and no purpose header', async () => {
    const proxyRequest = jest.fn((req, res) => {
      expect(req.headers).not.toHaveProperty('x-awf-purpose');
      expect(req.awfRequestContext).toMatchObject({ purpose: 'routing_classification' });
      respond(200, '{"output_text":"{}"}')(req, res);
    });
    const executor = createRoutingProviderExecutor({ getCopilotAdapter: makeAdapter, proxyRequest, checkRateLimit: jest.fn(() => false) });

    const result = await executor.execute(makeRequest());

    expect(result.statusCode).toBe(200);
    expect(proxyRequest).toHaveBeenCalledWith(
      expect.anything(), expect.anything(), 'api.githubcopilot.com', { Authorization: 'Bearer secret' },
      'copilot', '', null, null, 'https', // the null body transform disables alias resolution and fallback
    );
  });

  it('uses the existing rate limiter before entering the proxy path', async () => {
    const proxyRequest = jest.fn();
    const executor = createRoutingProviderExecutor({
      getCopilotAdapter: makeAdapter,
      proxyRequest,
      checkRateLimit: (_req, res) => {
        res.writeHead(429, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ error: { type: 'rate_limit_error' } }));
        return true;
      },
    });

    await expect(executor.execute(makeRequest())).resolves.toMatchObject({ statusCode: 429, terminal: { code: 'rate_limit_error' } });
    expect(proxyRequest).not.toHaveBeenCalled();
  });

  it('returns native guard failures as terminal and provider availability failures as retryable', async () => {
    const run = proxyRequest => createRoutingProviderExecutor({
      getCopilotAdapter: makeAdapter, proxyRequest, checkRateLimit: jest.fn(() => false),
    }).execute(makeRequest());

    await expect(run(respond(403, JSON.stringify({ error: { type: 'model_policy_violation', message: 'raw policy detail' } }))))
      .resolves.toMatchObject({ terminal: { code: 'model_policy_violation', detail: 'Classifier execution was rejected with model_policy_violation' } });
    await expect(run(respond(503, 'service unavailable'))).resolves.toMatchObject({ availabilityFailure: true });
    await expect(run(respond(400, JSON.stringify({ error: { type: 'invalid_request_error', message: 'the requested model is not supported' } }))))
      .resolves.toMatchObject({ availabilityFailure: true });
  });

  it('aborts an in-flight classifier call and forwards the signal', async () => {
    let upstream;
    const executor = createRoutingProviderExecutor({
      getCopilotAdapter: makeAdapter,
      proxyRequest: (req, res) => { upstream = { req, res }; },
      checkRateLimit: jest.fn(() => false),
    });
    const controller = new AbortController();
    const pending = executor.execute(makeRequest(), { signal: controller.signal });

    expect(upstream.req.awfRequestContext.signal).toBe(controller.signal);
    controller.abort();
    await expect(pending).rejects.toMatchObject({ code: 'routing_cancelled' });
    expect(upstream.res.destroyed).toBe(true);
  });

  it('rejects a classifier response larger than its bound', async () => {
    const executor = createRoutingProviderExecutor({
      getCopilotAdapter: makeAdapter,
      proxyRequest: (_req, res) => {
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(Buffer.alloc(MAX_CLASSIFIER_RESPONSE_BYTES + 1));
      },
      checkRateLimit: jest.fn(() => false),
    });

    await expect(executor.execute(makeRequest())).rejects.toMatchObject({ code: 'routing_contract_error' });
  });

  it('refuses an untrusted purpose or a non-native Copilot target before any provider call', async () => {
    const proxyRequest = jest.fn();
    const adapter = makeAdapter();
    const executor = createRoutingProviderExecutor({ getCopilotAdapter: () => adapter, proxyRequest, checkRateLimit: jest.fn(() => false) });

    await expect(executor.execute({ ...makeRequest(), purpose: 'agent' })).rejects.toMatchObject({ code: 'routing_configuration_error' });
    adapter.getRoutingProviderIdentity = () => null;
    await expect(executor.execute(makeRequest())).rejects.toMatchObject({ code: 'provider_unavailable' });
    expect(proxyRequest).not.toHaveBeenCalled();
  });
});

The issue also listed tests for the shared request path. These are the most important:

  • body-handler: steering is skipped for a request with the routing_classification purpose and for a request carrying req.awfRouting, and still runs for an ordinary request.
  • upstream-http: the trusted context's signal is passed as the upstream request signal, so cancelling stops the paid call.
  • upstream-response: a classifier's 400 response is not buffered and retried with a fallback model.
  • upstream-token and token-tracker-http: a classifier record persists purpose: 'routing_classification', and an ordinary record has no purpose key.

Please also add the purpose entry to schemas/token-usage.schema.json.

@lpcox

lpcox commented Sep 23, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the feedback. I think the review landed on a clear follow-up plan:

  • Retry handling: agree with the review on EPIPE and EAI_AGAIN being transient transport failures worth retrying. rabeckett also called out that ENOTFOUND should remain terminal because the router is reached through a fixed internal alias and a missing name indicates configuration drift, not a transient outage.
  • SSE failure handling: the concern is valid about observer exceptions escaping the shared proxy, but the agreed contract is to keep onSseData non-throwing and fail closed via the existing recordFailure / exit-78 path in the follow-up issue rather than silently swallowing a terminal failure. This retains the fail-closed behavior without masking real routing failures.
  • Test coverage: the biggest gap is the executor integration coverage. The review is right that the planner and classifier unit tests do not exercise the critical shared-proxy path. We should add direct tests for rate-limit / guard handling, provider availability normalization, cancellation propagation, oversized-response rejection, and trusted request-context handling.
  • Schema/accounting cleanup: add the purpose field to the token usage schema and verify router-classification records persist purpose: "routing_classification" while normal request records remain unchanged.

Recommended next steps for this PR:

  1. Update routing-planner retry classification to include EPIPE and EAI_AGAIN and add retry tests for those cases.
  2. Keep ENOTFOUND terminal and document why in the code/tests.
  3. Add routing-provider-executor.test.js covering the critical proxy path and failure modes.
  4. Add the purpose entry in the token schema and verify token persistence semantics.
  5. Keep the fail-closed SSE contract and avoid swallowing terminal routing failures.

This keeps the scope tight and addresses the concrete review feedback without broadening the PR beyond the routing/classifier path.

@lpcox

lpcox commented Sep 23, 2026

Copy link
Copy Markdown
Collaborator

@copilot execute the follow-up plan described here #8918 (comment)

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>

Copilot AI commented Sep 23, 2026

Copy link
Copy Markdown
Contributor Author

@copilot execute the follow-up plan described here #8918 (comment)

Executed the follow-up plan in a8b1b2f:

  1. routing-planner now retries EPIPE and EAI_AGAIN alongside the existing transport codes, with retry tests asserting three attempts for both.
  2. ENOTFOUND stays terminal — documented in the retry set comment and asserted by a test (single attempt, original error preserved) because the router is reached through a fixed internal alias.
  3. Added routing-provider-executor.test.js covering the trusted-purpose requirement and frozen awfRequestContext, provider availability checks, rate-limit/guard settle paths, provider error and availability normalization, cancellation (pre-aborted and mid-flight), oversized-response rejection, and synchronous proxy failures.
  4. Added purpose to schemas/token-usage.schema.json and verified in token-tracker.schema.test.js that router-classification records persist purpose: "routing_classification" while normal records omit it.
  5. Kept the fail-closed SSE contract unchanged; documented on createChunkHandler that onSseData must not throw and that observers fail closed through their own path rather than swallowing terminal failures.

Full api-proxy suite passes (1816 tests).

@github-actions

Copy link
Copy Markdown
Contributor

✅ Copilot review passed with no inline comments.

@copilot Add the ready-for-aw label to this PR to trigger agentic CI smoke tests.

@lpcox

lpcox commented Sep 23, 2026

Copy link
Copy Markdown
Collaborator

@rabeckett make sure the updates look ok

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

❌ Smoke Copilot BYOK AOAI (api-key) reports failed. AOAI BYOK (api-key) mode investigation needed...

🔑 BYOK (AOAI api-key) report filed by Smoke Copilot BYOK AOAI (api-key)

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Chroot tests passed! Smoke Chroot - All security and functionality tests succeeded.

Tested by Smoke Chroot

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

🔌 Smoke Services — All services reachable! ✅

🔌 Service connectivity validated by Smoke Services

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

✨ The prophecy is fulfilled... Smoke Codex has completed its mystical journey. The stars align. 🌟

Warning

Firewall blocked 12 domains

The following domains were blocked by the firewall during workflow execution:

  • ab.chatgpt.com
  • accounts.google.com
  • api.github.com
  • clients2.google.com
  • collector.github.com
  • contentautofill.googleapis.com
  • github.com
  • github.githubassets.com
  • msfeed25.pkgs.visualstudio.com
  • update.googleapis.com
  • www.google.com
  • www.gstatic.com

[!TIP]
api.github.com is blocked because GitHub API access uses the built-in GitHub tools by default. Instead of adding api.github.com to network.allowed, use tools.github.mode: gh-proxy for direct pre-authenticated GitHub CLI access without requiring network access to api.github.com:

tools:
  github:
    mode: gh-proxy

See GitHub Tools for more information on gh-proxy mode.

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "ab.chatgpt.com"
    - "accounts.google.com"
    - "api.github.com"
    - "clients2.google.com"
    - "collector.github.com"
    - "contentautofill.googleapis.com"
    - "github.com"
    - "github.githubassets.com"
    - "msfeed25.pkgs.visualstudio.com"
    - "update.googleapis.com"
    - "www.google.com"
    - "www.gstatic.com"

See Network Configuration for more information.

🔮 The oracle has spoken through Smoke Codex

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

❌ Smoke Copilot BYOK AOAI (Entra) reports failed. AOAI BYOK (Entra) mode investigation needed...

🪪 BYOK (AOAI Entra) report filed by Smoke Copilot BYOK AOAI (Entra)

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

✅ Smoke Copilot BYOK completed. Copilot BYOK mode operational. 🔓

🔑 BYOK report filed by Smoke Copilot BYOK

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

❌ Smoke Gemini reports failed. Facets need polishing...

💎 Faceted by Smoke Gemini

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

✅ Security Guard completed successfully!

Security review of PR #8918 complete. No security vulnerabilities found. All new routing infrastructure properly validates inputs, isolates internal traffic from user-facing requests, enforces timeouts, maintains token tracking accuracy, and propagates errors safely. Existing guard controls remain intact.

Generated by Security Guard for #8918

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤

📰 BREAKING: Report filed by Smoke Copilot

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

📡 Smoke OTel Tracing completed. All tracing scenarios validated. ✅

Warning

Firewall blocked 2 domains

The following domains were blocked by the firewall during workflow execution:

  • o205451.ingest.us.sentry.io
  • registry.npmjs.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "o205451.ingest.us.sentry.io"
    - "registry.npmjs.org"

See Network Configuration for more information.

📡 OTel tracing validated by Smoke OTel Tracing

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

✅ Smoke Claude passed

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • api.anthropic.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "api.anthropic.com"

See Network Configuration for more information.

Generated by Smoke Claude for #8918

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Smoke Cloud Hypervisor completed. Cloud Hypervisor + Copilot passed.

Warning

Firewall blocked 2 domains

The following domains were blocked by the firewall during workflow execution:

  • example.com
  • github.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "example.com"
    - "github.com"

See Network Configuration for more information.

Cloud Hypervisor + Copilot smoke test by Smoke Cloud Hypervisor

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

✅ Build Test Suite completed successfully!

Warning

Firewall blocked 8 domains

The following domains were blocked by the firewall during workflow execution:

  • api.nuget.org
  • bun.sh
  • dc.services.visualstudio.com
  • deno.land
  • dl.deno.land
  • github.com
  • releaseassets.githubusercontent.com
  • repo.maven.apache.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "api.nuget.org"
    - "bun.sh"
    - "dc.services.visualstudio.com"
    - "deno.land"
    - "dl.deno.land"
    - "github.com"
    - "releaseassets.githubusercontent.com"
    - "repo.maven.apache.org"

See Network Configuration for more information.

Generated by Build Test Suite for #8918

@github-actions

Copy link
Copy Markdown
Contributor

✅ Smoke Test: Copilot BYOK (Direct) Mode

Status: PASS

Test Results:

  • ✅ GitHub MCP: Connected (2 merged PRs found)
  • ✅ GitHub.com HTTP: 200 OK
  • ✅ File Write/Read: Verified
  • ✅ BYOK Inference: Working (api-proxy → api.githubcopilot.com via COPILOT_PROVIDER_API_KEY)

Running in direct BYOK mode with real key held by sidecar, placeholder injected into agent.

🔑 BYOK report filed by Smoke Copilot BYOK
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test: Copilot Engine

Overall: PASS

@lpcox @Copilot

📰 BREAKING: Report filed by Smoke Copilot
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test: Cloud Hypervisor + Copilot

  1. list_pull_requests (github/gh-aw-firewall, closed): PASS - got PR Pin Gemini CLI auth type so api-proxy runs stop failing with exit 41 #8916
  2. curl https://github.com: PASS - HTTP 200
  3. Write/read /tmp/gh-aw/agent/smoke-cloud-hypervisor-${GITHUB_RUN_ID}.txt: PASS
  4. curl (example.com/redacted) (should be blocked): PASS - HTTP 000

All checks passed.

Warning

Firewall blocked 2 domains

The following domains were blocked by the firewall during workflow execution:

  • example.com
  • github.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "example.com"
    - "github.com"

See Network Configuration for more information.

Cloud Hypervisor + Copilot smoke test by Smoke Cloud Hypervisor
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

@lpcox Smoke test results for Copilot network isolation:

EGRESS_RESULT allow=pass deny=pass

✅ Allowed domain (github.com) reachable — HTTP 200
✅ Non-allowed domain (example.com) blocked (TLS handshake failed via proxy)

Overall: PASS

Warning

Firewall blocked 2 domains

The following domains were blocked by the firewall during workflow execution:

  • api.github.com
  • example.com

[!TIP]
api.github.com is blocked because GitHub API access uses the built-in GitHub tools by default. Instead of adding api.github.com to network.allowed, use tools.github.mode: gh-proxy for direct pre-authenticated GitHub CLI access without requiring network access to api.github.com:

tools:
  github:
    mode: gh-proxy

See GitHub Tools for more information on gh-proxy mode.

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "api.github.com"
    - "example.com"

See Network Configuration for more information.

🛡️ Egress verdict from Smoke Copilot Network Isolation
Add label ready-for-aw to run again

@github-actions github-actions Bot added the smoke-copilot-network-isolation Copilot network-isolation egress smoke test label Sep 23, 2026
@lpcox
lpcox deployed to aoai-model September 23, 2026 22:04 — with GitHub Actions Active
@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test: Services Connectivity

  • Redis PING: ✅ PONG
  • PostgreSQL pg_isready: ✅ accepting connections
  • PostgreSQL SELECT 1: ✅ 1

Overall: PASS

🔌 Service connectivity validated by Smoke Services
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test: Claude Engine Validation

Check Result
API status ✅ PASS
gh check ✅ PASS
File status ✅ PASS

Overall result: PASS

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • api.anthropic.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "api.anthropic.com"

See Network Configuration for more information.

Generated by Smoke Claude for #8918 · claude · haiku45 · 55.8 AIC · ⊞ 4.7K · ◷
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

Chroot Version Comparison

Runtime Host Version Chroot Version Match?
Python Python 3.12.14 Python 3.12.14 ✅ YES
Node.js v24.21.0 v22.23.2 ❌ NO
Go go1.22.12 go1.22.12 ✅ YES

Overall: FAILED — Node.js version mismatch between host and chroot environment (host v24.21.0 vs chroot v22.23.2). smoke-chroot label not applied since not all runtimes matched.

Tested by Smoke Chroot
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

Reviewed merged PRs:

  • Document explicit upstream proxy port fix in runner-doctor B2 catalog
  • Register nvx as an opt-in preview runtime

GitHub review/details: ✅
Playwright title check: ✅
File write/read: ✅
Build AWF: ✅
Overall: PASS

Warning

Firewall blocked 12 domains

The following domains were blocked by the firewall during workflow execution:

  • ab.chatgpt.com
  • accounts.google.com
  • api.github.com
  • clients2.google.com
  • collector.github.com
  • contentautofill.googleapis.com
  • github.com
  • github.githubassets.com
  • msfeed25.pkgs.visualstudio.com
  • update.googleapis.com
  • www.google.com
  • www.gstatic.com

[!TIP]
api.github.com is blocked because GitHub API access uses the built-in GitHub tools by default. Instead of adding api.github.com to network.allowed, use tools.github.mode: gh-proxy for direct pre-authenticated GitHub CLI access without requiring network access to api.github.com:

tools:
  github:
    mode: gh-proxy

See GitHub Tools for more information on gh-proxy mode.

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "ab.chatgpt.com"
    - "accounts.google.com"
    - "api.github.com"
    - "clients2.google.com"
    - "collector.github.com"
    - "contentautofill.googleapis.com"
    - "github.com"
    - "github.githubassets.com"
    - "msfeed25.pkgs.visualstudio.com"
    - "update.googleapis.com"
    - "www.google.com"
    - "www.gstatic.com"

See Network Configuration for more information.

🔮 The oracle has spoken through Smoke Codex
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test: API Proxy OpenTelemetry Tracing — Results

Scenario Result Notes
1. Module Loading ✅ otel.js loaded successfully, isEnabled() → true, exports: startRequestSpan, setTokenAttributes, setBudgetAttributes, endSpan, endSpanError, shutdown, isEnabled, _parseEndpoints, _parseOtlpHeaders, _buildResourceSpans, _createOtlpWorkloadIdentity, _ProxyAwareOtlpExporter, _FileSpanExporter, _FanOutSpanExporter
2. Test Suite ✅ 3 suites / 68 tests passed, 0 failed (otel.test.js, otel-fanout.test.js, otel-workload-identity.test.js)
3. Env Var Forwarding ✅ env-passthrough.ts forwards GITHUB_AW_OTEL_TRACE_ID + GITHUB_AW_OTEL_PARENT_SPAN_ID to the agent; api-proxy-env-config.ts forwards GH_AW_OTLP_ENDPOINTS, OTEL_EXPORTER_OTLP_ENDPOINT, and both trace context vars to api-proxy
4. Token Tracker Integration ✅ token-tracker-http.js contains the onUsage callback hook point for OTEL
5. OTEL Diagnostics ⚪ Expected-pending No otel.jsonl exported under api-proxy-logs/ in this run (no live OTLP collector configured for the smoke test); token-usage.jsonl has 14 records confirming the sidecar processed requests normally — consistent with graceful degradation when OTEL isn't configured

Overall: PASS — all implemented OTEL integration points (module init, span creation, GenAI usage attributes, env propagation, token-tracker hook) verified working. Span export to a live OTLP endpoint is untested in this environment as expected (no collector configured), and the code degrades gracefully without errors.

Warning

Firewall blocked 2 domains

The following domains were blocked by the firewall during workflow execution:

  • o205451.ingest.us.sentry.io
  • registry.npmjs.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "o205451.ingest.us.sentry.io"
    - "registry.npmjs.org"

See Network Configuration for more information.

📡 OTel tracing validated by Smoke OTel Tracing
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Build Test Suite Results

Ecosystem Project Build/Install Tests Status
Bun elysia ✅ 1/1 passed ✅ PASS
Bun hono ✅ 1/1 passed ✅ PASS
C++ fmt ✅ N/A ✅ PASS
C++ json ✅ N/A ✅ PASS
Deno oak N/A 1/1 passed ✅ PASS
Deno std N/A 1/1 passed ✅ PASS
.NET hello-world ✅ N/A (run OK) ✅ PASS
.NET json-parse ✅ N/A (run OK) ✅ PASS
Go color ✅ ok ✅ PASS
Go env ✅ ok ✅ PASS
Go uuid ✅ ok ✅ PASS
Java gson ✅ 1/1 passed ✅ PASS
Java caffeine ✅ 1/1 passed ✅ PASS
Node.js clsx ✅ all passed ✅ PASS
Node.js execa ✅ all passed ✅ PASS
Node.js p-limit ✅ all passed ✅ PASS
Rust fd ✅ 1/1 passed ✅ PASS
Rust zoxide ✅ 1/1 passed ✅ PASS

Overall: 8/8 ecosystems passed — PASS

Note: The default Maven local repository (~/.m2/repository) could not be created because ~/.m2 is owned by root in this runner environment (pre-existing environment condition, unrelated to the firewall). Worked around by pointing Maven at an alternate local repo path (-Dmaven.repo.local=/tmp/gh-aw/agent/m2-repo); both Java projects then compiled and tested successfully through the Squid proxy with no network egress issues.

All ecosystems cloned and built/tested successfully with no firewall-related network blocking observed.

Warning

Firewall blocked 8 domains

The following domains were blocked by the firewall during workflow execution:

  • api.nuget.org
  • bun.sh
  • dc.services.visualstudio.com
  • deno.land
  • dl.deno.land
  • github.com
  • releaseassets.githubusercontent.com
  • repo.maven.apache.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "api.nuget.org"
    - "bun.sh"
    - "dc.services.visualstudio.com"
    - "deno.land"
    - "dl.deno.land"
    - "github.com"
    - "releaseassets.githubusercontent.com"
    - "repo.maven.apache.org"

See Network Configuration for more information.

Generated by Build Test Suite for #8918 · copilot · auto · 33.8 AIC · ⊞ 11.8K · ◷
Add label ready-for-aw to run again

@lpcox
lpcox merged commit d6d2945 into main Sep 23, 2026
144 of 149 checks passed
@lpcox
lpcox deleted the copilot/add-router-client-metered-classifier-path branch September 23, 2026 22:49

This branch was successfully deployed

1 active deployment
aoai-model — e58a07b1 Deployed Sep 23, 2026 by lpcox via conclusion #1748
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Routing]: add the router client and metered classifier path

4 participants