Summary
The compiler-generated lock files write the AWF config via a double-quoted bash printf:
printf '%s\n' "{\"$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.25.40/awf-config.schema.json\",...}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
Inside bash double quotes, $schema is variable expansion. Since $schema is unset on the runner, it expands to an empty string, so the JSON written to disk becomes:
{ "": "https://github.com/.../awf-config.schema.json", "network": {...}, "apiProxy": {...}, "container": {...} }
AWF v0.25.40 then rejects the config:
Error loading --config: Invalid AWF config at /home/runner/work/_temp/gh-aw/awf-config.json:
- config. is not supported
(Note the trailing dot — the unsupported property is the empty string from the lost $schema key.)
Repro
- Compile any agentic workflow with the current gh-aw (v0.71.5 reproduces).
- Inspect the generated
*.lock.yml — you will see printf '%s\n' "{\"$schema\":\"...\",...}" somewhere in the agent job.
- Run the workflow on
ubuntu-latest against AWF v0.25.40.
- Job fails at startup with the error above.
Affected runs (microsoft/vscode-engineering)
All 6 errors-*.lock.yml workflows broke after gh aw compile regenerated them on 2026-05-06; ~80+ failed runs in the last 24h. Example: https://github.com/microsoft/vscode-engineering/actions/runs/25504792956
Suggested fix
In the AWF-config printf template, either:
- Escape the dollar sign:
\"\$schema\" (bash will write literal $schema), or
- Use a single-quoted printf payload:
printf '%s\n' '{"$schema":"...","..."}' — but this conflicts with ${{ }} expressions if any are inlined. Some lock files (single-quoted variants like errors-fix-driver.lock.yml in our repo) already use this form successfully.
- Switch to a heredoc with quoted delimiter, like the existing safeoutputs config:
cat > "${RUNNER_TEMP}/gh-aw/awf-config.json" << 'GH_AW_AWF_CONFIG_EOF'
{"$schema":"...","network":{...}}
GH_AW_AWF_CONFIG_EOF
This is what safeoutputs/config.json already uses in the same lock files and is bullet-proof.
Workaround
Manually sed -i 's|\\"\$schema\\":|\\"\\\$schema\\":|g' each affected *.lock.yml after compile. Overwritten on next compile.
Related
Similar root-cause family to #30307 (config.models is not supported) and to my prior report #30695 (\u0026\u0026 literal in ${{ }}). All three are downstream symptoms of the JSON-emitting layer not accounting for the bash-string context it gets pasted into.
Summary
The compiler-generated lock files write the AWF config via a double-quoted bash printf:
Inside bash double quotes,
$schemais variable expansion. Since$schemais unset on the runner, it expands to an empty string, so the JSON written to disk becomes:{ "": "https://github.com/.../awf-config.schema.json", "network": {...}, "apiProxy": {...}, "container": {...} }AWF v0.25.40 then rejects the config:
(Note the trailing dot — the unsupported property is the empty string from the lost
$schemakey.)Repro
*.lock.yml— you will seeprintf '%s\n' "{\"$schema\":\"...\",...}"somewhere in the agent job.ubuntu-latestagainst AWF v0.25.40.Affected runs (microsoft/vscode-engineering)
All 6
errors-*.lock.ymlworkflows broke aftergh aw compileregenerated them on 2026-05-06; ~80+ failed runs in the last 24h. Example: https://github.com/microsoft/vscode-engineering/actions/runs/25504792956Suggested fix
In the AWF-config printf template, either:
\"\$schema\"(bash will write literal$schema), orprintf '%s\n' '{"$schema":"...","..."}'— but this conflicts with${{ }}expressions if any are inlined. Some lock files (single-quoted variants likeerrors-fix-driver.lock.ymlin our repo) already use this form successfully.safeoutputs/config.jsonalready uses in the same lock files and is bullet-proof.Workaround
Manually
sed -i 's|\\"\$schema\\":|\\"\\\$schema\\":|g'each affected*.lock.ymlafter compile. Overwritten on next compile.Related
Similar root-cause family to #30307 (
config.models is not supported) and to my prior report #30695 (\u0026\u0026literal in${{ }}). All three are downstream symptoms of the JSON-emitting layer not accounting for the bash-string context it gets pasted into.