Summary
When the Copilot provider is configured in BYOK mode against a non-Copilot OpenAI-compatible endpoint (e.g. Azure OpenAI), the api-proxy unconditionally injects a top-level session_id field into every upstream request body. Strict OpenAI-compatible servers (including Azure OpenAI's /openai/v1/responses) reject unknown body parameters with HTTP 400, causing every agent turn to fail:
CAPIError: 400 Unknown parameter: 'session_id'.
{
"error": {
"message": "Unknown parameter: 'session_id'.",
"type": "invalid_request_error",
"param": "session_id",
"code": "unknown_parameter"
}
}
The Copilot CLI then exhausts its retry budget and the run fails.
Versions
gh-aw-firewall v0.25.63
gh-aw v0.78.1
copilot CLI v1.0.58
- Provider: Azure OpenAI,
COPILOT_PROVIDER_BASE_URL=https://<resource>.openai.azure.com/openai/v1
Root cause
In containers/api-proxy/providers/copilot.js, createCopilotAdapter does:
const providerSessionId = (env.AWF_PROVIDER_SESSION_ID || '').trim() || undefined;
if (providerSessionId) {
// ...header injection elided...
if (!Object.hasOwn(byokExtraBodyFields, 'session_id')) {
byokExtraBodyFields.session_id = providerSessionId;
}
}
…and then composes a bodyTransform that calls injectByokExtraBodyFields on every request body whenever the BYOK API key is present.
The AWF_PROVIDER_SESSION_ID env var has a fallback chain in src/services/api-proxy-service-config.ts → resolveProviderSessionId:
const value = getConfigEnvValue(config, 'AWF_PROVIDER_SESSION_ID')
?? process.env.AWF_PROVIDER_SESSION_ID
?? process.env.GH_AW_GITHUB_RUN_ID
?? process.env.GITHUB_RUN_ID;
Since GitHub Actions always sets GITHUB_RUN_ID, the fallback always resolves to a non-empty value, so BYOK session_id body injection is on by default for every gh-aw run with a BYOK provider — including providers that don't accept the session_id field.
The x-session-id header injection in the same block is harmless against strict servers (unknown headers are ignored), but the body-field injection is not.
Reproduction
- Configure a gh-aw workflow with
engine: copilot and BYOK against Azure OpenAI:
engine:
id: copilot
env:
COPILOT_PROVIDER_BASE_URL: "https://<resource>.openai.azure.com/openai/v1"
COPILOT_MODEL: <deployment-name>
COPILOT_PROVIDER_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }}
- Trigger the workflow.
- Observe in
sandbox/agent/logs/process-*.log:
CAPIError: 400 Unknown parameter: 'session_id'.
- Observe in
sandbox/firewall/logs/api-proxy-logs/otel.jsonl that every POST /responses returns http.response.status_code: 400.
Impact
Every BYOK Copilot run against an OpenAI-compatible endpoint that enforces strict parameter validation fails immediately. This affects Azure OpenAI (always strict) and likely other gateways that pass through to Azure or otherwise validate request schemas.
Current workaround
In the workflow markdown, force AWF_PROVIDER_SESSION_ID to empty so resolveProviderSessionId returns undefined and the auto-injection is disabled:
engine:
id: copilot
env:
AWF_PROVIDER_SESSION_ID: ${{ '' }}
(${{ '' }} is required because gh-aw's YAML loader drops bare "" values.)
Note: setting apiProxy.targets.copilot.extraBodyFields.session_id = "" from awf-config does not work — the guard is if (!Object.hasOwn(byokExtraBodyFields, 'session_id')), so providing any value (even empty string) still triggers injection of that value. Only an unset/empty AWF_PROVIDER_SESSION_ID actually disables it.
Suggested fixes (one or more)
- Don't enable BYOK body-field injection by default. Only inject
session_id (and any other auto-fields) when the user has opted in — e.g. only when AWF_PROVIDER_SESSION_ID is explicitly set by the caller via additionalEnv / config file, not when it's only resolved from the GITHUB_RUN_ID fallback.
- Scope auto-injection to known-compatible targets.
session_id is a Copilot-API convention; don't auto-inject it when COPILOT_PROVIDER_BASE_URL / COPILOT_API_TARGET points away from *.githubcopilot.com / *.ghe.com. The isGithubCopilotCatalogTarget helper already exists for the model-fallback policy and could be reused here.
- Document a first-class opt-out in
awf-config.json (e.g. apiProxy.targets.copilot.disableSessionIdInjection: true) and a corresponding env var, so users don't have to know about the AWF_PROVIDER_SESSION_ID="" trick.
Option (2) seems most aligned with the existing model-fallback suppression for non-Copilot BYOK targets and would fix this without requiring user action.
Related files
containers/api-proxy/providers/copilot.js — createCopilotAdapter, injectByokExtraBodyFields, parseByokExtraBodyFields
src/services/api-proxy-service-config.ts — resolveProviderSessionId, buildProviderTargetEnv
containers/api-proxy/README.md — AWF_BYOK_EXTRA_BODY_FIELDS, AWF_PROVIDER_SESSION_ID docs
Summary
When the Copilot provider is configured in BYOK mode against a non-Copilot OpenAI-compatible endpoint (e.g. Azure OpenAI), the api-proxy unconditionally injects a top-level
session_idfield into every upstream request body. Strict OpenAI-compatible servers (including Azure OpenAI's/openai/v1/responses) reject unknown body parameters with HTTP 400, causing every agent turn to fail:The Copilot CLI then exhausts its retry budget and the run fails.
Versions
gh-aw-firewallv0.25.63gh-awv0.78.1copilotCLIv1.0.58COPILOT_PROVIDER_BASE_URL=https://<resource>.openai.azure.com/openai/v1Root cause
In
containers/api-proxy/providers/copilot.js,createCopilotAdapterdoes:…and then composes a
bodyTransformthat callsinjectByokExtraBodyFieldson every request body whenever the BYOK API key is present.The
AWF_PROVIDER_SESSION_IDenv var has a fallback chain insrc/services/api-proxy-service-config.ts → resolveProviderSessionId:Since GitHub Actions always sets
GITHUB_RUN_ID, the fallback always resolves to a non-empty value, so BYOKsession_idbody injection is on by default for every gh-aw run with a BYOK provider — including providers that don't accept thesession_idfield.The
x-session-idheader injection in the same block is harmless against strict servers (unknown headers are ignored), but the body-field injection is not.Reproduction
engine: copilotand BYOK against Azure OpenAI:sandbox/agent/logs/process-*.log:sandbox/firewall/logs/api-proxy-logs/otel.jsonlthat every POST/responsesreturnshttp.response.status_code: 400.Impact
Every BYOK Copilot run against an OpenAI-compatible endpoint that enforces strict parameter validation fails immediately. This affects Azure OpenAI (always strict) and likely other gateways that pass through to Azure or otherwise validate request schemas.
Current workaround
In the workflow markdown, force
AWF_PROVIDER_SESSION_IDto empty soresolveProviderSessionIdreturnsundefinedand the auto-injection is disabled:(
${{ '' }}is required because gh-aw's YAML loader drops bare""values.)Note: setting
apiProxy.targets.copilot.extraBodyFields.session_id = ""from awf-config does not work — the guard isif (!Object.hasOwn(byokExtraBodyFields, 'session_id')), so providing any value (even empty string) still triggers injection of that value. Only an unset/emptyAWF_PROVIDER_SESSION_IDactually disables it.Suggested fixes (one or more)
session_id(and any other auto-fields) when the user has opted in — e.g. only whenAWF_PROVIDER_SESSION_IDis explicitly set by the caller viaadditionalEnv/ config file, not when it's only resolved from theGITHUB_RUN_IDfallback.session_idis a Copilot-API convention; don't auto-inject it whenCOPILOT_PROVIDER_BASE_URL/COPILOT_API_TARGETpoints away from*.githubcopilot.com/*.ghe.com. TheisGithubCopilotCatalogTargethelper already exists for the model-fallback policy and could be reused here.awf-config.json(e.g.apiProxy.targets.copilot.disableSessionIdInjection: true) and a corresponding env var, so users don't have to know about theAWF_PROVIDER_SESSION_ID=""trick.Option (2) seems most aligned with the existing model-fallback suppression for non-Copilot BYOK targets and would fix this without requiring user action.
Related files
containers/api-proxy/providers/copilot.js—createCopilotAdapter,injectByokExtraBodyFields,parseByokExtraBodyFieldssrc/services/api-proxy-service-config.ts—resolveProviderSessionId,buildProviderTargetEnvcontainers/api-proxy/README.md—AWF_BYOK_EXTRA_BODY_FIELDS,AWF_PROVIDER_SESSION_IDdocs