Summary
Under runner.topology: arc-dind with the copilot engine, gh-aw exports
XDG_CONFIG_HOME="$HOME", but at the point that export runs $HOME is the unwritable
passwd home /home/runner (root-owned) — not the writable arc-dind home
${RUNNER_TEMP}/gh-aw/home that gh-aw itself sets HOME to. Any tool that resolves its config
dir from XDG_CONFIG_HOME (Flutter's Config/tool_state, and others) then tries to write into
/home/runner and fails with a permission error:
Flutter failed to write to a file at "/home/runner/tool_state".
The flutter tool cannot access the file or directory.
Please ensure that the SDK and/or project is installed in a location that has read/write permissions for the current user.
Impact
- Breaks any XDG-respecting tool run by the agent under arc-dind (observed with the Flutter SDK,
but it affects anything using XDG_CONFIG_HOME).
HOME itself is correctly the writable ${RUNNER_TEMP}/gh-aw/home, so this is subtle: only
the XDG-derived config path is poisoned, which masks the root cause.
- Setting
XDG_CONFIG_HOME via the workflow engine.env does not help — this
export XDG_CONFIG_HOME="$HOME" runs later and overwrites it. The only reliable consumer-side
workaround is to override HOME to a writable literal in engine.env so $HOME is writable
when the export evaluates.
Environment
Diagnostic (from inside the sandbox)
A trivial agent step dumping the environment shows the split:
HOME=/home/runner/_work/_temp/gh-aw/home # correct, writable
XDG_CONFIG_HOME=/home/runner # WRONG — unwritable
id -> uid=1001(runner) gid=1001(runner)
getent passwd 1001 -> runner:x:1001:1001:...:/home/runner:/bin/bash # passwd home = /home/runner
ls -ld /home/runner -> drwxr-xr-x root root ... writable: NO
ls -ld /home/runner/_work/_temp/gh-aw/home-> drwxr-xr-x runner runner writable: yes
So HOME was correctly overridden to the writable arc-dind home, but XDG_CONFIG_HOME captured
the unwritable passwd home /home/runner.
Root cause
Permalinks against main:
pkg/workflow/copilot_engine_execution.go#L115 — buildCopilotMCPConfigExport always emits:
b.WriteString("export XDG_CONFIG_HOME=\"$HOME\"\n")
(doc comment: "XDG_CONFIG_HOME=$HOME (Copilot CLI resolves its config dir from this)").
- Under arc-dind, gh-aw already knows the writable home path — it's the compiler constant
awfArcDindHomePathExpr = "${RUNNER_TEMP}/gh-aw/home" (pkg/workflow/awf_helpers.go#L47), and
rewriteArcDindEngineCommand (awf_helpers.go#L142) prepends export HOME=${RUNNER_TEMP}/gh-aw/home
to the engine command.
- But
XDG_CONFIG_HOME is indirected through $HOME, and at exec time inside the chroot $HOME
resolves to the synthesized passwd home /home/runner (root-owned, unwritable) rather than the
writable arc-dind home. Result: XDG_CONFIG_HOME=/home/runner.
Net: HOME is fixed up for arc-dind, but the XDG export that "follows" it captures the wrong,
unwritable home.
Suggested fix
When compiling for runner.topology: arc-dind, export XDG_CONFIG_HOME to the same writable
constant used for HOME, instead of indirecting through $HOME:
// arc-dind: XDG must point at the writable arc-dind home, not the (unwritable) passwd $HOME
b.WriteString(fmt.Sprintf("export XDG_CONFIG_HOME=%q\n", awfArcDindHomePathExpr))
(Non-arc-dind keeps export XDG_CONFIG_HOME="$HOME".) Alternatively, ensure the HOME override
is in effect in the same shell before the XDG export evaluates.
Notes
Summary
Under
runner.topology: arc-dindwith thecopilotengine, gh-aw exportsXDG_CONFIG_HOME="$HOME", but at the point that export runs$HOMEis the unwritablepasswd home
/home/runner(root-owned) — not the writable arc-dind home${RUNNER_TEMP}/gh-aw/homethat gh-aw itself setsHOMEto. Any tool that resolves its configdir from
XDG_CONFIG_HOME(Flutter'sConfig/tool_state, and others) then tries to write into/home/runnerand fails with a permission error:Impact
but it affects anything using
XDG_CONFIG_HOME).HOMEitself is correctly the writable${RUNNER_TEMP}/gh-aw/home, so this is subtle: onlythe XDG-derived config path is poisoned, which masks the root cause.
XDG_CONFIG_HOMEvia the workflowengine.envdoes not help — thisexport XDG_CONFIG_HOME="$HOME"runs later and overwrites it. The only reliable consumer-sideworkaround is to override
HOMEto a writable literal inengine.envso$HOMEis writablewhen the export evaluates.
Environment
copilot. Topology:runner.topology: arc-dind(self-hosted ARC + AWF firewall).main.6171a09696("fix(copilot): support arbitrary HOME directory, not just /home/runner (fix(copilot): support arbitrary HOME directory, not just /home/runner #38725)").
Diagnostic (from inside the sandbox)
A trivial agent step dumping the environment shows the split:
So
HOMEwas correctly overridden to the writable arc-dind home, butXDG_CONFIG_HOMEcapturedthe unwritable passwd home
/home/runner.Root cause
Permalinks against
main:pkg/workflow/copilot_engine_execution.go#L115—buildCopilotMCPConfigExportalways emits:awfArcDindHomePathExpr = "${RUNNER_TEMP}/gh-aw/home"(pkg/workflow/awf_helpers.go#L47), andrewriteArcDindEngineCommand(awf_helpers.go#L142) prependsexport HOME=${RUNNER_TEMP}/gh-aw/hometo the engine command.
XDG_CONFIG_HOMEis indirected through$HOME, and at exec time inside the chroot$HOMEresolves to the synthesized passwd home
/home/runner(root-owned, unwritable) rather than thewritable arc-dind home. Result:
XDG_CONFIG_HOME=/home/runner.Net:
HOMEis fixed up for arc-dind, but the XDG export that "follows" it captures the wrong,unwritable home.
Suggested fix
When compiling for
runner.topology: arc-dind, exportXDG_CONFIG_HOMEto the same writableconstant used for
HOME, instead of indirecting through$HOME:(Non-arc-dind keeps
export XDG_CONFIG_HOME="$HOME".) Alternatively, ensure theHOMEoverrideis in effect in the same shell before the XDG export evaluates.
Notes
base-branch/GH_AW_INPUT_*not passed to the MCPcontainer). Both surfaced upgrading a Flutter workflow to native arc-dind.
HOMEto a writable literal inengine.envso$HOMEiswritable when
export XDG_CONFIG_HOME="$HOME"runs.