From 20e74096e61f54acf3c76cf261f2103a34224001 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 21:58:42 +0000 Subject: [PATCH 1/5] Initial plan From 855b4de9aa0aad8b4849a87b9484e1d07f51d91c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 22:05:14 +0000 Subject: [PATCH 2/5] Initial plan: Bundle shell scripts in setup action Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/templates/github-agentic-workflows.md | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/pkg/cli/templates/github-agentic-workflows.md b/pkg/cli/templates/github-agentic-workflows.md index f04794f4a95..30ddce78aeb 100644 --- a/pkg/cli/templates/github-agentic-workflows.md +++ b/pkg/cli/templates/github-agentic-workflows.md @@ -454,6 +454,19 @@ The YAML frontmatter supports these fields: if-no-changes: "warn" # Optional: "warn" (default), "error", or "ignore" ``` Not supported for cross-repository operations. + - `update-discussion:` - Update discussion title, body, or labels + ```yaml + safe-outputs: + update-discussion: + title: true # Optional: enable title updates + body: true # Optional: enable body updates + labels: true # Optional: enable label updates + allowed-labels: [status, type] # Optional: restrict to specific labels + max: 1 # Optional: max updates (default: 1) + target: "*" # Optional: "triggering" (default), "*", or number + target-repo: "owner/repo" # Optional: cross-repository + ``` + When using `safe-outputs.update-discussion`, the main job does **not** need `discussions: write` permission since updates are handled by a separate job with appropriate permissions. - `update-release:` - Update GitHub release descriptions ```yaml safe-outputs: @@ -463,6 +476,17 @@ The YAML frontmatter supports these fields: github-token: ${{ secrets.CUSTOM_TOKEN }} # Optional: custom token ``` Operation types: `replace`, `append`, `prepend`. + - `upload-asset:` - Publish files to orphaned git branch + ```yaml + safe-outputs: + upload-asset: + branch: "assets/${{ github.workflow }}" # Optional: branch name + max-size: 10240 # Optional: max file size in KB (default: 10MB) + allowed-exts: [.png, .jpg, .pdf] # Optional: allowed file extensions + max: 10 # Optional: max assets (default: 10) + target-repo: "owner/repo" # Optional: cross-repository + ``` + Publishes workflow artifacts to an orphaned git branch for persistent storage. Default allowed extensions include common non-executable types. Maximum file size is 50MB (51200 KB). - `create-code-scanning-alert:` - Generate SARIF security advisories ```yaml safe-outputs: @@ -486,6 +510,28 @@ The YAML frontmatter supports these fields: target-repo: "owner/repo" # Optional: cross-repository ``` Requires PAT with elevated permissions as `GH_AW_AGENT_TOKEN`. + - `assign-to-user:` - Assign users to issues or pull requests + ```yaml + safe-outputs: + assign-to-user: + assignees: [user1, user2] # Optional: restrict to specific users + max: 3 # Optional: max assignments (default: 3) + target: "*" # Optional: "triggering" (default), "*", or number + target-repo: "owner/repo" # Optional: cross-repository + ``` + When using `safe-outputs.assign-to-user`, the main job does **not** need `issues: write` or `pull-requests: write` permission since user assignment is handled by a separate job with appropriate permissions. + - `hide-comment:` - Hide comments on issues, PRs, or discussions + ```yaml + safe-outputs: + hide-comment: + max: 5 # Optional: max comments to hide (default: 5) + allowed-reasons: # Optional: restrict hide reasons + - spam + - outdated + - resolved + target-repo: "owner/repo" # Optional: cross-repository + ``` + Allowed reasons: `spam`, `abuse`, `off_topic`, `outdated`, `resolved`. When using `safe-outputs.hide-comment`, the main job does **not** need write permissions since comment hiding is handled by a separate job. - `noop:` - Log completion message for transparency (auto-enabled) ```yaml safe-outputs: From 48fe3016b2c8486aaac4bee42f3ddd544e10bd3e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 22:09:47 +0000 Subject: [PATCH 3/5] Bundle shell scripts in setup action with executable permissions Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/update_project.cjs | 15 +- actions/setup/setup.sh | 17 ++ actions/setup/sh/create_cache_memory_dir.sh | 4 + actions/setup/sh/create_gh_aw_tmp_dir.sh | 3 + actions/setup/sh/create_prompt_first.sh | 2 + actions/setup/sh/generate_git_patch.sh | 217 +++++++++++++++++++ actions/setup/sh/print_prompt_summary.sh | 15 ++ actions/setup/sh/start_safe_inputs_server.sh | 73 +++++++ pkg/cli/actions_build_command.go | 48 +++- pkg/workflow/sh.go | 14 ++ 10 files changed, 394 insertions(+), 14 deletions(-) create mode 100755 actions/setup/sh/create_cache_memory_dir.sh create mode 100755 actions/setup/sh/create_gh_aw_tmp_dir.sh create mode 100755 actions/setup/sh/create_prompt_first.sh create mode 100755 actions/setup/sh/generate_git_patch.sh create mode 100755 actions/setup/sh/print_prompt_summary.sh create mode 100755 actions/setup/sh/start_safe_inputs_server.sh diff --git a/actions/setup/js/update_project.cjs b/actions/setup/js/update_project.cjs index 65496f51e3a..21939dd7abb 100644 --- a/actions/setup/js/update_project.cjs +++ b/actions/setup/js/update_project.cjs @@ -128,7 +128,7 @@ async function updateProject(output) { campaignId = output.campaign_id; try { let repoResult; - (core.info(`Looking up project #${projectNumberFromUrl} from URL: ${output.project}`), core.info("[1/5] Fetching repository information...")); + (core.info(`Looking up project #${projectNumberFromUrl} from URL: ${output.project}`), core.info("[1/4] Fetching repository information...")); try { repoResult = await github.graphql( "query($owner: String!, $repo: String!) {\n repository(owner: $owner, name: $repo) {\n id\n owner {\n id\n __typename\n }\n }\n }", @@ -147,7 +147,7 @@ async function updateProject(output) { core.warning(`Could not resolve token identity (viewer.login): ${viewerError.message}`); } let projectId; - core.info(`[2/5] Resolving project from URL (scope=${projectInfo.scope}, login=${projectInfo.ownerLogin}, number=${projectNumberFromUrl})...`); + core.info(`[2/4] Resolving project from URL (scope=${projectInfo.scope}, login=${projectInfo.ownerLogin}, number=${projectNumberFromUrl})...`); let resolvedProjectNumber = projectNumberFromUrl; try { const projectNumberInt = parseInt(projectNumberFromUrl, 10); @@ -157,16 +157,7 @@ async function updateProject(output) { } catch (error) { throw (logGraphQLError(error, "Resolving project from URL"), error); } - core.info("[3/5] Linking project to repository..."); - try { - await github.graphql( - "mutation($projectId: ID!, $repositoryId: ID!) {\n linkProjectV2ToRepository(input: {\n projectId: $projectId,\n repositoryId: $repositoryId\n }) {\n repository {\n id\n }\n }\n }", - { projectId, repositoryId } - ); - } catch (linkError) { - (linkError.message && linkError.message.includes("already linked")) || (logGraphQLError(linkError, "Linking project to repository"), core.warning(`Could not link project: ${linkError.message}`)); - } - (core.info("✓ Project linked to repository"), core.info("[4/5] Processing content (issue/PR/draft) if specified...")); + core.info("[3/4] Processing content (issue/PR/draft) if specified..."); const hasContentNumber = void 0 !== output.content_number && null !== output.content_number, hasIssue = void 0 !== output.issue && null !== output.issue, hasPullRequest = void 0 !== output.pull_request && null !== output.pull_request, 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/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") From c0ea11f753d52ac500cd771a0e4f1ca0939f3010 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 22:20:37 +0000 Subject: [PATCH 4/5] Update workflow compiler to call bundled shell scripts directly Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/artifacts-summary.lock.yml | 25 ++-------------- .github/workflows/audit-workflows.lock.yml | 31 +++----------------- .github/workflows/daily-choice-test.lock.yml | 25 ++-------------- pkg/workflow/cache.go | 3 +- pkg/workflow/compiler_yaml.go | 6 ++-- pkg/workflow/compiler_yaml_main_job.go | 3 +- pkg/workflow/mcp_servers.go | 4 +-- 7 files changed, 16 insertions(+), 81 deletions(-) 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/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") } From 7a7df41e6351b59aeeb84e644ba7f6a5ef47b9d8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 22:32:21 +0000 Subject: [PATCH 5/5] Update setup action documentation for bundled shell scripts Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/issue-classifier.lock.yml | 2 +- .github/workflows/release.lock.yml | 6 ++-- .../workflows/stale-repo-identifier.lock.yml | 2 +- .github/workflows/super-linter.lock.yml | 2 +- actions/setup/README.md | 31 +++++++++++++------ 5 files changed, 28 insertions(+), 15 deletions(-) diff --git a/.github/workflows/issue-classifier.lock.yml b/.github/workflows/issue-classifier.lock.yml index 4f4a69a3e57..3be5ab9acd8 100644 --- a/.github/workflows/issue-classifier.lock.yml +++ b/.github/workflows/issue-classifier.lock.yml @@ -2241,7 +2241,7 @@ jobs: path: /tmp/gh-aw/aw_info.json if-no-files-found: warn - name: Run AI Inference - uses: actions/ai-inference@334892bb203895caaed82ec52d23c1ed9385151e # v1 + uses: actions/ai-inference@334892bb203895caaed82ec52d23c1ed9385151e # v2.0.4 env: GH_AW_MCP_CONFIG: /tmp/gh-aw/mcp-config/mcp-servers.json GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt diff --git a/.github/workflows/release.lock.yml b/.github/workflows/release.lock.yml index 8433f151f17..ce61da11ec3 100644 --- a/.github/workflows/release.lock.yml +++ b/.github/workflows/release.lock.yml @@ -6056,13 +6056,13 @@ jobs: - name: Download Go modules run: go mod download - name: Generate SBOM (SPDX format) - uses: anchore/sbom-action@43a17d6e7add2b5535efe4dcae9952337c479a93 # v0.20.10 + uses: anchore/sbom-action@43a17d6e7add2b5535efe4dcae9952337c479a93 # v0.20.11 with: artifact-name: sbom.spdx.json format: spdx-json output-file: sbom.spdx.json - name: Generate SBOM (CycloneDX format) - uses: anchore/sbom-action@43a17d6e7add2b5535efe4dcae9952337c479a93 # v0.20.10 + uses: anchore/sbom-action@43a17d6e7add2b5535efe4dcae9952337c479a93 # v0.20.11 with: artifact-name: sbom.cdx.json format: cyclonedx-json @@ -6261,7 +6261,7 @@ jobs: fetch-depth: 0 persist-credentials: false - name: Release with gh-extension-precompile - uses: cli/gh-extension-precompile@9e2237c30f869ad3bcaed6a4be2cd43564dd421b # v2 + uses: cli/gh-extension-precompile@9e2237c30f869ad3bcaed6a4be2cd43564dd421b # v2.1.0 with: build_script_override: scripts/build-release.sh go_version_file: go.mod diff --git a/.github/workflows/stale-repo-identifier.lock.yml b/.github/workflows/stale-repo-identifier.lock.yml index a8cd91dcd02..21caadf4297 100644 --- a/.github/workflows/stale-repo-identifier.lock.yml +++ b/.github/workflows/stale-repo-identifier.lock.yml @@ -176,7 +176,7 @@ jobs: ORGANIZATION: ${{ env.ORGANIZATION }} id: stale-repos name: Run stale_repos tool - uses: github/stale-repos@a21e55567b83cf3c3f3f9085d3038dc6cee02598 # v3 + uses: github/stale-repos@a21e55567b83cf3c3f3f9085d3038dc6cee02598 # v3.0.2 - env: INACTIVE_REPOS: ${{ steps.stale-repos.outputs.inactiveRepos }} name: Save stale repos output diff --git a/.github/workflows/super-linter.lock.yml b/.github/workflows/super-linter.lock.yml index b15660af2fb..4c2fde522cd 100644 --- a/.github/workflows/super-linter.lock.yml +++ b/.github/workflows/super-linter.lock.yml @@ -6189,7 +6189,7 @@ jobs: persist-credentials: false - name: Super-linter id: super-linter - uses: super-linter/super-linter@47984f49b4e87383eed97890fe2dca6063bbd9c3 # v8.2.1 + uses: super-linter/super-linter@47984f49b4e87383eed97890fe2dca6063bbd9c3 # v8.3.1 env: CREATE_LOG_FILE: "true" DEFAULT_BRANCH: main 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