Created by GitHub Ace · View Session
Summary
With threat-detect v0.4.0, the external detection step aborts with exit 2 before the engine ever runs. Upstream #747 added an artifact-inventory table written to GITHUB_STEP_SUMMARY, and per spec TD-20c a failed write is a config error. Inside the AWF sandbox that write always fails, so:
stepsummary.WriteArtifactInventory → open failure
main.go → reason=config_error → exit 2
(no detection_result.json produced)
conclude → ERR_SYSTEM: Detection result file not found
Engine-agnostic, which is why all three upstream standalone smokes fail identically. Upstream has committed a resiliency fix on their side, but the wiring gap is ours and the inventory table is silently lost until it is fixed here.
This is the blocker for #50644 (the v0.4.0 pin bump) in practice.
Root cause
The detection step inherits GITHUB_STEP_SUMMARY=/home/runner/work/_temp/_runner_file_commands/step_summary_<uuid>. From sandbox/firewall/audit/docker-compose.redacted.yml on a real failing run:
environment:
GITHUB_STEP_SUMMARY: /home/runner/work/_temp/_runner_file_commands/step_summary_7e854bcb-…
volumes:
- /tmp:/tmp:rw
- /home/runner/work/<repo>/<repo>:…:rw
- /tmp/awf-…-chroot-home:/host/home/runner:rw # empty home overlay
Two facts:
-
/home/runner/work/_temp is not mounted, and /home/runner is overlaid by an empty home volume. The runner's file-command path resolves into an empty dir inside the chroot → ENOENT.
This is deliberate AWF isolation and must stay that way. If _runner_file_commands were mounted rw, a sandboxed agent could write GITHUB_ENV / GITHUB_OUTPUT / GITHUB_STEP_SUMMARY and inject directly into the host workflow. Mounting it would be a privilege-escalation hole. Do not "fix" this by adding a mount.
-
gh-aw's step-level GITHUB_STEP_SUMMARY override never reaches the process. The runner re-injects the real file-command variable for run: steps after step-level env: is applied, and AWF forwards the actual process env via --env-all. So the container sees the runner path despite the lock file setting the override.
Confirmed in this repo
The compiled Execute threat detection with AWF step in .github/workflows/ab-testing-advisor.lock.yml does carry the override:
GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md
(inherited from the copilot engine env block via extractStepEnvLines), and the AWF invocation uses --env-all with --mount /tmp/gh-aw/threat-detection:/tmp/gh-aw/threat-detection:rw. The override is present and ineffective, exactly as described.
A second, independent gap: even if the override did work, /tmp/gh-aw/agent-step-summary.md is the agent's summary path. The detection job neither touches it nor appends it — I checked the compiled detection: job and there is no Append agent step summary step and no touch. So the detector's inventory table would be written into a file nobody ever reads. Both halves need fixing.
Proposed fix
Mirror what the agent job already does (touch + append_agent_step_summary.sh), but with a detection-specific path inside the already-rw-mounted detection dir:
- Add a
ThreatDetectionStepSummaryPath constant = /tmp/gh-aw/threat-detection/step-summary.md.
touch it in the host-side PathSetup before the AWF invocation (same pattern as claude_engine.go:399).
- Pass
--step-summary /tmp/gh-aw/threat-detection/step-summary.md explicitly on the threat-detect command line, rather than relying on env. A flag is the correct mechanism here — the whole problem is that env for this variable is not controllable from a run: step, so any env-based fix is fragile by construction.
- Add a post-execution step that appends the file to the real
$GITHUB_STEP_SUMMARY on the host, after redaction, no-op when empty.
- Add the path to
buildUploadDetectionArtifactStep so it survives even if the append step is skipped.
The --step-summary flag exists as of upstream #739 and is supported by both threat-detect and threat-detect conclude.
Alternative considered and rejected: explicitly unsetting GITHUB_STEP_SUMMARY for the sandboxed step. That stops the exit-2 abort but permanently discards the inventory table and the detector's real prompt/verdict blocks, which is most of the value of #50653.
Note on conclude
threat-detect conclude runs on the host, not in the sandbox, so its --step-summary default ($GITHUB_STEP_SUMMARY) works correctly and needs no change. Worth an explicit test so this asymmetry is not "cleaned up" later by someone assuming both need the flag.
Related finding — the agent job's redirect is likely also ineffective
The same mechanism applies to the agent job. Every engine sets GITHUB_STEP_SUMMARY: AgentStepSummaryPath in its step env (claude_engine.go:480, copilot_engine_execution.go:532, codex_engine.go:366, gemini_engine.go:305, pi_engine.go:419) and append_agent_step_summary.sh appends /tmp/gh-aw/agent-step-summary.md afterwards.
If the runner re-injects the file-command variable there too, then agents have been writing to the real summary path — i.e. into the empty chroot overlay, failing silently — and agent-step-summary.md has been empty all along. Nobody noticed because, unlike the detector, no agent treats the write failure as an error.
This should be verified as part of this issue: check whether /tmp/gh-aw/agent-step-summary.md is non-empty on any recent agent run that produced step-summary output. If it is consistently empty, the same flag-or-wrapper fix is needed on the agent path, and it is worth splitting into its own issue since it touches all five engines.
Acceptance criteria
References
Summary
With
threat-detectv0.4.0, the external detection step aborts with exit 2 before the engine ever runs. Upstream #747 added an artifact-inventory table written toGITHUB_STEP_SUMMARY, and per spec TD-20c a failed write is a config error. Inside the AWF sandbox that write always fails, so:Engine-agnostic, which is why all three upstream standalone smokes fail identically. Upstream has committed a resiliency fix on their side, but the wiring gap is ours and the inventory table is silently lost until it is fixed here.
This is the blocker for #50644 (the v0.4.0 pin bump) in practice.
Root cause
The detection step inherits
GITHUB_STEP_SUMMARY=/home/runner/work/_temp/_runner_file_commands/step_summary_<uuid>. Fromsandbox/firewall/audit/docker-compose.redacted.ymlon a real failing run:Two facts:
/home/runner/work/_tempis not mounted, and/home/runneris overlaid by an empty home volume. The runner's file-command path resolves into an empty dir inside the chroot →ENOENT.This is deliberate AWF isolation and must stay that way. If
_runner_file_commandswere mounted rw, a sandboxed agent could writeGITHUB_ENV/GITHUB_OUTPUT/GITHUB_STEP_SUMMARYand inject directly into the host workflow. Mounting it would be a privilege-escalation hole. Do not "fix" this by adding a mount.gh-aw's step-level
GITHUB_STEP_SUMMARYoverride never reaches the process. The runner re-injects the real file-command variable forrun:steps after step-levelenv:is applied, and AWF forwards the actual process env via--env-all. So the container sees the runner path despite the lock file setting the override.Confirmed in this repo
The compiled
Execute threat detection with AWFstep in.github/workflows/ab-testing-advisor.lock.ymldoes carry the override:(inherited from the copilot engine env block via
extractStepEnvLines), and the AWF invocation uses--env-allwith--mount /tmp/gh-aw/threat-detection:/tmp/gh-aw/threat-detection:rw. The override is present and ineffective, exactly as described.A second, independent gap: even if the override did work,
/tmp/gh-aw/agent-step-summary.mdis the agent's summary path. The detection job neithertouches it nor appends it — I checked the compileddetection:job and there is noAppend agent step summarystep and notouch. So the detector's inventory table would be written into a file nobody ever reads. Both halves need fixing.Proposed fix
Mirror what the agent job already does (
touch+append_agent_step_summary.sh), but with a detection-specific path inside the already-rw-mounted detection dir:ThreatDetectionStepSummaryPathconstant =/tmp/gh-aw/threat-detection/step-summary.md.touchit in the host-sidePathSetupbefore the AWF invocation (same pattern asclaude_engine.go:399).--step-summary /tmp/gh-aw/threat-detection/step-summary.mdexplicitly on thethreat-detectcommand line, rather than relying on env. A flag is the correct mechanism here — the whole problem is that env for this variable is not controllable from arun:step, so any env-based fix is fragile by construction.$GITHUB_STEP_SUMMARYon the host, after redaction, no-op when empty.buildUploadDetectionArtifactStepso it survives even if the append step is skipped.The
--step-summaryflag exists as of upstream #739 and is supported by boththreat-detectandthreat-detect conclude.Alternative considered and rejected: explicitly unsetting
GITHUB_STEP_SUMMARYfor the sandboxed step. That stops the exit-2 abort but permanently discards the inventory table and the detector's real prompt/verdict blocks, which is most of the value of #50653.Note on
concludethreat-detect concluderuns on the host, not in the sandbox, so its--step-summarydefault ($GITHUB_STEP_SUMMARY) works correctly and needs no change. Worth an explicit test so this asymmetry is not "cleaned up" later by someone assuming both need the flag.Related finding — the agent job's redirect is likely also ineffective
The same mechanism applies to the agent job. Every engine sets
GITHUB_STEP_SUMMARY: AgentStepSummaryPathin its step env (claude_engine.go:480,copilot_engine_execution.go:532,codex_engine.go:366,gemini_engine.go:305,pi_engine.go:419) andappend_agent_step_summary.shappends/tmp/gh-aw/agent-step-summary.mdafterwards.If the runner re-injects the file-command variable there too, then agents have been writing to the real summary path — i.e. into the empty chroot overlay, failing silently — and
agent-step-summary.mdhas been empty all along. Nobody noticed because, unlike the detector, no agent treats the write failure as an error.This should be verified as part of this issue: check whether
/tmp/gh-aw/agent-step-summary.mdis non-empty on any recent agent run that produced step-summary output. If it is consistently empty, the same flag-or-wrapper fix is needed on the agent path, and it is worth splitting into its own issue since it touches all five engines.Acceptance criteria
--step-summarypassed explicitly to a path inside therw-mountedThreatDetectionDir; no new AWF mount added, and_runner_file_commandsremains unmounted.touched before execution and appended after, no-op when empty.concludeis not given the sandbox path (it runs on the host).AgentStepSummaryPathredirect is also ineffective; follow-up issue filed if so.make recompile.References
GITHUB_STEP_SUMMARYwritability caution in threat-detection: stage prompt-template, prompt-import-tree, aw_info.json and comment-memory into the detection dir #50647 — that bullet should be removed in favour of this issue.