Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions pkg/workflow/awf_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,29 @@ func TestBuildAWFCommand_UsesConfigFile(t *testing.T) {
assert.Contains(t, command, `"enabled":true`, "config JSON should have apiProxy enabled")
}

func TestBuildAWFCommand_IncludesSquidStartupRetryAndLogs(t *testing.T) {
config := AWFCommandConfig{
EngineName: "copilot",
EngineCommand: "copilot --prompt-file /tmp/prompt.txt",
LogFile: "/tmp/gh-aw/agent-stdio.log",
AllowedDomains: "github.com",
WorkflowData: &WorkflowData{
EngineConfig: &EngineConfig{ID: "copilot"},
NetworkPermissions: &NetworkPermissions{
Firewall: &FirewallConfig{Enabled: true},
},
},
}

command := BuildAWFCommand(config)

assert.Contains(t, command, "awf_bootstrap_retry_max=3", "expected retry max for AWF startup")
assert.Contains(t, command, "awf_bootstrap_retry_delay=5", "expected retry backoff for AWF startup")
assert.Contains(t, command, "dependency failed to start: container awf-squid is unhealthy", "expected squid healthcheck retry match")
assert.Contains(t, command, "docker compose up -d --pull never", "expected compose failure retry match")
assert.Contains(t, command, "docker logs awf-squid", "expected squid logs capture on final failure")
}

func TestBuildAWFCommand_PreservesGitHubExpressionOperatorsInConfigJSON(t *testing.T) {
config := AWFCommandConfig{
EngineName: "copilot",
Expand Down
82 changes: 46 additions & 36 deletions pkg/workflow/awf_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,44 @@ fi`,
// Build the complete command with proper formatting.
// configFileSetup (if non-empty) writes the AWF config JSON immediately before the
// AWF invocation so the file is present when AWF parses --config.
awfRunWithRetry := fmt.Sprintf(`awf_bootstrap_retry_max=3
awf_bootstrap_retry_delay=5
awf_bootstrap_attempt=1
while true; do
awf_attempt_log=$(umask 177 && mktemp "${RUNNER_TEMP:-/tmp}/awf-startup-XXXXXX.log")
# shellcheck disable=SC1003
%s %s %s %s \
-- %s 2>&1 | tee "$awf_attempt_log" | tee -a %s
awf_exit=${PIPESTATUS[0]}
if [[ $awf_exit -eq 0 ]]; then
rm -f "$awf_attempt_log"
break
fi
if grep -Fq "dependency failed to start: container awf-squid is unhealthy" "$awf_attempt_log" || \
(grep -Fq "Failed to start containers:" "$awf_attempt_log" && grep -Fq "docker compose up -d --pull never" "$awf_attempt_log"); then
if [[ $awf_bootstrap_attempt -lt $awf_bootstrap_retry_max ]]; then
echo "[WARN] AWF startup failed due to awf-squid healthcheck (attempt $awf_bootstrap_attempt/$awf_bootstrap_retry_max); retrying in ${awf_bootstrap_retry_delay}s..." | tee -a %s
rm -f "$awf_attempt_log"
sleep "$awf_bootstrap_retry_delay"
awf_bootstrap_attempt=$((awf_bootstrap_attempt + 1))
continue
fi
echo "[ERROR] AWF startup failed after $awf_bootstrap_retry_max attempts; capturing awf-squid logs" | tee -a %s
docker logs awf-squid 2>&1 | tail -200 | sed 's/^/[awf-squid] /' | tee -a %s || true
fi
rm -f "$awf_attempt_log"
exit "$awf_exit"
done`,
awfCommand,
expandableArgs,
arcDindPrefixArgsRef,
shellJoinArgs(awfArgs),
shellWrappedCommand,
shellEscapeArg(config.LogFile),
shellEscapeArg(config.LogFile),
shellEscapeArg(config.LogFile),
shellEscapeArg(config.LogFile))

var command string
if config.PathSetup != "" && configFileSetup != "" {
command = fmt.Sprintf(`set -o pipefail
Expand All @@ -273,76 +311,48 @@ fi`,
%s
%s
%s
# shellcheck disable=SC1003
%s %s %s %s \
-- %s 2>&1 | tee -a %s`,
%s`,
writeAgentCLIStartMs,
config.PathSetup,
preCreateLog,
configFileSetup,
arcDindPrefixProbe,
awfCommand,
expandableArgs,
arcDindPrefixArgsRef,
shellJoinArgs(awfArgs),
shellWrappedCommand,
shellEscapeArg(config.LogFile))
awfRunWithRetry)
} else if config.PathSetup != "" {
// Include path setup before AWF command (runs on host before AWF)
command = fmt.Sprintf(`set -o pipefail
%s
%s
%s
%s
# shellcheck disable=SC1003
%s %s %s %s \
-- %s 2>&1 | tee -a %s`,
%s`,
writeAgentCLIStartMs,
config.PathSetup,
preCreateLog,
arcDindPrefixProbe,
awfCommand,
expandableArgs,
arcDindPrefixArgsRef,
shellJoinArgs(awfArgs),
shellWrappedCommand,
shellEscapeArg(config.LogFile))
awfRunWithRetry)
} else if configFileSetup != "" {
command = fmt.Sprintf(`set -o pipefail
%s
%s
%s
%s
# shellcheck disable=SC1003
%s %s %s %s \
-- %s 2>&1 | tee -a %s`,
%s`,
writeAgentCLIStartMs,
preCreateLog,
configFileSetup,
arcDindPrefixProbe,
awfCommand,
expandableArgs,
arcDindPrefixArgsRef,
shellJoinArgs(awfArgs),
shellWrappedCommand,
shellEscapeArg(config.LogFile))
awfRunWithRetry)
} else {
command = fmt.Sprintf(`set -o pipefail
%s
%s
%s
# shellcheck disable=SC1003
%s %s %s %s \
-- %s 2>&1 | tee -a %s`,
%s`,
writeAgentCLIStartMs,
preCreateLog,
arcDindPrefixProbe,
awfCommand,
expandableArgs,
arcDindPrefixArgsRef,
shellJoinArgs(awfArgs),
shellWrappedCommand,
shellEscapeArg(config.LogFile))
awfRunWithRetry)
}

awfHelpersLog.Print("Successfully built AWF command")
Expand Down
31 changes: 28 additions & 3 deletions pkg/workflow/testdata/TestWasmGolden_AllEngines/claude.golden
Original file line number Diff line number Diff line change
Expand Up @@ -523,9 +523,34 @@ jobs:
if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw"
fi
# shellcheck disable=SC1003
sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --tty --env-all --exclude-env ANTHROPIC_API_KEY --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \
-- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/claude_harness.cjs claude --print --no-chrome --allowed-tools '\''Bash,BashOutput,Edit,Edit(/tmp/*),Edit(/tmp/gh-aw/agent/*),ExitPlanMode,Glob,Grep,KillBash,LS,MultiEdit,MultiEdit(/tmp/*),MultiEdit(/tmp/gh-aw/agent/*),NotebookEdit,NotebookRead,Read,Read(/tmp/*),Read(/tmp/gh-aw/agent/*),Task,TodoWrite,Write,Write(/tmp/*),Write(/tmp/gh-aw/agent/*),mcp__github__download_workflow_run_artifact,mcp__github__get_code_scanning_alert,mcp__github__get_commit,mcp__github__get_dependabot_alert,mcp__github__get_discussion,mcp__github__get_discussion_comments,mcp__github__get_file_contents,mcp__github__get_job_logs,mcp__github__get_label,mcp__github__get_latest_release,mcp__github__get_me,mcp__github__get_notification_details,mcp__github__get_pull_request,mcp__github__get_pull_request_comments,mcp__github__get_pull_request_diff,mcp__github__get_pull_request_files,mcp__github__get_pull_request_review_comments,mcp__github__get_pull_request_reviews,mcp__github__get_pull_request_status,mcp__github__get_release_by_tag,mcp__github__get_secret_scanning_alert,mcp__github__get_tag,mcp__github__get_workflow_run,mcp__github__get_workflow_run_logs,mcp__github__get_workflow_run_usage,mcp__github__issue_read,mcp__github__list_branches,mcp__github__list_code_scanning_alerts,mcp__github__list_commits,mcp__github__list_dependabot_alerts,mcp__github__list_discussion_categories,mcp__github__list_discussions,mcp__github__list_issue_types,mcp__github__list_issues,mcp__github__list_label,mcp__github__list_notifications,mcp__github__list_pull_requests,mcp__github__list_releases,mcp__github__list_secret_scanning_alerts,mcp__github__list_starred_repositories,mcp__github__list_tags,mcp__github__list_workflow_jobs,mcp__github__list_workflow_run_artifacts,mcp__github__list_workflow_runs,mcp__github__list_workflows,mcp__github__pull_request_read,mcp__github__search_code,mcp__github__search_issues,mcp__github__search_orgs,mcp__github__search_pull_requests,mcp__github__search_repositories,mcp__github__search_users'\'' --debug-file /tmp/gh-aw/agent-stdio.log --verbose --permission-mode acceptEdits --output-format stream-json --mcp-config "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt${GH_AW_MODEL_DETECTION_CLAUDE:+ --model "$GH_AW_MODEL_DETECTION_CLAUDE"}' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
awf_bootstrap_retry_max=3
awf_bootstrap_retry_delay=5
awf_bootstrap_attempt=1
while true; do
awf_attempt_log=$(umask 177 && mktemp "${RUNNER_TEMP:-/tmp}/awf-startup-XXXXXX.log")
# shellcheck disable=SC1003
sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --tty --env-all --exclude-env ANTHROPIC_API_KEY --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \
-- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/claude_harness.cjs claude --print --no-chrome --allowed-tools '\''Bash,BashOutput,Edit,Edit(/tmp/*),Edit(/tmp/gh-aw/agent/*),ExitPlanMode,Glob,Grep,KillBash,LS,MultiEdit,MultiEdit(/tmp/*),MultiEdit(/tmp/gh-aw/agent/*),NotebookEdit,NotebookRead,Read,Read(/tmp/*),Read(/tmp/gh-aw/agent/*),Task,TodoWrite,Write,Write(/tmp/*),Write(/tmp/gh-aw/agent/*),mcp__github__download_workflow_run_artifact,mcp__github__get_code_scanning_alert,mcp__github__get_commit,mcp__github__get_dependabot_alert,mcp__github__get_discussion,mcp__github__get_discussion_comments,mcp__github__get_file_contents,mcp__github__get_job_logs,mcp__github__get_label,mcp__github__get_latest_release,mcp__github__get_me,mcp__github__get_notification_details,mcp__github__get_pull_request,mcp__github__get_pull_request_comments,mcp__github__get_pull_request_diff,mcp__github__get_pull_request_files,mcp__github__get_pull_request_review_comments,mcp__github__get_pull_request_reviews,mcp__github__get_pull_request_status,mcp__github__get_release_by_tag,mcp__github__get_secret_scanning_alert,mcp__github__get_tag,mcp__github__get_workflow_run,mcp__github__get_workflow_run_logs,mcp__github__get_workflow_run_usage,mcp__github__issue_read,mcp__github__list_branches,mcp__github__list_code_scanning_alerts,mcp__github__list_commits,mcp__github__list_dependabot_alerts,mcp__github__list_discussion_categories,mcp__github__list_discussions,mcp__github__list_issue_types,mcp__github__list_issues,mcp__github__list_label,mcp__github__list_notifications,mcp__github__list_pull_requests,mcp__github__list_releases,mcp__github__list_secret_scanning_alerts,mcp__github__list_starred_repositories,mcp__github__list_tags,mcp__github__list_workflow_jobs,mcp__github__list_workflow_run_artifacts,mcp__github__list_workflow_runs,mcp__github__list_workflows,mcp__github__pull_request_read,mcp__github__search_code,mcp__github__search_issues,mcp__github__search_orgs,mcp__github__search_pull_requests,mcp__github__search_repositories,mcp__github__search_users'\'' --debug-file /tmp/gh-aw/agent-stdio.log --verbose --permission-mode acceptEdits --output-format stream-json --mcp-config "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt${GH_AW_MODEL_DETECTION_CLAUDE:+ --model "$GH_AW_MODEL_DETECTION_CLAUDE"}' 2>&1 | tee "$awf_attempt_log" | tee -a /tmp/gh-aw/agent-stdio.log
awf_exit=${PIPESTATUS[0]}
if [[ $awf_exit -eq 0 ]]; then
rm -f "$awf_attempt_log"
break
fi
if grep -Fq "dependency failed to start: container awf-squid is unhealthy" "$awf_attempt_log" || \
(grep -Fq "Failed to start containers:" "$awf_attempt_log" && grep -Fq "docker compose up -d --pull never" "$awf_attempt_log"); then
if [[ $awf_bootstrap_attempt -lt $awf_bootstrap_retry_max ]]; then
echo "[WARN] AWF startup failed due to awf-squid healthcheck (attempt $awf_bootstrap_attempt/$awf_bootstrap_retry_max); retrying in ${awf_bootstrap_retry_delay}s..." | tee -a /tmp/gh-aw/agent-stdio.log
rm -f "$awf_attempt_log"
sleep "$awf_bootstrap_retry_delay"
awf_bootstrap_attempt=$((awf_bootstrap_attempt + 1))
continue
fi
echo "[ERROR] AWF startup failed after $awf_bootstrap_retry_max attempts; capturing awf-squid logs" | tee -a /tmp/gh-aw/agent-stdio.log
docker logs awf-squid 2>&1 | tail -200 | sed 's/^/[awf-squid] /' | tee -a /tmp/gh-aw/agent-stdio.log || true
fi
rm -f "$awf_attempt_log"
exit "$awf_exit"
done
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
BASH_DEFAULT_TIMEOUT_MS: 60000
Expand Down
31 changes: 28 additions & 3 deletions pkg/workflow/testdata/TestWasmGolden_AllEngines/codex.golden
Original file line number Diff line number Diff line change
Expand Up @@ -493,9 +493,34 @@ jobs:
if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw"
fi
# shellcheck disable=SC1003
sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \
-- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/codex_harness.cjs codex exec${GH_AW_MODEL_DETECTION_CODEX:+ --model "$GH_AW_MODEL_DETECTION_CODEX"} -c web_search="disabled" --dangerously-bypass-approvals-and-sandbox --skip-git-repo-check --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
awf_bootstrap_retry_max=3
awf_bootstrap_retry_delay=5
awf_bootstrap_attempt=1
while true; do
awf_attempt_log=$(umask 177 && mktemp "${RUNNER_TEMP:-/tmp}/awf-startup-XXXXXX.log")
# shellcheck disable=SC1003
sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \
-- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/codex_harness.cjs codex exec${GH_AW_MODEL_DETECTION_CODEX:+ --model "$GH_AW_MODEL_DETECTION_CODEX"} -c web_search="disabled" --dangerously-bypass-approvals-and-sandbox --skip-git-repo-check --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee "$awf_attempt_log" | tee -a /tmp/gh-aw/agent-stdio.log
awf_exit=${PIPESTATUS[0]}
if [[ $awf_exit -eq 0 ]]; then
rm -f "$awf_attempt_log"
break
fi
if grep -Fq "dependency failed to start: container awf-squid is unhealthy" "$awf_attempt_log" || \
(grep -Fq "Failed to start containers:" "$awf_attempt_log" && grep -Fq "docker compose up -d --pull never" "$awf_attempt_log"); then
if [[ $awf_bootstrap_attempt -lt $awf_bootstrap_retry_max ]]; then
echo "[WARN] AWF startup failed due to awf-squid healthcheck (attempt $awf_bootstrap_attempt/$awf_bootstrap_retry_max); retrying in ${awf_bootstrap_retry_delay}s..." | tee -a /tmp/gh-aw/agent-stdio.log
rm -f "$awf_attempt_log"
sleep "$awf_bootstrap_retry_delay"
awf_bootstrap_attempt=$((awf_bootstrap_attempt + 1))
continue
fi
echo "[ERROR] AWF startup failed after $awf_bootstrap_retry_max attempts; capturing awf-squid logs" | tee -a /tmp/gh-aw/agent-stdio.log
docker logs awf-squid 2>&1 | tail -200 | sed 's/^/[awf-squid] /' | tee -a /tmp/gh-aw/agent-stdio.log || true
fi
rm -f "$awf_attempt_log"
exit "$awf_exit"
done
env:
CODEX_API_KEY: ${{ secrets.CODEX_API_KEY || secrets.OPENAI_API_KEY }}
CODEX_HOME: /tmp/gh-aw/mcp-config
Expand Down
Loading