Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 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
5 changes: 5 additions & 0 deletions .changeset/patch-gh-aw-home-path-constants.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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