diff --git a/.github/workflows/artifacts-summary.lock.yml b/.github/workflows/artifacts-summary.lock.yml
index e0690988983..721cb233a94 100644
--- a/.github/workflows/artifacts-summary.lock.yml
+++ b/.github/workflows/artifacts-summary.lock.yml
@@ -104,10 +104,7 @@ jobs:
with:
persist-credentials: false
- name: Create gh-aw temp directory
- run: |
- mkdir -p /tmp/gh-aw/agent
- mkdir -p /tmp/gh-aw/sandbox/agent/logs
- echo "Created /tmp/gh-aw/agent directory for agentic workflow temporary files"
+ run: bash /tmp/gh-aw/actions/create_gh_aw_tmp_dir.sh
- name: Configure Git credentials
env:
REPO_NAME: ${{ github.repository }}
@@ -1860,9 +1857,7 @@ jobs:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
- run: |
- PROMPT_DIR="$(dirname "$GH_AW_PROMPT")"
- mkdir -p "$PROMPT_DIR"
+ run: bash /tmp/gh-aw/actions/create_prompt_first.sh
cat << 'PROMPT_EOF' > "$GH_AW_PROMPT"
## Report Structure
@@ -2288,21 +2283,7 @@ jobs:
- name: Print prompt
env:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- run: |
- # Print prompt to workflow logs (equivalent to core.info)
- echo "Generated Prompt:"
- cat "$GH_AW_PROMPT"
- # Print prompt to step summary
- {
- echo ""
- echo "Generated Prompt
"
- echo ""
- echo '``````markdown'
- cat "$GH_AW_PROMPT"
- echo '``````'
- echo ""
- echo " "
- } >> "$GITHUB_STEP_SUMMARY"
+ run: bash /tmp/gh-aw/actions/print_prompt_summary.sh
- name: Upload prompt
if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
diff --git a/.github/workflows/audit-workflows.lock.yml b/.github/workflows/audit-workflows.lock.yml
index b90b9bbc28d..d6b16e9f5ad 100644
--- a/.github/workflows/audit-workflows.lock.yml
+++ b/.github/workflows/audit-workflows.lock.yml
@@ -111,10 +111,7 @@ jobs:
with:
persist-credentials: false
- name: Create gh-aw temp directory
- run: |
- mkdir -p /tmp/gh-aw/agent
- mkdir -p /tmp/gh-aw/sandbox/agent/logs
- echo "Created /tmp/gh-aw/agent directory for agentic workflow temporary files"
+ run: bash /tmp/gh-aw/actions/create_gh_aw_tmp_dir.sh
- name: Set up Go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
with:
@@ -161,11 +158,7 @@ jobs:
# Cache memory file share configuration from frontmatter processed below
- name: Create cache-memory directory
- run: |
- mkdir -p /tmp/gh-aw/cache-memory
- echo "Cache memory directory created at /tmp/gh-aw/cache-memory"
- echo "This folder provides persistent file storage across workflow runs"
- echo "LLMs and agentic tools can freely read and write files in this directory"
+ run: bash /tmp/gh-aw/actions/create_cache_memory_dir.sh
- name: Restore cache memory file share data
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
@@ -1945,9 +1938,7 @@ jobs:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
- run: |
- PROMPT_DIR="$(dirname "$GH_AW_PROMPT")"
- mkdir -p "$PROMPT_DIR"
+ run: bash /tmp/gh-aw/actions/create_prompt_first.sh
cat << 'PROMPT_EOF' > "$GH_AW_PROMPT"
@@ -2584,21 +2575,7 @@ jobs:
- name: Print prompt
env:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- run: |
- # Print prompt to workflow logs (equivalent to core.info)
- echo "Generated Prompt:"
- cat "$GH_AW_PROMPT"
- # Print prompt to step summary
- {
- echo ""
- echo "Generated Prompt
"
- echo ""
- echo '``````markdown'
- cat "$GH_AW_PROMPT"
- echo '``````'
- echo ""
- echo " "
- } >> "$GITHUB_STEP_SUMMARY"
+ run: bash /tmp/gh-aw/actions/print_prompt_summary.sh
- name: Upload prompt
if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
diff --git a/.github/workflows/daily-choice-test.lock.yml b/.github/workflows/daily-choice-test.lock.yml
index 87be9396830..8bba15419d1 100644
--- a/.github/workflows/daily-choice-test.lock.yml
+++ b/.github/workflows/daily-choice-test.lock.yml
@@ -98,10 +98,7 @@ jobs:
with:
persist-credentials: false
- name: Create gh-aw temp directory
- run: |
- mkdir -p /tmp/gh-aw/agent
- mkdir -p /tmp/gh-aw/sandbox/agent/logs
- echo "Created /tmp/gh-aw/agent directory for agentic workflow temporary files"
+ run: bash /tmp/gh-aw/actions/create_gh_aw_tmp_dir.sh
- name: Configure Git credentials
env:
REPO_NAME: ${{ github.repository }}
@@ -1786,9 +1783,7 @@ jobs:
env:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
- run: |
- PROMPT_DIR="$(dirname "$GH_AW_PROMPT")"
- mkdir -p "$PROMPT_DIR"
+ run: bash /tmp/gh-aw/actions/create_prompt_first.sh
cat << 'PROMPT_EOF' > "$GH_AW_PROMPT"
# Daily Choice Type Test
@@ -2107,21 +2102,7 @@ jobs:
- name: Print prompt
env:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- run: |
- # Print prompt to workflow logs (equivalent to core.info)
- echo "Generated Prompt:"
- cat "$GH_AW_PROMPT"
- # Print prompt to step summary
- {
- echo ""
- echo "Generated Prompt
"
- echo ""
- echo '``````markdown'
- cat "$GH_AW_PROMPT"
- echo '``````'
- echo ""
- echo " "
- } >> "$GITHUB_STEP_SUMMARY"
+ run: bash /tmp/gh-aw/actions/print_prompt_summary.sh
- name: Upload prompt
if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
diff --git a/actions/setup/README.md b/actions/setup/README.md
index 99957267467..834bbf8a6cf 100644
--- a/actions/setup/README.md
+++ b/actions/setup/README.md
@@ -4,9 +4,13 @@ This action copies workflow script files to the agent environment.
## Description
-This action runs in all workflow jobs to provide JavaScript scripts that can be required instead of being inlined in the workflow. This includes scripts for activation jobs, agent jobs, and safe-output jobs.
+This action runs in all workflow jobs to provide scripts that can be used instead of being inlined in the workflow. This includes scripts for activation jobs, agent jobs, and safe-output jobs.
-The action copies 117 `.cjs` JavaScript files from the `js/` directory to a destination directory (default: `/tmp/gh-aw/actions`). These files are generated by running `make actions-build` and are committed to the repository.
+The action copies:
+- 117 `.cjs` JavaScript files from the `js/` directory
+- 6 `.sh` shell scripts from the `sh/` directory
+
+All files are copied to the destination directory (default: `/tmp/gh-aw/actions`). These files are generated by running `make actions-build` and are committed to the repository.
## Usage
@@ -31,7 +35,7 @@ Default: `/tmp/gh-aw/actions`
### `files-copied`
-The number of files copied to the destination directory (should be 117).
+The number of files copied to the destination directory (should be 123: 117 JavaScript files + 6 shell scripts).
## Example
@@ -50,25 +54,34 @@ steps:
## Files Included
-This action copies 117 `.cjs` files from `actions/setup/js/`, including:
+This action copies files from `actions/setup/`, including:
+### JavaScript Files (117 files from `js/`)
- Activation job scripts (check_stop_time, check_skip_if_match, check_command_position, etc.)
- Agent job scripts (compute_text, create_issue, create_pull_request, etc.)
- Safe output scripts (safe_outputs_*, safe_inputs_*, messages, etc.)
- Utility scripts (sanitize_*, validate_*, generate_*, etc.)
-All files are copied from the committed `js/` directory which is populated by running `make actions-build` during development.
+### Shell Scripts (6 files from `sh/`)
+- `create_gh_aw_tmp_dir.sh` - Creates temporary directory structure
+- `start_safe_inputs_server.sh` - Starts safe-inputs HTTP server
+- `print_prompt_summary.sh` - Prints prompt summary to logs
+- `generate_git_patch.sh` - Generates git patches
+- `create_cache_memory_dir.sh` - Creates cache-memory directory
+- `create_prompt_first.sh` - Creates prompt directory
+
+All files are copied from the committed `js/` and `sh/` directories which are populated by running `make actions-build` during development.
## Development
-The `js/` directory contains generated JavaScript files created by `make actions-build`. These files are committed to the repository so that workflows using sparse checkout can access them without needing to rebuild.
+The `js/` and `sh/` directories contain generated files created by `make actions-build`. These files are committed to the repository so that workflows using sparse checkout can access them without needing to rebuild.
-To update the JavaScript files after modifying source files in `pkg/workflow/js/`:
+To update the files after modifying source files in `pkg/workflow/js/` or `pkg/workflow/sh/`:
```bash
make actions-build
-git add actions/setup/js/
-git commit -m "Update action JavaScript files"
+git add actions/setup/js/ actions/setup/sh/
+git commit -m "Update action files"
```
## Testing Locally
diff --git a/actions/setup/setup.sh b/actions/setup/setup.sh
index 1cd374101f3..8c565c39f31 100755
--- a/actions/setup/setup.sh
+++ b/actions/setup/setup.sh
@@ -72,6 +72,23 @@ for file in "${JS_SOURCE_DIR}"/*.json; do
fi
done
+# Copy shell scripts from sh/ directory with executable permissions
+SH_SOURCE_DIR="${SCRIPT_DIR}/sh"
+if [ -d "${SH_SOURCE_DIR}" ]; then
+ echo "::debug::Found shell scripts directory: ${SH_SOURCE_DIR}"
+ for file in "${SH_SOURCE_DIR}"/*.sh; do
+ if [ -f "$file" ]; then
+ filename=$(basename "$file")
+ cp "$file" "${DESTINATION}/${filename}"
+ chmod +x "${DESTINATION}/${filename}"
+ echo "::notice::Copied shell script: ${filename}"
+ FILE_COUNT=$((FILE_COUNT + 1))
+ fi
+ done
+else
+ echo "::debug::No shell scripts directory found at ${SH_SOURCE_DIR}"
+fi
+
echo "::notice::Successfully copied ${FILE_COUNT} files to ${DESTINATION}"
# Set output
diff --git a/actions/setup/sh/create_cache_memory_dir.sh b/actions/setup/sh/create_cache_memory_dir.sh
new file mode 100755
index 00000000000..065573646b6
--- /dev/null
+++ b/actions/setup/sh/create_cache_memory_dir.sh
@@ -0,0 +1,4 @@
+mkdir -p /tmp/gh-aw/cache-memory
+echo "Cache memory directory created at /tmp/gh-aw/cache-memory"
+echo "This folder provides persistent file storage across workflow runs"
+echo "LLMs and agentic tools can freely read and write files in this directory"
diff --git a/actions/setup/sh/create_gh_aw_tmp_dir.sh b/actions/setup/sh/create_gh_aw_tmp_dir.sh
new file mode 100755
index 00000000000..9220e089ddd
--- /dev/null
+++ b/actions/setup/sh/create_gh_aw_tmp_dir.sh
@@ -0,0 +1,3 @@
+mkdir -p /tmp/gh-aw/agent
+mkdir -p /tmp/gh-aw/sandbox/agent/logs
+echo "Created /tmp/gh-aw/agent directory for agentic workflow temporary files"
diff --git a/actions/setup/sh/create_prompt_first.sh b/actions/setup/sh/create_prompt_first.sh
new file mode 100755
index 00000000000..6d2adcf941d
--- /dev/null
+++ b/actions/setup/sh/create_prompt_first.sh
@@ -0,0 +1,2 @@
+PROMPT_DIR="$(dirname "$GH_AW_PROMPT")"
+mkdir -p "$PROMPT_DIR"
diff --git a/actions/setup/sh/generate_git_patch.sh b/actions/setup/sh/generate_git_patch.sh
new file mode 100755
index 00000000000..ac064297778
--- /dev/null
+++ b/actions/setup/sh/generate_git_patch.sh
@@ -0,0 +1,217 @@
+# Diagnostic logging: Show environment information
+echo "=== Diagnostic: Environment Information ==="
+echo "GITHUB_SHA: ${GITHUB_SHA}"
+echo "DEFAULT_BRANCH: ${DEFAULT_BRANCH}"
+echo "Current HEAD: $(git rev-parse HEAD 2>/dev/null || echo 'unknown')"
+echo "Current branch: $(git branch --show-current 2>/dev/null || echo 'detached HEAD')"
+
+# Diagnostic logging: Show recent commits before patch generation
+echo ""
+echo "=== Diagnostic: Recent commits (last 10) ==="
+git log --oneline -10 || echo "Failed to show git log"
+
+# Check current git status
+echo ""
+echo "=== Diagnostic: Current git status ==="
+git status
+
+# Extract branch name from JSONL output
+BRANCH_NAME=""
+if [ -f "$GH_AW_SAFE_OUTPUTS" ]; then
+ echo ""
+ echo "Checking for branch name in JSONL output..."
+ echo "JSONL file path: $GH_AW_SAFE_OUTPUTS"
+ while IFS= read -r line; do
+ if [ -n "$line" ]; then
+ # Extract branch from create-pull-request line using simple grep and sed
+ # Note: types use underscores (normalized by safe-outputs MCP server)
+ if echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"create_pull_request"'; then
+ echo "Found create_pull_request line: $line"
+ # Extract branch value using sed
+ BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')"
+ if [ -n "$BRANCH_NAME" ]; then
+ echo "Extracted branch name from create_pull_request: $BRANCH_NAME"
+ break
+ fi
+ # Extract branch from push_to_pull_request_branch line using simple grep and sed
+ # Note: types use underscores (normalized by safe-outputs MCP server)
+ elif echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"push_to_pull_request_branch"'; then
+ echo "Found push_to_pull_request_branch line: $line"
+ # Extract branch value using sed
+ BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')"
+ if [ -n "$BRANCH_NAME" ]; then
+ echo "Extracted branch name from push_to_pull_request_branch: $BRANCH_NAME"
+ break
+ fi
+ fi
+ fi
+ done < "$GH_AW_SAFE_OUTPUTS"
+else
+ echo ""
+ echo "GH_AW_SAFE_OUTPUTS file not found at: $GH_AW_SAFE_OUTPUTS"
+fi
+
+# If no branch found in JSONL, log it but don't give up yet
+if [ -z "$BRANCH_NAME" ]; then
+ echo ""
+ echo "No branch name found in JSONL output"
+ echo "Will check for commits made to current HEAD instead"
+fi
+
+# Strategy 1: If we have a branch name, check if that branch exists and get its diff
+PATCH_GENERATED=false
+if [ -n "$BRANCH_NAME" ]; then
+ echo ""
+ echo "=== Strategy 1: Using named branch from JSONL ==="
+ echo "Looking for branch: $BRANCH_NAME"
+ # Check if the branch exists
+ if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then
+ echo "Branch $BRANCH_NAME exists, generating patch from branch changes"
+
+ # Check if origin/$BRANCH_NAME exists to use as base
+ if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then
+ echo "Using origin/$BRANCH_NAME as base for patch generation"
+ BASE_REF="origin/$BRANCH_NAME"
+ else
+ echo "origin/$BRANCH_NAME does not exist, using merge-base with default branch"
+ # Use the default branch name from environment variable
+ echo "Default branch: $DEFAULT_BRANCH"
+ # Fetch the default branch to ensure it's available locally
+ git fetch origin "$DEFAULT_BRANCH"
+ # Find merge base between default branch and current branch
+ BASE_REF="$(git merge-base "origin/$DEFAULT_BRANCH" "$BRANCH_NAME")"
+ echo "Using merge-base as base: $BASE_REF"
+ fi
+
+ # Diagnostic logging: Show diff stats before generating patch
+ echo ""
+ echo "=== Diagnostic: Diff stats for patch generation ==="
+ echo "Command: git diff --stat $BASE_REF..$BRANCH_NAME"
+ git diff --stat "$BASE_REF".."$BRANCH_NAME" || echo "Failed to show diff stats"
+
+ # Diagnostic logging: Count commits to be included
+ echo ""
+ echo "=== Diagnostic: Commits to be included in patch ==="
+ COMMIT_COUNT="$(git rev-list --count "$BASE_REF".."$BRANCH_NAME" 2>/dev/null || echo "0")"
+ echo "Number of commits: $COMMIT_COUNT"
+ if [ "$COMMIT_COUNT" -gt 0 ]; then
+ echo "Commit SHAs:"
+ git log --oneline "$BASE_REF".."$BRANCH_NAME" || echo "Failed to list commits"
+ fi
+
+ # Diagnostic logging: Show the exact command being used
+ echo ""
+ echo "=== Diagnostic: Generating patch ==="
+ echo "Command: git format-patch $BASE_REF..$BRANCH_NAME --stdout > /tmp/gh-aw/aw.patch"
+
+ # Generate patch from the determined base to the branch
+ git format-patch "$BASE_REF".."$BRANCH_NAME" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from branch" > /tmp/gh-aw/aw.patch
+ echo "Patch file created from branch: $BRANCH_NAME (base: $BASE_REF)"
+ PATCH_GENERATED=true
+ else
+ echo "Branch $BRANCH_NAME does not exist locally"
+ fi
+fi
+
+# Strategy 2: Check if commits were made to current HEAD since checkout
+if [ "$PATCH_GENERATED" = false ]; then
+ echo ""
+ echo "=== Strategy 2: Checking for commits on current HEAD ==="
+
+ # Get current HEAD SHA
+ CURRENT_HEAD="$(git rev-parse HEAD 2>/dev/null || echo '')"
+ echo "Current HEAD: $CURRENT_HEAD"
+ echo "Checkout SHA (GITHUB_SHA): $GITHUB_SHA"
+
+ if [ -z "$CURRENT_HEAD" ]; then
+ echo "ERROR: Could not determine current HEAD SHA"
+ elif [ -z "$GITHUB_SHA" ]; then
+ echo "ERROR: GITHUB_SHA environment variable is not set"
+ elif [ "$CURRENT_HEAD" = "$GITHUB_SHA" ]; then
+ echo "No commits have been made since checkout (HEAD == GITHUB_SHA)"
+ echo "No patch will be generated"
+ else
+ echo "HEAD has moved since checkout - checking if commits were added"
+
+ # Check if GITHUB_SHA is an ancestor of current HEAD
+ if git merge-base --is-ancestor "$GITHUB_SHA" HEAD 2>/dev/null; then
+ echo "GITHUB_SHA is an ancestor of HEAD - commits were added"
+
+ # Count commits between GITHUB_SHA and HEAD
+ COMMIT_COUNT="$(git rev-list --count "${GITHUB_SHA}..HEAD" 2>/dev/null || echo "0")"
+ echo ""
+ echo "=== Diagnostic: Commits added since checkout ==="
+ echo "Number of commits: $COMMIT_COUNT"
+
+ if [ "$COMMIT_COUNT" -gt 0 ]; then
+ echo "Commit SHAs:"
+ git log --oneline "${GITHUB_SHA}..HEAD" || echo "Failed to list commits"
+
+ # Show diff stats
+ echo ""
+ echo "=== Diagnostic: Diff stats for patch generation ==="
+ echo "Command: git diff --stat ${GITHUB_SHA}..HEAD"
+ git diff --stat "${GITHUB_SHA}..HEAD" || echo "Failed to show diff stats"
+
+ # Generate patch from GITHUB_SHA to HEAD
+ echo ""
+ echo "=== Diagnostic: Generating patch ==="
+ echo "Command: git format-patch ${GITHUB_SHA}..HEAD --stdout > /tmp/gh-aw/aw.patch"
+ git format-patch "${GITHUB_SHA}..HEAD" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from HEAD" > /tmp/gh-aw/aw.patch
+ echo "Patch file created from commits on HEAD (base: $GITHUB_SHA)"
+ PATCH_GENERATED=true
+ else
+ echo "No commits found between GITHUB_SHA and HEAD"
+ fi
+ else
+ echo "GITHUB_SHA is not an ancestor of HEAD - repository state has diverged"
+ echo "This may indicate a rebase or other history rewriting operation"
+ echo "Will not generate patch due to ambiguous history"
+ fi
+ fi
+fi
+
+# Final status
+echo ""
+if [ "$PATCH_GENERATED" = true ]; then
+ echo "=== Patch generation completed successfully ==="
+else
+ echo "=== No patch generated ==="
+ echo "Reason: No commits found via branch name or HEAD analysis"
+fi
+
+# Show patch info if it exists
+if [ -f /tmp/gh-aw/aw.patch ]; then
+ echo ""
+ echo "=== Diagnostic: Patch file information ==="
+ ls -lh /tmp/gh-aw/aw.patch
+
+ # Get patch file size in KB
+ PATCH_SIZE="$(du -k /tmp/gh-aw/aw.patch | cut -f1)"
+ echo "Patch file size: ${PATCH_SIZE} KB"
+
+ # Count lines in patch
+ PATCH_LINES="$(wc -l < /tmp/gh-aw/aw.patch)"
+ echo "Patch file lines: $PATCH_LINES"
+
+ # Extract and count commits from patch file (each commit starts with "From ")
+ PATCH_COMMITS="$(grep -c "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch 2>/dev/null || echo "0")"
+ echo "Commits included in patch: $PATCH_COMMITS"
+
+ # List commit SHAs in the patch
+ if [ "$PATCH_COMMITS" -gt 0 ]; then
+ echo "Commit SHAs in patch:"
+ grep "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch | sed 's/^From \([0-9a-f]\{40\}\).*/ \1/' || echo "Failed to extract commit SHAs"
+ fi
+
+ # Show the first 50 lines of the patch for review
+ {
+ echo '## Git Patch'
+ echo ''
+ echo '```diff'
+ head -500 /tmp/gh-aw/aw.patch || echo "Could not display patch contents"
+ echo '...'
+ echo '```'
+ echo ''
+ } >> "$GITHUB_STEP_SUMMARY"
+fi
diff --git a/actions/setup/sh/print_prompt_summary.sh b/actions/setup/sh/print_prompt_summary.sh
new file mode 100755
index 00000000000..ac06687f4c8
--- /dev/null
+++ b/actions/setup/sh/print_prompt_summary.sh
@@ -0,0 +1,15 @@
+# Print prompt to workflow logs (equivalent to core.info)
+echo "Generated Prompt:"
+cat "$GH_AW_PROMPT"
+
+# Print prompt to step summary
+{
+ echo ""
+ echo "Generated Prompt
"
+ echo ""
+ echo '``````markdown'
+ cat "$GH_AW_PROMPT"
+ echo '``````'
+ echo ""
+ echo " "
+} >> "$GITHUB_STEP_SUMMARY"
diff --git a/actions/setup/sh/start_safe_inputs_server.sh b/actions/setup/sh/start_safe_inputs_server.sh
new file mode 100755
index 00000000000..74496d1fc5e
--- /dev/null
+++ b/actions/setup/sh/start_safe_inputs_server.sh
@@ -0,0 +1,73 @@
+cd /tmp/gh-aw/safe-inputs
+
+# Verify required files exist
+echo "Verifying safe-inputs setup..."
+if [ ! -f mcp-server.cjs ]; then
+ echo "ERROR: mcp-server.cjs not found in /tmp/gh-aw/safe-inputs"
+ ls -la /tmp/gh-aw/safe-inputs/
+ exit 1
+fi
+if [ ! -f tools.json ]; then
+ echo "ERROR: tools.json not found in /tmp/gh-aw/safe-inputs"
+ ls -la /tmp/gh-aw/safe-inputs/
+ exit 1
+fi
+echo "Configuration files verified"
+
+# Log environment configuration
+echo "Server configuration:"
+echo " Port: $GH_AW_SAFE_INPUTS_PORT"
+echo " API Key: ${GH_AW_SAFE_INPUTS_API_KEY:0:8}..."
+echo " Working directory: $(pwd)"
+
+# Ensure logs directory exists
+mkdir -p /tmp/gh-aw/safe-inputs/logs
+
+# Create initial server.log file for artifact upload
+{
+ echo "Safe Inputs MCP Server Log"
+ echo "Start time: $(date)"
+ echo "==========================================="
+ echo ""
+} > /tmp/gh-aw/safe-inputs/logs/server.log
+
+# Start the HTTP server in the background
+echo "Starting safe-inputs MCP HTTP server..."
+node mcp-server.cjs >> /tmp/gh-aw/safe-inputs/logs/server.log 2>&1 &
+SERVER_PID=$!
+echo "Started safe-inputs MCP server with PID $SERVER_PID"
+
+# Wait for server to be ready (max 10 seconds)
+echo "Waiting for server to become ready..."
+for i in {1..10}; do
+ # Check if process is still running
+ if ! kill -0 $SERVER_PID 2>/dev/null; then
+ echo "ERROR: Server process $SERVER_PID has died"
+ echo "Server log contents:"
+ cat /tmp/gh-aw/safe-inputs/logs/server.log
+ exit 1
+ fi
+
+ # Check if server is responding
+ if curl -s -f "http://localhost:$GH_AW_SAFE_INPUTS_PORT/health" > /dev/null 2>&1; then
+ echo "Safe Inputs MCP server is ready (attempt $i/10)"
+ break
+ fi
+
+ if [ "$i" -eq 10 ]; then
+ echo "ERROR: Safe Inputs MCP server failed to start after 10 seconds"
+ echo "Process status: $(pgrep -f 'mcp-server.cjs' || echo 'not running')"
+ echo "Server log contents:"
+ cat /tmp/gh-aw/safe-inputs/logs/server.log
+ echo "Checking port availability:"
+ netstat -tuln | grep "$GH_AW_SAFE_INPUTS_PORT" || echo "Port $GH_AW_SAFE_INPUTS_PORT not listening"
+ exit 1
+ fi
+
+ echo "Waiting for server... (attempt $i/10)"
+ sleep 1
+done
+
+# Output the configuration for the MCP client
+echo "port=$GH_AW_SAFE_INPUTS_PORT" >> "$GITHUB_OUTPUT"
+echo "api_key=$GH_AW_SAFE_INPUTS_API_KEY" >> "$GITHUB_OUTPUT"
diff --git a/pkg/cli/actions_build_command.go b/pkg/cli/actions_build_command.go
index 29d45d9f0ee..f8f19969b15 100644
--- a/pkg/cli/actions_build_command.go
+++ b/pkg/cli/actions_build_command.go
@@ -129,7 +129,26 @@ func ActionsCleanCommand() error {
}
}
- // Note: setup uses setup.sh as template, so we don't clean it
+ // Clean js/ and sh/ directories for setup action
+ if actionName == "setup" {
+ jsDir := filepath.Join(actionsDir, actionName, "js")
+ if _, err := os.Stat(jsDir); err == nil {
+ if err := os.RemoveAll(jsDir); err != nil {
+ return fmt.Errorf("failed to remove %s: %w", jsDir, err)
+ }
+ fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" ✓ Removed %s/js/", actionName)))
+ cleanedCount++
+ }
+
+ shDir := filepath.Join(actionsDir, actionName, "sh")
+ if _, err := os.Stat(shDir); err == nil {
+ if err := os.RemoveAll(shDir); err != nil {
+ return fmt.Errorf("failed to remove %s: %w", shDir, err)
+ }
+ fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" ✓ Removed %s/sh/", actionName)))
+ cleanedCount++
+ }
+ }
}
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("✨ Cleanup complete (%d files removed)", cleanedCount)))
@@ -324,13 +343,15 @@ func buildSetupSafeOutputsAction(actionsDir, actionName string) error {
}
// buildSetupAction builds the setup action by copying JavaScript files to js/ directory
+// and shell scripts to sh/ directory
func buildSetupAction(actionsDir, actionName string) error {
actionPath := filepath.Join(actionsDir, actionName)
jsDir := filepath.Join(actionPath, "js")
+ shDir := filepath.Join(actionPath, "sh")
// Get dependencies for this action
dependencies := getActionDependencies(actionName)
- fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" ✓ Found %d dependencies", len(dependencies))))
+ fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" ✓ Found %d JavaScript dependencies", len(dependencies))))
// Get all JavaScript sources
sources := workflow.GetJavaScriptSources()
@@ -357,6 +378,29 @@ func buildSetupAction(actionsDir, actionName string) error {
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" ✓ Copied %d files to js/", copiedCount)))
+ // Get bundled shell scripts
+ shellScripts := workflow.GetBundledShellScripts()
+ fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" ✓ Found %d shell scripts", len(shellScripts))))
+
+ // Create sh directory if it doesn't exist
+ if err := os.MkdirAll(shDir, 0755); err != nil {
+ return fmt.Errorf("failed to create sh directory: %w", err)
+ }
+
+ // Copy each shell script to the sh directory
+ shCopiedCount := 0
+ for filename, content := range shellScripts {
+ destPath := filepath.Join(shDir, filename)
+ // Shell scripts should be executable (0755)
+ if err := os.WriteFile(destPath, []byte(content), 0755); err != nil {
+ return fmt.Errorf("failed to write %s: %w", filename, err)
+ }
+ fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" - %s", filename)))
+ shCopiedCount++
+ }
+
+ fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" ✓ Copied %d shell scripts to sh/", shCopiedCount)))
+
return nil
}
diff --git a/pkg/workflow/cache.go b/pkg/workflow/cache.go
index fc03c038edf..536023c2eaa 100644
--- a/pkg/workflow/cache.go
+++ b/pkg/workflow/cache.go
@@ -337,8 +337,7 @@ func generateCacheMemorySteps(builder *strings.Builder, data *WorkflowData) {
if useBackwardCompatiblePaths {
// For single default cache, use the original directory for backward compatibility
builder.WriteString(" - name: Create cache-memory directory\n")
- builder.WriteString(" run: |\n")
- WriteShellScriptToYAML(builder, createCacheMemoryDirScript, " ")
+ builder.WriteString(" run: bash /tmp/gh-aw/actions/create_cache_memory_dir.sh\n")
} else {
fmt.Fprintf(builder, " - name: Create cache-memory directory (%s)\n", cache.ID)
builder.WriteString(" run: |\n")
diff --git a/pkg/workflow/compiler_yaml.go b/pkg/workflow/compiler_yaml.go
index 6d03efc1701..bc2604f016c 100644
--- a/pkg/workflow/compiler_yaml.go
+++ b/pkg/workflow/compiler_yaml.go
@@ -208,8 +208,7 @@ func (c *Compiler) generatePrompt(yaml *strings.Builder, data *WorkflowData) {
fmt.Fprintf(yaml, " %s: ${{ %s }}\n", mapping.EnvVar, mapping.Content)
}
- yaml.WriteString(" run: |\n")
- WriteShellScriptToYAML(yaml, createPromptFirstScript, " ")
+ yaml.WriteString(" run: bash /tmp/gh-aw/actions/create_prompt_first.sh\n")
if len(chunks) > 0 {
// Write template with placeholders directly to target file
@@ -309,8 +308,7 @@ func (c *Compiler) generatePrompt(yaml *strings.Builder, data *WorkflowData) {
yaml.WriteString(" - name: Print prompt\n")
yaml.WriteString(" env:\n")
yaml.WriteString(" GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt\n")
- yaml.WriteString(" run: |\n")
- WriteShellScriptToYAML(yaml, printPromptSummaryScript, " ")
+ yaml.WriteString(" run: bash /tmp/gh-aw/actions/print_prompt_summary.sh\n")
}
func (c *Compiler) generatePostSteps(yaml *strings.Builder, data *WorkflowData) {
diff --git a/pkg/workflow/compiler_yaml_main_job.go b/pkg/workflow/compiler_yaml_main_job.go
index 99a577550ef..e37d9ec64c1 100644
--- a/pkg/workflow/compiler_yaml_main_job.go
+++ b/pkg/workflow/compiler_yaml_main_job.go
@@ -90,8 +90,7 @@ func (c *Compiler) generateMainJobSteps(yaml *strings.Builder, data *WorkflowDat
// Create /tmp/gh-aw/ base directory for all temporary files
// This must be created before custom steps so they can use the temp directory
yaml.WriteString(" - name: Create gh-aw temp directory\n")
- yaml.WriteString(" run: |\n")
- WriteShellScriptToYAML(yaml, createGhAwTmpDirScript, " ")
+ yaml.WriteString(" run: bash /tmp/gh-aw/actions/create_gh_aw_tmp_dir.sh\n")
// Add custom steps if present
if data.CustomSteps != "" {
diff --git a/pkg/workflow/mcp_servers.go b/pkg/workflow/mcp_servers.go
index c72d0a0e852..a504f37bdc7 100644
--- a/pkg/workflow/mcp_servers.go
+++ b/pkg/workflow/mcp_servers.go
@@ -537,8 +537,8 @@ func (c *Compiler) generateMCPSetup(yaml *strings.Builder, tools map[string]any,
}
yaml.WriteString(" \n")
- // Use the embedded shell script to start the server
- WriteShellScriptToYAML(yaml, startSafeInputsServerScript, " ")
+ // Call the bundled shell script to start the server
+ yaml.WriteString(" bash /tmp/gh-aw/actions/start_safe_inputs_server.sh\n")
yaml.WriteString(" \n")
}
diff --git a/pkg/workflow/sh.go b/pkg/workflow/sh.go
index 9b5c22d7771..05c1c91794f 100644
--- a/pkg/workflow/sh.go
+++ b/pkg/workflow/sh.go
@@ -46,6 +46,20 @@ var playwrightPromptText string
//go:embed prompts/edit_tool_prompt.md
var editToolPromptText string
+// GetBundledShellScripts returns a map of shell scripts that should be bundled with the setup action.
+// These are scripts that do NOT use GitHub Actions templating (like ${{ }} expressions).
+// Scripts with templating must remain embedded inline in the workflow YAML.
+func GetBundledShellScripts() map[string]string {
+ return map[string]string{
+ "create_gh_aw_tmp_dir.sh": createGhAwTmpDirScript,
+ "start_safe_inputs_server.sh": startSafeInputsServerScript,
+ "print_prompt_summary.sh": printPromptSummaryScript,
+ "generate_git_patch.sh": generateGitPatchScript,
+ "create_cache_memory_dir.sh": createCacheMemoryDirScript,
+ "create_prompt_first.sh": createPromptFirstScript,
+ }
+}
+
// WriteShellScriptToYAML writes a shell script with proper indentation to a strings.Builder
func WriteShellScriptToYAML(yaml *strings.Builder, script string, indent string) {
scriptLines := strings.Split(script, "\n")