Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
27 changes: 18 additions & 9 deletions actions/setup/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,28 @@

set -e

# Helper: create directories with sudo on macOS where /opt is root-owned
# Helper: create directories, falling back to sudo when /opt (or similar) is root-owned
create_dir() {
if [[ "$(uname -s)" == "Darwin" ]]; then
sudo mkdir -p "$1"
sudo chown -R "$(whoami)" "$1"
else
mkdir -p "$1"
if mkdir -p "$1" 2>/dev/null; then
return
fi
# Fall back to sudo if regular mkdir fails (e.g., /opt is root-owned on Linux and macOS)
sudo mkdir -p "$1"
sudo chown -R "$(whoami)" "$1"
}

# Get destination from input or use default
DESTINATION="${INPUT_DESTINATION:-/opt/gh-aw/actions}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good improvement - using mkdir -p first and only falling back to sudo when needed. This is more portable across Linux environments where /opt may or may not require elevated permissions.


# Derive GH_AW_HOME from DESTINATION (strip /actions suffix)
# This allows setup.sh to be used with custom base directories
GH_AW_HOME="${DESTINATION%/actions}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice use of parameter expansion \$\{DESTINATION%/actions} to derive GH_AW_HOME. This cleanly strips the /actions suffix and avoids hardcoding /opt/gh-aw everywhere.


# Export GH_AW_HOME to $GITHUB_ENV so all subsequent steps can use it
if [ -n "${GITHUB_ENV}" ]; then
echo "GH_AW_HOME=${GH_AW_HOME}" >> "${GITHUB_ENV}"
fi
Comment on lines 28 to +39

# Get safe-output-custom-tokens flag from input (default: false)
SAFE_OUTPUT_CUSTOM_TOKENS_ENABLED="${INPUT_SAFE_OUTPUT_CUSTOM_TOKENS:-false}"

Expand Down Expand Up @@ -118,7 +127,7 @@ fi
echo "Successfully copied ${FILE_COUNT} files to ${DESTINATION}"

# Copy prompt markdown files to their expected directory
PROMPTS_DEST="/opt/gh-aw/prompts"
PROMPTS_DEST="${GH_AW_HOME}/prompts"
echo "Copying prompt markdown files to ${PROMPTS_DEST}"
create_dir "${PROMPTS_DEST}"

Expand All @@ -140,7 +149,7 @@ else
fi

# Copy mcp-scripts files to their expected directory
MCP_SCRIPTS_DEST="/opt/gh-aw/mcp-scripts"
MCP_SCRIPTS_DEST="${GH_AW_HOME}/mcp-scripts"
echo "Copying mcp-scripts files to ${MCP_SCRIPTS_DEST}"
create_dir "${MCP_SCRIPTS_DEST}"

Expand Down Expand Up @@ -194,7 +203,7 @@ fi
echo "Successfully copied ${MCP_SCRIPTS_COUNT} mcp-scripts files to ${MCP_SCRIPTS_DEST}"

# Copy safe-outputs files to their expected directory
SAFE_OUTPUTS_DEST="/opt/gh-aw/safeoutputs"
SAFE_OUTPUTS_DEST="${GH_AW_HOME}/safeoutputs"
echo "Copying safe-outputs files to ${SAFE_OUTPUTS_DEST}"
create_dir "${SAFE_OUTPUTS_DEST}"

Expand Down
14 changes: 7 additions & 7 deletions actions/setup/sh/start_mcp_gateway.sh
Original file line number Diff line number Diff line change
Expand Up @@ -355,19 +355,19 @@ echo "Detected engine type: $ENGINE_TYPE"
case "$ENGINE_TYPE" in
copilot)
echo "Using Copilot converter..."
bash /opt/gh-aw/actions/convert_gateway_config_copilot.sh
bash ${GH_AW_HOME:-/opt/gh-aw}/actions/convert_gateway_config_copilot.sh
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good use of \$\{GH_AW_HOME:-/opt/gh-aw} default fallback — ensures backward compatibility if GH_AW_HOME isn't set in older environments.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using \$\{GH_AW_HOME:-/opt/gh-aw} with a fallback default is a solid approach — maintains backward compatibility while supporting custom install paths.

;;
codex)
echo "Using Codex converter..."
bash /opt/gh-aw/actions/convert_gateway_config_codex.sh
bash ${GH_AW_HOME:-/opt/gh-aw}/actions/convert_gateway_config_codex.sh
;;
claude)
echo "Using Claude converter..."
bash /opt/gh-aw/actions/convert_gateway_config_claude.sh
bash ${GH_AW_HOME:-/opt/gh-aw}/actions/convert_gateway_config_claude.sh
;;
gemini)
echo "Using Gemini converter..."
bash /opt/gh-aw/actions/convert_gateway_config_gemini.sh
bash ${GH_AW_HOME:-/opt/gh-aw}/actions/convert_gateway_config_gemini.sh
;;
*)
echo "No agent-specific converter found for engine: $ENGINE_TYPE"
Expand All @@ -384,13 +384,13 @@ echo ""
# Check MCP server functionality
echo "Checking MCP server functionality..."
MCP_CHECK_START=$(date +%s%3N)
if [ -f /opt/gh-aw/actions/check_mcp_servers.sh ]; then
if [ -f ${GH_AW_HOME:-/opt/gh-aw}/actions/check_mcp_servers.sh ]; then
echo "Running MCP server checks..."
# Store check diagnostic logs in /tmp/gh-aw/mcp-logs/start-gateway.log for artifact upload
# Use tee to output to both stdout and the log file
# Enable pipefail so the exit code comes from check_mcp_servers.sh, not tee
set -o pipefail
if ! bash /opt/gh-aw/actions/check_mcp_servers.sh \
if ! bash ${GH_AW_HOME:-/opt/gh-aw}/actions/check_mcp_servers.sh \
/tmp/gh-aw/mcp-config/gateway-output.json \
"http://localhost:${MCP_GATEWAY_PORT}" \
"${MCP_GATEWAY_API_KEY}" 2>&1 | tee /tmp/gh-aw/mcp-logs/start-gateway.log; then
Expand All @@ -402,7 +402,7 @@ if [ -f /opt/gh-aw/actions/check_mcp_servers.sh ]; then
set +o pipefail
print_timing $MCP_CHECK_START "MCP server connectivity checks"
else
echo "WARNING: MCP server check script not found at /opt/gh-aw/actions/check_mcp_servers.sh"
echo "WARNING: MCP server check script not found at ${GH_AW_HOME:-/opt/gh-aw}/actions/check_mcp_servers.sh"
echo "Skipping MCP server functionality checks"
fi
echo ""
Expand Down
14 changes: 7 additions & 7 deletions actions/setup/sh/start_mcp_scripts_server.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@

set -e

cd /opt/gh-aw/mcp-scripts || exit 1
cd ${GH_AW_HOME:-/opt/gh-aw}/mcp-scripts || exit 1

# Verify required files exist
echo "Verifying mcp-scripts setup..."

# Check core configuration files
if [ ! -f mcp-server.cjs ]; then
echo "ERROR: mcp-server.cjs not found in /opt/gh-aw/mcp-scripts"
ls -la /opt/gh-aw/mcp-scripts/
echo "ERROR: mcp-server.cjs not found in ${GH_AW_HOME:-/opt/gh-aw}/mcp-scripts"
ls -la ${GH_AW_HOME:-/opt/gh-aw}/mcp-scripts/
exit 1
fi
if [ ! -f tools.json ]; then
echo "ERROR: tools.json not found in /opt/gh-aw/mcp-scripts"
ls -la /opt/gh-aw/mcp-scripts/
echo "ERROR: tools.json not found in ${GH_AW_HOME:-/opt/gh-aw}/mcp-scripts"
ls -la ${GH_AW_HOME:-/opt/gh-aw}/mcp-scripts/
exit 1
fi

Expand Down Expand Up @@ -48,13 +48,13 @@ for dep in "${REQUIRED_DEPS[@]}"; do
done

if [ ${#MISSING_FILES[@]} -gt 0 ]; then
echo "ERROR: Missing required dependency files in /opt/gh-aw/mcp-scripts/"
echo "ERROR: Missing required dependency files in ${GH_AW_HOME:-/opt/gh-aw}/mcp-scripts/"
for file in "${MISSING_FILES[@]}"; do
echo " - $file"
done
echo
echo "Current directory contents:"
ls -la /opt/gh-aw/mcp-scripts/
ls -la ${GH_AW_HOME:-/opt/gh-aw}/mcp-scripts/
echo
echo "These files should have been copied by the Setup Scripts action."
echo "This usually indicates a problem with the actions/setup step."
Expand Down
14 changes: 7 additions & 7 deletions actions/setup/sh/start_safe_outputs_server.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@

set -e

cd /opt/gh-aw/safeoutputs || exit 1
cd ${GH_AW_HOME:-/opt/gh-aw}/safeoutputs || exit 1

# Verify required files exist
echo "Verifying safe-outputs setup..."

# Check core files (mcp-server.cjs and tools.json are required)
if [ ! -f mcp-server.cjs ]; then
echo "ERROR: mcp-server.cjs not found in /opt/gh-aw/safeoutputs"
ls -la /opt/gh-aw/safeoutputs/
echo "ERROR: mcp-server.cjs not found in ${GH_AW_HOME:-/opt/gh-aw}/safeoutputs"
ls -la ${GH_AW_HOME:-/opt/gh-aw}/safeoutputs/
exit 1
fi
if [ ! -f tools.json ]; then
echo "ERROR: tools.json not found in /opt/gh-aw/safeoutputs"
ls -la /opt/gh-aw/safeoutputs/
echo "ERROR: tools.json not found in ${GH_AW_HOME:-/opt/gh-aw}/safeoutputs"
ls -la ${GH_AW_HOME:-/opt/gh-aw}/safeoutputs/
exit 1
fi

Expand Down Expand Up @@ -48,13 +48,13 @@ for dep in "${REQUIRED_DEPS[@]}"; do
done

if [ ${#MISSING_FILES[@]} -gt 0 ]; then
echo "ERROR: Missing required dependency files in /opt/gh-aw/safeoutputs/"
echo "ERROR: Missing required dependency files in ${GH_AW_HOME:-/opt/gh-aw}/safeoutputs/"
for file in "${MISSING_FILES[@]}"; do
echo " - $file"
done
echo
echo "Current directory contents:"
ls -la /opt/gh-aw/safeoutputs/
ls -la ${GH_AW_HOME:-/opt/gh-aw}/safeoutputs/
echo
echo "These files should have been copied by the Setup Scripts action."
echo "This usually indicates a problem with the actions/setup step."
Expand Down
7 changes: 5 additions & 2 deletions pkg/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,9 +435,12 @@ const DefaultAlpineImage = "alpine:latest"
// This image is built during workflow execution and includes the gh-aw binary and dependencies
const DevModeGhAwImage = "localhost/gh-aw:dev"

// GhAwHomeDefault is the default value for GH_AW_HOME when the env var is not set
const GhAwHomeDefault = "/opt/gh-aw"

// DefaultGhAwMount is the mount path for the gh-aw directory in containerized MCP servers
// The gh-aw binary and supporting files are mounted read-only from /opt/gh-aw
const DefaultGhAwMount = "/opt/gh-aw:/opt/gh-aw:ro"
// Uses shell expansion so docker gets the resolved path at runtime
const DefaultGhAwMount = "\\${GH_AW_HOME:-/opt/gh-aw}:\\${GH_AW_HOME:-/opt/gh-aw}:ro"

// DefaultGhBinaryMount is the mount path for the gh CLI binary in containerized MCP servers
// The gh CLI is required for agentic-workflows MCP server to run gh commands
Expand Down
2 changes: 1 addition & 1 deletion pkg/workflow/agentic_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@ func GenerateMultiSecretValidationStep(secretNames []string, engineName, docsURL
stepLines := []string{
stepName,
" id: validate-secret",
" run: /opt/gh-aw/actions/validate_multi_secret.sh " + scriptArgsStr,
" run: " + GhAwHome + "/actions/validate_multi_secret.sh " + scriptArgsStr,
" env:",
}

Expand Down
18 changes: 9 additions & 9 deletions pkg/workflow/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,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: bash /opt/gh-aw/actions/create_cache_memory_dir.sh\n")
builder.WriteString(" run: bash " + GhAwHome + "/actions/create_cache_memory_dir.sh\n")
} else {
fmt.Fprintf(builder, " - name: Create cache-memory directory (%s)\n", cache.ID)
builder.WriteString(" run: |\n")
Expand Down Expand Up @@ -498,9 +498,9 @@ func generateCacheMemoryValidation(builder *strings.Builder, data *WorkflowData)

// Build validation script
var validationScript strings.Builder
validationScript.WriteString(" const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');\n")
validationScript.WriteString(" const { setupGlobals } = require(" + JsRequireGhAw("actions/setup_globals.cjs") + ");\n")
validationScript.WriteString(" setupGlobals(core, github, context, exec, io);\n")
validationScript.WriteString(" const { validateMemoryFiles } = require('/opt/gh-aw/actions/validate_memory_files.cjs');\n")
validationScript.WriteString(" const { validateMemoryFiles } = require(" + JsRequireGhAw("actions/validate_memory_files.cjs") + ");\n")
fmt.Fprintf(&validationScript, " const allowedExtensions = %s;\n", allowedExtsJSON)
fmt.Fprintf(&validationScript, " const result = validateMemoryFiles('%s', 'cache', allowedExtensions);\n", cacheDir)
validationScript.WriteString(" if (!result.valid) {\n")
Expand Down Expand Up @@ -770,9 +770,9 @@ func (c *Compiler) buildUpdateCacheMemoryJob(data *WorkflowData, threatDetection

// Build validation script
var validationScript strings.Builder
validationScript.WriteString(" const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');\n")
validationScript.WriteString(" const { setupGlobals } = require(" + JsRequireGhAw("actions/setup_globals.cjs") + ");\n")
validationScript.WriteString(" setupGlobals(core, github, context, exec, io);\n")
validationScript.WriteString(" const { validateMemoryFiles } = require('/opt/gh-aw/actions/validate_memory_files.cjs');\n")
validationScript.WriteString(" const { validateMemoryFiles } = require(" + JsRequireGhAw("actions/validate_memory_files.cjs") + ");\n")
fmt.Fprintf(&validationScript, " const allowedExtensions = %s;\n", allowedExtsJSON)
fmt.Fprintf(&validationScript, " const result = validateMemoryFiles('%s', 'cache', allowedExtensions);\n", cacheDir)
validationScript.WriteString(" if (!result.valid) {\n")
Expand Down Expand Up @@ -844,11 +844,11 @@ func (c *Compiler) buildUpdateCacheMemoryJob(data *WorkflowData, threatDetection
}

// Set GH_AW_WORKFLOW_ID_SANITIZED so cache keys match those used in the agent job
var jobEnv map[string]string
jobEnv := map[string]string{
"GH_AW_HOME": constants.GhAwHomeDefault,
}
if data.WorkflowID != "" {
jobEnv = map[string]string{
"GH_AW_WORKFLOW_ID_SANITIZED": SanitizeWorkflowIDForCacheKey(data.WorkflowID),
}
jobEnv["GH_AW_WORKFLOW_ID_SANITIZED"] = SanitizeWorkflowIDForCacheKey(data.WorkflowID)
}

job := &Job{
Expand Down
22 changes: 6 additions & 16 deletions pkg/workflow/compiler_main_job.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,24 +174,17 @@ func (c *Compiler) buildMainJob(data *WorkflowData, activationJobCreated bool) (
}
}

// Build job-level environment variables for safe outputs
var env map[string]string
if data.SafeOutputs != nil {
env = make(map[string]string)

// Set GH_AW_SAFE_OUTPUTS to path in /opt (read-only mount for agent container)
// The MCP server writes agent outputs to this file during execution
// This file is in /opt to prevent the agent container from having write access
env["GH_AW_SAFE_OUTPUTS"] = "/opt/gh-aw/safeoutputs/outputs.jsonl"
// Build job-level environment variables
// Always initialize env with GH_AW_HOME so steps don't need the :-fallback syntax
env := map[string]string{
"GH_AW_HOME": constants.GhAwHomeDefault,
}
Comment on lines +177 to +181

if data.SafeOutputs != nil {
// Set GH_AW_MCP_LOG_DIR for safe outputs MCP server logging
// Store in mcp-logs directory so it's included in mcp-logs artifact
env["GH_AW_MCP_LOG_DIR"] = "/tmp/gh-aw/mcp-logs/safeoutputs"

// Set config and tools paths (readonly files in /opt/gh-aw)
env["GH_AW_SAFE_OUTPUTS_CONFIG_PATH"] = "/opt/gh-aw/safeoutputs/config.json"
env["GH_AW_SAFE_OUTPUTS_TOOLS_PATH"] = "/opt/gh-aw/safeoutputs/tools.json"

// Add asset-related environment variables
// These must always be set (even to empty) because awmg v0.0.12+ validates ${VAR} references
if data.SafeOutputs.UploadAssets != nil {
Expand All @@ -214,9 +207,6 @@ func (c *Compiler) buildMainJob(data *WorkflowData, activationJobCreated bool) (
// This contains the workflow ID with all hyphens removed and lowercased
// Used in cache keys to avoid spaces and special characters
if data.WorkflowID != "" {
if env == nil {
env = make(map[string]string)
}
sanitizedID := SanitizeWorkflowIDForCacheKey(data.WorkflowID)
env["GH_AW_WORKFLOW_ID_SANITIZED"] = sanitizedID
}
Expand Down
10 changes: 9 additions & 1 deletion pkg/workflow/compiler_safe_outputs_job.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,12 @@ func (c *Compiler) buildConsolidatedSafeOutputsJob(data *WorkflowData, mainJobNa
if len(c.generateCheckoutActionsFolder(data)) > 0 {
insertIndex += 6 // Checkout step (6 lines: name, uses, with, sparse-checkout header, actions, persist-credentials)
}
insertIndex += 4 // Setup step (4 lines: name, uses, with, destination)
enableCustomTokensForInsert := c.hasCustomTokenSafeOutputs(data.SafeOutputs)
if enableCustomTokensForInsert {
insertIndex += 4 // Setup step with custom tokens (4 lines: name, uses, with, safe-output-custom-tokens)
} else {
insertIndex += 2 // Setup step without custom tokens (2 lines: name, uses)
}
}

// Add artifact download steps count
Expand Down Expand Up @@ -385,6 +390,9 @@ func (c *Compiler) buildConsolidatedSafeOutputsJob(data *WorkflowData, mainJobNa
func (c *Compiler) buildJobLevelSafeOutputEnvVars(data *WorkflowData, workflowID string) map[string]string {
envVars := make(map[string]string)

// Set GH_AW_HOME so steps can use $GH_AW_HOME without the :-fallback syntax
envVars["GH_AW_HOME"] = constants.GhAwHomeDefault

// Set GH_AW_WORKFLOW_ID to the workflow ID (filename without extension)
// This is used for branch naming in create_pull_request and other operations
envVars["GH_AW_WORKFLOW_ID"] = fmt.Sprintf("%q", workflowID)
Expand Down
10 changes: 5 additions & 5 deletions pkg/workflow/compiler_yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -508,13 +508,13 @@ func (c *Compiler) generatePrompt(yaml *strings.Builder, data *WorkflowData, pre
yaml.WriteString(" - name: Validate prompt placeholders\n")
yaml.WriteString(" env:\n")
yaml.WriteString(" GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt\n")
yaml.WriteString(" run: bash /opt/gh-aw/actions/validate_prompt_placeholders.sh\n")
yaml.WriteString(" run: bash " + GhAwHome + "/actions/validate_prompt_placeholders.sh\n")

// Print prompt (merged into prompt generation)
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: bash /opt/gh-aw/actions/print_prompt_summary.sh\n")
yaml.WriteString(" run: bash " + GhAwHome + "/actions/print_prompt_summary.sh\n")
}
func (c *Compiler) generatePostSteps(yaml *strings.Builder, data *WorkflowData) {
if data.PostSteps != "" {
Expand Down Expand Up @@ -665,7 +665,7 @@ func (c *Compiler) generateCreateAwInfo(yaml *strings.Builder, data *WorkflowDat
fmt.Fprintf(yaml, " uses: %s\n", GetActionPin("actions/github-script"))
yaml.WriteString(" with:\n")
yaml.WriteString(" script: |\n")
yaml.WriteString(" const { main } = require('/opt/gh-aw/actions/generate_aw_info.cjs');\n")
yaml.WriteString(" const { main } = require(" + JsRequireGhAw("actions/generate_aw_info.cjs") + ");\n")
yaml.WriteString(" await main(core, context);\n")
}

Expand Down Expand Up @@ -724,9 +724,9 @@ func (c *Compiler) generateOutputCollectionStep(yaml *strings.Builder, data *Wor
yaml.WriteString(" script: |\n")

// Load script from external file using require()
yaml.WriteString(" const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');\n")
yaml.WriteString(" const { setupGlobals } = require(" + JsRequireGhAw("actions/setup_globals.cjs") + ");\n")
yaml.WriteString(" setupGlobals(core, github, context, exec, io);\n")
yaml.WriteString(" const { main } = require('/opt/gh-aw/actions/collect_ndjson_output.cjs');\n")
yaml.WriteString(" const { main } = require(" + JsRequireGhAw("actions/collect_ndjson_output.cjs") + ");\n")
yaml.WriteString(" await main();\n")

}
Expand Down
Loading
Loading