Summary
apiProxy.enableTokenSteering is emitted as true in every compiled lock, is not documented, and is not overridable from frontmatter. In two independent runs on two different engines it silently overrode the provider and model the workflow explicitly asked for, and routed every model call to a provider slot the proxy had just reported as not configured. Both runs produced zero useful work.
Neither failure is recoverable by the workflow author, because the steering surface (enableTokenSteering, GH_AW_LLM_PROVIDER) appears only in the compiled artifact and the resolved config, and the engines/sandbox reference exposes no knob for it.
Case 1 — engine: opencode: model silently downgraded, calls routed to unconfigured Copilot
Run 30183258206, gh-aw v0.83.2, AWF v0.27.41, ubuntu-latest, workflow_dispatch.
What the workflow asked for. The compiled activation and every downstream job carry:
GH_AW_INFO_MODEL: anthropic/claude-sonnet-5
GH_AW_ENGINE_MODEL: anthropic/claude-sonnet-5
and the agent step exports:
OPENCODE_MODEL: awf-proxy/claude-sonnet-5
What gh-aw's own Write OpenCode Config step then wrote. The provider model map contains a different model:
"provider": {
"awf-proxy": {
"api": "http://172.30.0.30:10002",
"options": { "apiKey": "awf-copilot-proxy" },
"models": { "claude-sonnet-4.5": {} }
}
}
OPENCODE_MODEL names claude-sonnet-5; the config gh-aw writes in the adjacent step offers only claude-sonnet-4.5. Every request in the run logged modelID=claude-sonnet-4.5. No warning was emitted about the substitution.
Where the substitution comes from. The emitted AWF config sets "apiProxy": { "enabled": true, "enableTokenSteering": true, ... } and defines the alias group the request resolves through with the Copilot patterns listed first:
"sonnet-6x": [
"copilot/*sonnet-4.5*",
"copilot/*sonnet-4.6*",
"copilot/*sonnet-5*",
"copilot/*sonnet-4-5-*",
"anthropic/*sonnet-4-5-*",
"copilot/*sonnet-4-6*",
"anthropic/*sonnet-4-6*",
"anthropic/*sonnet-5*"
]
"models": { "agent": ["sonnet-6x", ...] }. An explicitly requested anthropic/claude-sonnet-5 is resolved through this group to the group's first entry, copilot/*sonnet-4.5* — which explains both the provider change and the model downgrade in one step.
Why that is fatal here. The proxy had already logged, in the same step, that Copilot is not configured for this run:
[INFO] API proxy enabled: OpenAI=false, Anthropic=true, Copilot=false, Gemini=false, Vertex=false
Anthropic was configured. Copilot was not. Steering routed to Copilot anyway, and every call failed:
level=ERROR message="stream error" providerID=awf-proxy modelID=claude-sonnet-4.5
error.error="AI_APICallError: Credentials for GitHub Copilot (port 10002) are not
configured. Set COPILOT_GITHUB_TOKEN or COPILOT_PROVIDER_API_KEY to enable this provider."
The steering decision is made against a provider the proxy knows is unprovisioned, while the provider that is provisioned sits unused.
Case 1b — the retry loop has no terminal state
The build agent retried this same unrecoverable configuration error for 17 minutes, with doubling backoff and no give-up, until the step timeout killed the job:
01:41:32 stream error (attempt 1)
01:41:34 01:41:38 01:41:46 01:42:02 01:42:34 01:43:38
01:45:46 01:50:02 01:58:34 (attempt 10)
##[error] The action 'Execute OpenCode CLI' has timed out after 20 minutes.
Total agent job: 21m3s, of which ~19 minutes was retrying an error that cannot resolve without a config change. Notably the title sub-agent in the same run did the right thing and stopped:
error.error="AI_RetryError: Failed after 3 attempts. Last error: Credentials for
GitHub Copilot (port 10002) are not configured..."
So a bounded retry policy already exists; the primary agent path does not use it. "Credentials for provider X are not configured" is a deterministic configuration error and should be non-retryable — retrying it converts a 30-second diagnosable failure into a 20-minute timeout with a misleading error.
Case 2 — engine: copilot in BYOK mode: explicit BYOK config captured but not honored
Run 30227526593, gh-aw with engine: { id: copilot, version: 1.0.73 }, AWF v0.27.41.
Frontmatter set the documented BYOK surface explicitly:
COPILOT_PROVIDER_TYPE=anthropic
COPILOT_PROVIDER_BASE_URL=https://api.anthropic.com
COPILOT_MODEL=claude-sonnet-5
awf-resolved-config.json shows the values were captured:
{ "enableApiProxy": true, "enableTokenSteering": true,
"copilotProviderType": "anthropic",
"copilotProviderBaseUrl": "https://api.anthropic.com",
"copilotApiTarget": "api.anthropic.com" }
and api-proxy/models.json shows which slot actually served the request:
{ "providers": { "copilot": { "configured": true, "target": "api.anthropic.com" },
"anthropic": { "configured": false } } }
Every otel span in the run is api_proxy.copilot.request with gen_ai.provider.name="copilot". POST /v1/messages returned 401 on every attempt; GET /models returned 404 (a Copilot-style catalog probe against an API that has no such route). agent-stdio.log reports API proxy enabled: ... Copilot=true (byok-direct).
The failure is the auth scheme, not the credential. The proxy's anthropic slot injects x-api-key + anthropic-version; the copilot byok-direct slot forwards to the same host without it. copilotProviderType: "anthropic" did not cause slot selection — enableTokenSteering: true / GH_AW_LLM_PROVIDER=github kept the request on the copilot slot.
The credential is valid. One direct POST https://api.anthropic.com/v1/messages with x-api-key, anthropic-version: 2023-06-01, model=claude-sonnet-5, max_tokens=1 returned HTTP 200 with normal usage accounting, using the identical key.
Result: engine: copilot BYOK to an Anthropic provider is documented and configurable but cannot authenticate on this substrate, and no supported per-workflow override exists.
Common root cause
Token steering resolves the provider after the workflow's explicit configuration and without consulting which provider slots are actually provisioned for the run. The alias table's Copilot-first ordering makes Copilot the default winner even when the proxy reports Copilot=false.
What would fix it
- Never steer to an unconfigured provider. Filter the alias candidate list against the run's configured provider slots before resolving. In case 1 this alone produces a working run.
- Honor explicit provider/model configuration over the alias table, or fail loudly when steering overrides it. Silent
sonnet-5 -> sonnet-4.5 substitution inside gh-aw's own emitted config is the worst outcome, because a run that quietly used a different model still looks like a pass.
- Expose an override.
enableTokenSteering is emitted in every lock but is undocumented and not settable from frontmatter. A documented opt-out would make both cases self-serviceable.
- Make "provider not configured" non-retryable. The
title agent's bounded 3-attempt policy is the correct behavior; apply it to the primary agent path.
Environment
Context
Both runs were security probes that treat the agent as hostile and test whether the provider credential is recoverable from inside the agent container. The probes are designed so that a run which did not happen cannot be reported as a pass, which is why these surfaced as hard stops rather than clean sweeps.
Summary
apiProxy.enableTokenSteeringis emitted astruein every compiled lock, is not documented, and is not overridable from frontmatter. In two independent runs on two different engines it silently overrode the provider and model the workflow explicitly asked for, and routed every model call to a provider slot the proxy had just reported as not configured. Both runs produced zero useful work.Neither failure is recoverable by the workflow author, because the steering surface (
enableTokenSteering,GH_AW_LLM_PROVIDER) appears only in the compiled artifact and the resolved config, and the engines/sandbox reference exposes no knob for it.Case 1 —
engine: opencode: model silently downgraded, calls routed to unconfigured CopilotRun
30183258206, gh-awv0.83.2, AWFv0.27.41,ubuntu-latest,workflow_dispatch.What the workflow asked for. The compiled activation and every downstream job carry:
and the agent step exports:
What gh-aw's own
Write OpenCode Configstep then wrote. The provider model map contains a different model:OPENCODE_MODELnamesclaude-sonnet-5; the config gh-aw writes in the adjacent step offers onlyclaude-sonnet-4.5. Every request in the run loggedmodelID=claude-sonnet-4.5. No warning was emitted about the substitution.Where the substitution comes from. The emitted AWF config sets
"apiProxy": { "enabled": true, "enableTokenSteering": true, ... }and defines the alias group the request resolves through with the Copilot patterns listed first:"models": { "agent": ["sonnet-6x", ...] }. An explicitly requestedanthropic/claude-sonnet-5is resolved through this group to the group's first entry,copilot/*sonnet-4.5*— which explains both the provider change and the model downgrade in one step.Why that is fatal here. The proxy had already logged, in the same step, that Copilot is not configured for this run:
Anthropic was configured. Copilot was not. Steering routed to Copilot anyway, and every call failed:
The steering decision is made against a provider the proxy knows is unprovisioned, while the provider that is provisioned sits unused.
Case 1b — the retry loop has no terminal state
The
buildagent retried this same unrecoverable configuration error for 17 minutes, with doubling backoff and no give-up, until the step timeout killed the job:Total agent job: 21m3s, of which ~19 minutes was retrying an error that cannot resolve without a config change. Notably the
titlesub-agent in the same run did the right thing and stopped:So a bounded retry policy already exists; the primary agent path does not use it. "Credentials for provider X are not configured" is a deterministic configuration error and should be non-retryable — retrying it converts a 30-second diagnosable failure into a 20-minute timeout with a misleading error.
Case 2 —
engine: copilotin BYOK mode: explicit BYOK config captured but not honoredRun
30227526593, gh-aw withengine: { id: copilot, version: 1.0.73 }, AWFv0.27.41.Frontmatter set the documented BYOK surface explicitly:
awf-resolved-config.jsonshows the values were captured:{ "enableApiProxy": true, "enableTokenSteering": true, "copilotProviderType": "anthropic", "copilotProviderBaseUrl": "https://api.anthropic.com", "copilotApiTarget": "api.anthropic.com" }and
api-proxy/models.jsonshows which slot actually served the request:{ "providers": { "copilot": { "configured": true, "target": "api.anthropic.com" }, "anthropic": { "configured": false } } }Every otel span in the run is
api_proxy.copilot.requestwithgen_ai.provider.name="copilot".POST /v1/messagesreturned 401 on every attempt;GET /modelsreturned 404 (a Copilot-style catalog probe against an API that has no such route).agent-stdio.logreportsAPI proxy enabled: ... Copilot=true (byok-direct).The failure is the auth scheme, not the credential. The proxy's anthropic slot injects
x-api-key+anthropic-version; the copilot byok-direct slot forwards to the same host without it.copilotProviderType: "anthropic"did not cause slot selection —enableTokenSteering: true/GH_AW_LLM_PROVIDER=githubkept the request on the copilot slot.The credential is valid. One direct
POST https://api.anthropic.com/v1/messageswithx-api-key,anthropic-version: 2023-06-01,model=claude-sonnet-5,max_tokens=1returned HTTP 200 with normal usage accounting, using the identical key.Result:
engine: copilotBYOK to an Anthropic provider is documented and configurable but cannot authenticate on this substrate, and no supported per-workflow override exists.Common root cause
Token steering resolves the provider after the workflow's explicit configuration and without consulting which provider slots are actually provisioned for the run. The alias table's Copilot-first ordering makes Copilot the default winner even when the proxy reports
Copilot=false.What would fix it
sonnet-5->sonnet-4.5substitution inside gh-aw's own emitted config is the worst outcome, because a run that quietly used a different model still looks like a pass.enableTokenSteeringis emitted in every lock but is undocumented and not settable from frontmatter. A documented opt-out would make both cases self-serviceable.titleagent's bounded 3-attempt policy is the correct behavior; apply it to the primary agent path.Environment
v0.83.2(case 1) and theengine: copilot1.0.73 path (case 2)v0.27.41, rootless,apiProxy.enabled: trueubuntu-latest,workflow_dispatchContext
Both runs were security probes that treat the agent as hostile and test whether the provider credential is recoverable from inside the agent container. The probes are designed so that a run which did not happen cannot be reported as a pass, which is why these surfaced as hard stops rather than clean sweeps.