From e51322185e2d92b13b690d4033a031f5ac71efe5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Jun 2026 00:58:18 +0000 Subject: [PATCH 1/3] Initial plan From fc71886392e3817b536bb2e6afba1d5d7a2d12b4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Jun 2026 01:11:00 +0000 Subject: [PATCH 2/3] Initial plan: extract hardcoded file paths to named constants Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup-cli/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/setup-cli/install.sh b/actions/setup-cli/install.sh index 5635319fd84..c7a5ed2ffed 100755 --- a/actions/setup-cli/install.sh +++ b/actions/setup-cli/install.sh @@ -1,7 +1,7 @@ #!/bin/bash set +o histexpand -# Kept in sync with install-gh-aw.sh — edit that file, then copy to this path. +# Kept in sync with actions/setup-cli/install.sh — edit this file, then copy to that path. # Script to download and install gh-aw binary for the current OS and architecture # Supports: Linux, macOS (Darwin), FreeBSD, Windows (Git Bash/MSYS/Cygwin) From 64a0dd186f106cb0bb550bb50901e580913adeed Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Jun 2026 01:25:59 +0000 Subject: [PATCH 3/3] Extract hardcoded file paths to named constants Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/git.go | 10 +- pkg/cli/includes.go | 2 +- pkg/cli/logs_run_processor.go | 2 +- pkg/cli/mcp_tools_privileged.go | 7 +- pkg/cli/shell_completion.go | 10 +- pkg/cli/trial_repository.go | 2 +- pkg/constants/constants.go | 165 +++++++++++++++++- .../hardcodedfilepath/hardcodedfilepath.go | 6 +- pkg/parser/import_field_extractor.go | 3 +- pkg/parser/include_expander.go | 3 +- pkg/parser/mcp.go | 2 +- pkg/parser/remote_fetch.go | 12 +- pkg/workflow/agentic_engine.go | 2 +- pkg/workflow/antigravity_engine.go | 4 +- pkg/workflow/antigravity_logs.go | 3 +- pkg/workflow/antigravity_mcp.go | 3 +- pkg/workflow/awf_helpers.go | 6 +- pkg/workflow/claude_engine.go | 4 +- pkg/workflow/claude_mcp.go | 3 +- pkg/workflow/codex_engine.go | 8 +- pkg/workflow/codex_mcp.go | 2 +- pkg/workflow/compiler_difc_proxy.go | 6 +- pkg/workflow/compiler_main_job.go | 2 +- pkg/workflow/compiler_safe_outputs_job.go | 4 +- pkg/workflow/compiler_yaml.go | 4 +- pkg/workflow/compiler_yaml_main_job.go | 28 +-- pkg/workflow/copilot_engine.go | 2 +- pkg/workflow/copilot_engine_execution.go | 8 +- pkg/workflow/copilot_logs.go | 3 +- pkg/workflow/crush_engine.go | 2 +- pkg/workflow/crush_mcp.go | 3 +- pkg/workflow/engine_output.go | 3 +- .../frontmatter_extraction_metadata.go | 3 +- pkg/workflow/gemini_engine.go | 4 +- pkg/workflow/gemini_logs.go | 3 +- pkg/workflow/gemini_mcp.go | 3 +- .../mcp_config_playwright_renderer.go | 3 +- pkg/workflow/mcp_environment.go | 2 +- pkg/workflow/mcp_renderer_builtin.go | 4 +- pkg/workflow/opencode_mcp.go | 3 +- pkg/workflow/pi_engine.go | 4 +- pkg/workflow/repo_memory.go | 6 +- pkg/workflow/runtime_import_validation.go | 5 +- pkg/workflow/safe_jobs.go | 4 +- .../safe_outputs_config_generation.go | 3 +- pkg/workflow/safe_outputs_steps.go | 2 +- pkg/workflow/setup_action_paths.go | 8 + pkg/workflow/step_order_validation.go | 4 +- pkg/workflow/threat_detection.go | 2 +- 49 files changed, 288 insertions(+), 99 deletions(-) diff --git a/pkg/cli/git.go b/pkg/cli/git.go index 687769e499c..7493dfabc12 100644 --- a/pkg/cli/git.go +++ b/pkg/cli/git.go @@ -313,14 +313,14 @@ func getRepositorySlugFromRemoteForPath(path string) string { func stageWorkflowChanges() { // Find git root and add .github/workflows relative to it if gitRoot, err := gitutil.FindGitRoot(); err == nil { - workflowsPath := filepath.Join(gitRoot, ".github/workflows/") + workflowsPath := filepath.Join(gitRoot, constants.WorkflowsDirSlash) _ = exec.Command("git", "-C", gitRoot, "add", workflowsPath).Run() // Also stage .gitattributes if it was modified _ = stageGitAttributesIfChanged() } else { // Fallback to relative path if git root can't be found - _ = exec.Command("git", "add", ".github/workflows/").Run() + _ = exec.Command("git", "add", constants.WorkflowsDirSlash).Run() _ = exec.Command("git", "add", ".gitattributes").Run() } } @@ -335,7 +335,7 @@ func ensureGitAttributes() (bool, error) { } gitAttributesPath := filepath.Join(gitRoot, ".gitattributes") - lockYmlEntry := ".github/workflows/*.lock.yml linguist-generated=true merge=ours" + lockYmlEntry := constants.WorkflowsLockYmlGitAttributesEntry requiredEntries := []string{lockYmlEntry} // Read existing .gitattributes file if it exists @@ -357,7 +357,7 @@ func ensureGitAttributes() (bool, error) { break } // Check for old format entries that need updating - if strings.HasPrefix(trimmedLine, ".github/workflows/*.lock.yml") && required == lockYmlEntry { + if strings.HasPrefix(trimmedLine, constants.WorkflowsLockYmlGlob) && required == lockYmlEntry { gitLog.Print("Updating old .gitattributes entry format") lines[i] = lockYmlEntry found = true @@ -414,7 +414,7 @@ func ensureLogsGitignore() error { // Check if .gitignore already exists if _, err := os.Stat(gitignorePath); err == nil { - gitLog.Print(".github/aw/logs/.gitignore already exists") + gitLog.Print(".github/aw/logs/.gitignore already exists") //nolint:hardcodedfilepath return nil } diff --git a/pkg/cli/includes.go b/pkg/cli/includes.go index 70c72db89d6..1b74ffb5753 100644 --- a/pkg/cli/includes.go +++ b/pkg/cli/includes.go @@ -91,7 +91,7 @@ func FetchIncludeFromSource(includePath string, baseSpec *WorkflowSpec, verbose // If it's a relative path starting with shared/, it's relative to .github/ var fullPath string if strings.HasPrefix(filePath, "shared/") { - fullPath = ".github/" + filePath + fullPath = constants.GithubDir + filePath } else { // Otherwise, resolve relative to the workflow path directory baseDir := getParentDir(baseSpec.WorkflowPath) diff --git a/pkg/cli/logs_run_processor.go b/pkg/cli/logs_run_processor.go index e6e364e3333..f0fe046e24b 100644 --- a/pkg/cli/logs_run_processor.go +++ b/pkg/cli/logs_run_processor.go @@ -531,7 +531,7 @@ func inferWorkflowPathFromDisplayName(displayName string) string { if slug == "" { return "" } - return ".github/workflows/" + slug + ".lock.yml" + return constants.WorkflowsDirSlash + slug + ".lock.yml" } // runHasDifcFilteredItems checks if a run's gateway logs contain any DIFC_FILTERED events. diff --git a/pkg/cli/mcp_tools_privileged.go b/pkg/cli/mcp_tools_privileged.go index 6009af0695d..a94fb6689f9 100644 --- a/pkg/cli/mcp_tools_privileged.go +++ b/pkg/cli/mcp_tools_privileged.go @@ -10,6 +10,7 @@ import ( "strconv" "strings" + "github.com/github/gh-aw/pkg/constants" "github.com/modelcontextprotocol/go-sdk/jsonrpc" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -133,7 +134,7 @@ from where the previous request stopped due to timeout.`, // Build command arguments // Force output directory to /tmp/gh-aw/aw-mcp/logs for MCP server - cmdArgs := []string{"logs", "-o", "/tmp/gh-aw/aw-mcp/logs"} + cmdArgs := []string{"logs", "-o", constants.TmpAwMcpLogsDir} if args.WorkflowName != "" { cmdArgs = append(cmdArgs, args.WorkflowName) } @@ -384,7 +385,7 @@ Multi-run diff returns JSON describing changes between the base and each compari // Pass all run IDs/URLs directly - the audit command handles single vs. diff mode. cmdArgs := []string{"audit"} cmdArgs = append(cmdArgs, runItems...) - cmdArgs = append(cmdArgs, "-o", "/tmp/gh-aw/aw-mcp/logs", "--json") + cmdArgs = append(cmdArgs, "-o", constants.TmpAwMcpLogsDir, "--json") if len(args.Artifacts) > 0 { cmdArgs = append(cmdArgs, "--artifacts", strings.Join(args.Artifacts, ",")) } @@ -525,7 +526,7 @@ Returns JSON describing the differences between the base run and each comparison // Build: gh aw audit diff -o ... --json [--artifacts ...] cmdArgs := []string{"audit", "diff", args.BaseRunID} cmdArgs = append(cmdArgs, args.CompareRunIDs...) - cmdArgs = append(cmdArgs, "-o", "/tmp/gh-aw/aw-mcp/logs", "--json") + cmdArgs = append(cmdArgs, "-o", constants.TmpAwMcpLogsDir, "--json") if len(args.Artifacts) > 0 { cmdArgs = append(cmdArgs, "--artifacts", strings.Join(args.Artifacts, ",")) } diff --git a/pkg/cli/shell_completion.go b/pkg/cli/shell_completion.go index e16059822ac..6c6106bd847 100644 --- a/pkg/cli/shell_completion.go +++ b/pkg/cli/shell_completion.go @@ -145,7 +145,7 @@ func installBashCompletion(verbose bool, cmd *cobra.Command) error { brewPrefix := os.Getenv("HOMEBREW_PREFIX") if brewPrefix == "" { // Try common locations - for _, prefix := range []string{"/opt/homebrew", "/usr/local"} { + for _, prefix := range []string{constants.HomebrewPrefix, constants.UsrLocalPrefix} { if _, err := os.Stat(filepath.Join(prefix, "etc", "bash_completion.d")); err == nil { brewPrefix = prefix break @@ -159,8 +159,8 @@ func installBashCompletion(verbose bool, cmd *cobra.Command) error { } } else { // Linux - if _, err := os.Stat("/etc/bash_completion.d"); err == nil { - completionPath = "/etc/bash_completion.d/gh-aw" + if _, err := os.Stat(constants.BashCompletionDir); err == nil { + completionPath = constants.BashCompletionGhAwPath } else { completionPath = filepath.Join(homeDir, ".bash_completion.d", "gh-aw") } @@ -426,7 +426,7 @@ func uninstallBashCompletion(verbose bool) error { if runtime.GOOS == "darwin" { brewPrefix := os.Getenv("HOMEBREW_PREFIX") if brewPrefix == "" { - for _, prefix := range []string{"/opt/homebrew", "/usr/local"} { + for _, prefix := range []string{constants.HomebrewPrefix, constants.UsrLocalPrefix} { if _, err := os.Stat(filepath.Join(prefix, "etc", "bash_completion.d")); err == nil { possiblePaths = append(possiblePaths, filepath.Join(prefix, "etc", "bash_completion.d", "gh-aw")) } @@ -438,7 +438,7 @@ func uninstallBashCompletion(verbose bool) error { // System-wide installations (Linux) if runtime.GOOS == "linux" { - possiblePaths = append(possiblePaths, "/etc/bash_completion.d/gh-aw") + possiblePaths = append(possiblePaths, constants.BashCompletionGhAwPath) } removed := false diff --git a/pkg/cli/trial_repository.go b/pkg/cli/trial_repository.go index f7d22aae70a..860f0c3aad3 100644 --- a/pkg/cli/trial_repository.go +++ b/pkg/cli/trial_repository.go @@ -312,7 +312,7 @@ func installWorkflowInTrialMode(ctx context.Context, tempDir string, parsedSpec // Compile the workflow with trial modifications config := CompileConfig{ - MarkdownFiles: []string{".github/workflows/" + parsedSpec.WorkflowName + ".md"}, + MarkdownFiles: []string{constants.WorkflowsDirSlash + parsedSpec.WorkflowName + ".md"}, Verbose: opts.Verbose, EngineOverride: opts.EngineOverride, Validate: true, diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go index 78f1fd0282a..2ab38c471eb 100644 --- a/pkg/constants/constants.go +++ b/pkg/constants/constants.go @@ -364,6 +364,169 @@ var SharedWorkflowForbiddenFields = []string{ "tracker-id", // Tracker ID } +// Repository directory path constants +// +// These constants define the conventional repository-relative directory paths +// used by gh-aw for GitHub Actions workflows, agents, and related configuration. + +// GithubDir is the root .github directory prefix (with trailing slash). +// Use this for path prefix comparisons against workspace-relative paths. +const GithubDir = ".github/" + +// WorkflowsDir is the GitHub Actions workflow directory path (without trailing slash). +// This is the canonical location for workflow markdown and compiled lock YAML files. +const WorkflowsDir = ".github/workflows" + +// WorkflowsDirSlash is WorkflowsDir with a trailing slash. +// Use this for path prefix matching (e.g. strings.HasPrefix or strings.Contains). +const WorkflowsDirSlash = WorkflowsDir + "/" + +// AgentsDir is the custom GitHub Copilot agent definitions directory (with trailing slash). +const AgentsDir = ".github/agents/" + +// WorkflowsLockYmlGlob is the glob pattern for compiled workflow lock YAML files. +const WorkflowsLockYmlGlob = WorkflowsDirSlash + "*.lock.yml" + +// WorkflowsLockYmlGitAttributesEntry is the .gitattributes entry that marks lock YAML +// files as generated and sets the merge strategy. +const WorkflowsLockYmlGitAttributesEntry = WorkflowsLockYmlGlob + " linguist-generated=true merge=ours" + +// Temporary runtime directory constants (/tmp/gh-aw tree) +// +// These constants define the /tmp/gh-aw directory layout used by the agent +// and engine harnesses during workflow execution. Paths here are always +// in the /tmp/gh-aw tree regardless of whether the runner uses RUNNER_TEMP. +// See also GhAwRootDir / GhAwRootDirShell for the host-side RUNNER_TEMP paths. + +// TmpGhAwDir is the root /tmp/gh-aw directory (without trailing slash). +const TmpGhAwDir = "/tmp/gh-aw" + +// TmpGhAwDirSlash is TmpGhAwDir with a trailing slash. +// Use for path prefix comparisons (e.g. strings.HasPrefix). +const TmpGhAwDirSlash = TmpGhAwDir + "/" + +// TmpGhAwAgentDir is the agent working directory in the /tmp/gh-aw tree. +const TmpGhAwAgentDir = TmpGhAwDir + "/agent/" + +// AgentStdioLogPath is the path for capturing agent standard I/O log output. +const AgentStdioLogPath = TmpGhAwDir + "/agent-stdio.log" + +// AwPromptsFile is the runtime prompt file path populated by the setup action. +// Engine harnesses read this file to pass the compiled prompt to the AI engine. +const AwPromptsFile = TmpGhAwDir + "/aw-prompts/prompt.txt" + +// TmpMcpConfigDir is the mcp-config directory in the /tmp/gh-aw tree. +// Engines that require a writable MCP config directory (e.g. Codex) use this path. +const TmpMcpConfigDir = TmpGhAwDir + "/mcp-config" + +// TmpMcpServersJsonPath is the MCP servers JSON config file in the /tmp tree. +// Used by engines that resolve the config through the writable /tmp path. +const TmpMcpServersJsonPath = TmpMcpConfigDir + "/mcp-servers.json" + +// TmpMcpConfigLogsDir is the MCP config server log directory. +const TmpMcpConfigLogsDir = TmpMcpConfigDir + "/logs/" + +// TmpMcpLogsDir is the MCP server logs root directory (with trailing slash). +const TmpMcpLogsDir = TmpGhAwDir + "/mcp-logs/" + +// TmpMcpLogsSafeOutputsDir is the safe-outputs MCP server log directory. +const TmpMcpLogsSafeOutputsDir = TmpGhAwDir + "/mcp-logs/safeoutputs" + +// TmpMcpLogsPlaywrightDir is the Playwright MCP server log directory. +const TmpMcpLogsPlaywrightDir = TmpGhAwDir + "/mcp-logs/playwright" + +// TmpMcpLogsMount is the Docker volume mount spec for the MCP logs directory. +const TmpMcpLogsMount = TmpGhAwDir + "/mcp-logs:" + TmpGhAwDir + "/mcp-logs" + +// TmpMcpScriptsLogsDir is the mcp-scripts server log directory (with trailing slash). +const TmpMcpScriptsLogsDir = TmpGhAwDir + "/mcp-scripts/logs/" + +// TmpRepoMemoryDir is the repo-memory data directory (with trailing slash). +const TmpRepoMemoryDir = TmpGhAwDir + "/repo-memory/" + +// TmpCommentMemoryDir is the comment-memory data directory (with trailing slash). +const TmpCommentMemoryDir = TmpGhAwDir + "/comment-memory/" + +// TmpAwBundleGlob is the glob pattern for bundle files produced by the agent. +const TmpAwBundleGlob = TmpGhAwDir + "/aw-*.bundle" + +// TmpAwPatchGlob is the glob pattern for patch files produced by the agent. +const TmpAwPatchGlob = TmpGhAwDir + "/aw-*.patch" + +// TmpGeminiClientErrorGlob is the glob for Gemini client error JSON diagnostic files. +const TmpGeminiClientErrorGlob = TmpGhAwDir + "/gemini-client-error-*.json" + +// TmpAntigravityClientErrorGlob is the glob for Antigravity client error JSON diagnostic files. +const TmpAntigravityClientErrorGlob = TmpGhAwDir + "/antigravity-client-error-*.json" + +// TmpPiAgentDir is the Pi engine agent working directory. +const TmpPiAgentDir = TmpGhAwDir + "/pi-agent-dir" + +// ThreatDetectionLogPath is the threat detection engine log file path. +const ThreatDetectionLogPath = TmpGhAwDir + "/threat-detection/detection.log" + +// TmpProxyLogsDir is the DIFC proxy logs directory (with trailing slash). +const TmpProxyLogsDir = TmpGhAwDir + "/proxy-logs/" + +// TmpProxyTLSDir is the proxy TLS certificates sub-directory (with trailing slash). +const TmpProxyTLSDir = TmpGhAwDir + "/proxy-logs/proxy-tls/" + +// TmpProxyTLSCACert is the proxy TLS CA certificate file path. +const TmpProxyTLSCACert = TmpGhAwDir + "/proxy-logs/proxy-tls/ca.crt" + +// TmpDIFCProxyTLSCACert is the DIFC proxy TLS CA certificate file path. +const TmpDIFCProxyTLSCACert = TmpGhAwDir + "/difc-proxy-tls/ca.crt" + +// TmpAwMcpLogsDir is the aw-mcp server logs directory. +const TmpAwMcpLogsDir = TmpGhAwDir + "/aw-mcp/logs" + +// TmpSandboxAgentLogsDir is the sandbox agent logs directory (with trailing slash). +const TmpSandboxAgentLogsDir = TmpGhAwDir + "/sandbox/agent/logs/" + +// Shell and Actions expression form path constants +// +// These complement GhAwRootDirShell and GhAwRootDir for sub-paths commonly +// referenced in both shell run: blocks and GitHub Actions expression contexts. + +// GhAwRootDirShellSlash is GhAwRootDirShell with a trailing slash. +// Use for path prefix matching in shell expressions (e.g. ${RUNNER_TEMP}/gh-aw/). +const GhAwRootDirShellSlash = GhAwRootDirShell + "/" + +// ShellMcpConfigDir is the mcp-config directory in shell environment variable form. +const ShellMcpConfigDir = GhAwRootDirShell + "/mcp-config" + +// ShellMcpServersJsonPath is the MCP servers JSON config file path in shell form. +// Used by engines that resolve the config via the host RUNNER_TEMP path. +const ShellMcpServersJsonPath = GhAwRootDirShell + "/mcp-config/mcp-servers.json" + +// GhAwRootDirSlash is GhAwRootDir with a trailing slash (Actions expression form). +const GhAwRootDirSlash = GhAwRootDir + "/" + +// McpServersJsonPathExpr is the MCP servers JSON config path in Actions expression form. +const McpServersJsonPathExpr = GhAwRootDir + "/mcp-config/mcp-servers.json" + +// CodexMcpConfigTomlPath is the Codex MCP config TOML file path in Actions expression form. +const CodexMcpConfigTomlPath = GhAwRootDir + "/mcp-config/config.toml" + +// System path constants +// +// Well-known host system paths used by CLI tools and shell completion. + +// CopilotBinaryPath is the path to the Copilot CLI binary inside AWF containers. +const CopilotBinaryPath = "/usr/local/bin/copilot" + +// BashCompletionDir is the system-wide bash completion directory. +const BashCompletionDir = "/etc/bash_completion.d" + +// BashCompletionGhAwPath is the gh-aw bash completion file path. +const BashCompletionGhAwPath = BashCompletionDir + "/gh-aw" + +// HomebrewPrefix is the default Homebrew installation prefix on macOS. +const HomebrewPrefix = "/opt/homebrew" + +// UsrLocalPrefix is the standard /usr/local installation prefix. +const UsrLocalPrefix = "/usr/local" + // GetWorkflowDir returns the workflows directory path. // Always uses forward slashes, which are required for git/GitHub paths. // GH_AW_WORKFLOWS_DIR overrides the default; any OS-specific separators are normalized. @@ -371,7 +534,7 @@ func GetWorkflowDir() string { if dir := os.Getenv("GH_AW_WORKFLOWS_DIR"); dir != "" { return filepath.ToSlash(dir) } - return ".github/workflows" + return WorkflowsDir } // MaxSymlinkDepth limits recursive symlink resolution when fetching remote files. diff --git a/pkg/linters/hardcodedfilepath/hardcodedfilepath.go b/pkg/linters/hardcodedfilepath/hardcodedfilepath.go index 2f5484fdfb9..85036a8354a 100644 --- a/pkg/linters/hardcodedfilepath/hardcodedfilepath.go +++ b/pkg/linters/hardcodedfilepath/hardcodedfilepath.go @@ -54,9 +54,9 @@ func (r constRef) String() string { // ".github", or "${RUNNER_TEMP}" alone are not flagged. var pathPrefixes = []string{ "/tmp/", - "${RUNNER_TEMP}/", - "${{ runner.temp }}/", - ".github/", + "${RUNNER_TEMP}/", //nolint:hardcodedfilepath + "${{ runner.temp }}/", //nolint:hardcodedfilepath + ".github/", //nolint:hardcodedfilepath "/opt/", "/usr/", "/var/", diff --git a/pkg/parser/import_field_extractor.go b/pkg/parser/import_field_extractor.go index 30b300955e5..0ad202df75a 100644 --- a/pkg/parser/import_field_extractor.go +++ b/pkg/parser/import_field_extractor.go @@ -12,6 +12,7 @@ import ( "regexp" "strings" + "github.com/github/gh-aw/pkg/constants" "github.com/github/gh-aw/pkg/importinpututil" ) @@ -960,7 +961,7 @@ func computeImportRelPath(fullPath, importPath string) string { if idx := strings.LastIndex(normalizedFullPath, "/.github/"); idx >= 0 { return normalizedFullPath[idx+1:] // +1 to skip the leading slash } - if strings.HasPrefix(normalizedFullPath, ".github/") { + if strings.HasPrefix(normalizedFullPath, constants.GithubDir) { return normalizedFullPath } return importPath diff --git a/pkg/parser/include_expander.go b/pkg/parser/include_expander.go index 21d3b41f508..5da8fa470d6 100644 --- a/pkg/parser/include_expander.go +++ b/pkg/parser/include_expander.go @@ -8,6 +8,7 @@ import ( "regexp" "strings" + "github.com/github/gh-aw/pkg/constants" "github.com/github/gh-aw/pkg/logger" ) @@ -192,7 +193,7 @@ func ExtractBodyLevelImportPaths(content, baseDir string) []BodyLevelImport { // Convert relative paths to workspace-root-relative. // Paths already starting with ".github/" are workspace-root-relative. // Absolute paths are used as-is. - if !strings.HasPrefix(importPath, ".github/") && !filepath.IsAbs(importPath) { + if !strings.HasPrefix(importPath, constants.GithubDir) && !filepath.IsAbs(importPath) { if repoRoot != "" { fullPath := filepath.Join(baseDir, importPath) if rel, err := filepath.Rel(repoRoot, fullPath); err == nil && !strings.HasPrefix(rel, "..") { diff --git a/pkg/parser/mcp.go b/pkg/parser/mcp.go index 250ea162e85..2ab1d8b82a0 100644 --- a/pkg/parser/mcp.go +++ b/pkg/parser/mcp.go @@ -343,7 +343,7 @@ func buildPlaywrightBuiltinConfig(toolValue any) RegistryMCPServerConfig { Command: "docker", Args: []string{ "run", "-i", "--rm", "--shm-size=2gb", "--cap-add=SYS_ADMIN", - "-v", "/tmp/gh-aw/mcp-logs:/tmp/gh-aw/mcp-logs", + "-v", constants.TmpMcpLogsMount, "mcr.microsoft.com/playwright:" + string(constants.DefaultPlaywrightBrowserVersion), }, Env: make(map[string]string), diff --git a/pkg/parser/remote_fetch.go b/pkg/parser/remote_fetch.go index 351fe303a5a..a6ebcbcf40a 100644 --- a/pkg/parser/remote_fetch.go +++ b/pkg/parser/remote_fetch.go @@ -109,12 +109,12 @@ func isUnderWorkflowsDirectory(filePath string) bool { normalizedPath := filepath.ToSlash(filePath) // Check if the path contains .github/workflows/ - if !strings.Contains(normalizedPath, ".github/workflows/") { + if !strings.Contains(normalizedPath, constants.WorkflowsDirSlash) { return false } // Extract the part after .github/workflows/ - parts := strings.Split(normalizedPath, ".github/workflows/") + parts := strings.Split(normalizedPath, constants.WorkflowsDirSlash) if len(parts) < 2 { return false } @@ -134,7 +134,7 @@ func isCustomAgentFile(filePath string) bool { normalizedPath := filepath.ToSlash(filePath) // Check if the path contains .github/agents/ and ends with .md - return strings.Contains(normalizedPath, ".github/agents/") && strings.HasSuffix(strings.ToLower(normalizedPath), ".md") + return strings.Contains(normalizedPath, constants.AgentsDir) && strings.HasSuffix(strings.ToLower(normalizedPath), ".md") } // isRepositoryImport checks if an import spec is a repository-only import (no file path) @@ -239,10 +239,10 @@ func computeIncludeResolveAndSecurityBases(filePath, baseDir string) (string, st if strings.HasSuffix(githubFolder, ".github") { repoRoot := filepath.Dir(githubFolder) filePathSlash := filepath.ToSlash(filePath) - if strings.HasPrefix(filePathSlash, ".github/") { + if strings.HasPrefix(filePathSlash, constants.GithubDir) { resolveBase = repoRoot } else if stripped, ok := strings.CutPrefix(filePathSlash, "/"); ok { - if !strings.HasPrefix(stripped, ".github/") && !strings.HasPrefix(stripped, ".agents/") { + if !strings.HasPrefix(stripped, constants.GithubDir) && !strings.HasPrefix(stripped, ".agents/") { return "", "", filePath } normalizedFilePath = filepath.FromSlash(stripped) @@ -259,7 +259,7 @@ func computeIncludeResolveAndSecurityBases(filePath, baseDir string) (string, st func resolveAndValidateLocalIncludePath(filePath, resolveBase, securityBase string) (string, error) { if stripped, ok := strings.CutPrefix(filepath.ToSlash(filePath), "/"); ok { - if !strings.HasPrefix(stripped, ".github/") && !strings.HasPrefix(stripped, ".agents/") { + if !strings.HasPrefix(stripped, constants.GithubDir) && !strings.HasPrefix(stripped, ".agents/") { remoteLog.Printf("Security: Path not within .github or .agents: %s", filePath) return "", fmt.Errorf("security: path %s must be within .github or .agents folder", filePath) } diff --git a/pkg/workflow/agentic_engine.go b/pkg/workflow/agentic_engine.go index 854f4fecd96..1eba394adf8 100644 --- a/pkg/workflow/agentic_engine.go +++ b/pkg/workflow/agentic_engine.go @@ -345,7 +345,7 @@ func (e *BaseEngine) GetModelEnvVarName() string { // Engines can override this to use engine-specific log files func (e *BaseEngine) GetLogFileForParsing() string { // Default to agent-stdio.log which contains stdout/stderr - return "/tmp/gh-aw/agent-stdio.log" + return constants.AgentStdioLogPath } // GetRequiredSecretNames returns an empty list by default diff --git a/pkg/workflow/antigravity_engine.go b/pkg/workflow/antigravity_engine.go index 934bec2d187..a408cc40689 100644 --- a/pkg/workflow/antigravity_engine.go +++ b/pkg/workflow/antigravity_engine.go @@ -105,7 +105,7 @@ func (e *AntigravityEngine) GetInstallationSteps(workflowData *WorkflowData) []G // ancestor under /tmp/gh-aw/ and the actions/upload-artifact LCA calculation stays correct. func (e *AntigravityEngine) GetDeclaredOutputFiles() []string { return []string{ - "/tmp/gh-aw/antigravity-client-error-*.json", + constants.TmpAntigravityClientErrorGlob, } } @@ -237,7 +237,7 @@ touch %s // Build environment variables env := map[string]string{ "ANTIGRAVITY_API_KEY": "${{ secrets.ANTIGRAVITY_API_KEY }}", - "GH_AW_PROMPT": "/tmp/gh-aw/aw-prompts/prompt.txt", + "GH_AW_PROMPT": constants.AwPromptsFile, // Tag the step as a GitHub AW agentic execution for discoverability by agents "GITHUB_AW": "true", "GITHUB_WORKSPACE": "${{ github.workspace }}", diff --git a/pkg/workflow/antigravity_logs.go b/pkg/workflow/antigravity_logs.go index eecb29ac5b2..7d2548a9a34 100644 --- a/pkg/workflow/antigravity_logs.go +++ b/pkg/workflow/antigravity_logs.go @@ -4,6 +4,7 @@ import ( "encoding/json" "strings" + "github.com/github/gh-aw/pkg/constants" "github.com/github/gh-aw/pkg/logger" ) @@ -110,7 +111,7 @@ func (e *AntigravityEngine) GetLogParserScriptId() string { // GetLogFileForParsing returns the log file path for parsing func (e *AntigravityEngine) GetLogFileForParsing() string { - return "/tmp/gh-aw/agent-stdio.log" + return constants.AgentStdioLogPath } // GetDefaultDetectionModel returns the default model for threat detection diff --git a/pkg/workflow/antigravity_mcp.go b/pkg/workflow/antigravity_mcp.go index d37d7b02c9f..d1e9ef5036c 100644 --- a/pkg/workflow/antigravity_mcp.go +++ b/pkg/workflow/antigravity_mcp.go @@ -3,6 +3,7 @@ package workflow import ( "strings" + "github.com/github/gh-aw/pkg/constants" "github.com/github/gh-aw/pkg/logger" ) @@ -13,5 +14,5 @@ func (e *AntigravityEngine) RenderMCPConfig(yaml *strings.Builder, tools map[str antigravityMCPLog.Printf("Rendering MCP config for Antigravity: tool_count=%d, mcp_tool_count=%d", len(tools), len(mcpTools)) // Antigravity uses JSON format without Copilot-specific fields and multi-line args - return renderDefaultJSONMCPConfig(yaml, tools, mcpTools, workflowData, "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json") + return renderDefaultJSONMCPConfig(yaml, tools, mcpTools, workflowData, constants.ShellMcpServersJsonPath) } diff --git a/pkg/workflow/awf_helpers.go b/pkg/workflow/awf_helpers.go index e438367440e..1b1db072881 100644 --- a/pkg/workflow/awf_helpers.go +++ b/pkg/workflow/awf_helpers.go @@ -286,7 +286,7 @@ fi`, // Build the expandable args string for args that need shell variable expansion. // These MUST be appended as raw (unescaped) strings because single-quoting would // prevent the runner's shell from expanding ${GITHUB_WORKSPACE} and ${RUNNER_TEMP}. - ghAwDir := "${RUNNER_TEMP}/gh-aw" + ghAwDir := constants.GhAwRootDirShell expandableArgs := fmt.Sprintf( `--container-workdir "${GITHUB_WORKSPACE}" --mount "%s:%s:ro" --mount "%s:/host%s:ro"`, ghAwDir, ghAwDir, ghAwDir, ghAwDir, @@ -380,7 +380,7 @@ fi`, // is mounted :ro above; this child mount overrides access for the staging subdirectory only. // The staging directory must already exist on the host (created in Generate Safe Outputs Config step). if config.WorkflowData != nil && config.WorkflowData.SafeOutputs != nil && config.WorkflowData.SafeOutputs.UploadArtifact != nil { - stagingDir := "${RUNNER_TEMP}/gh-aw/safeoutputs/upload-artifacts" + stagingDir := SafeOutputsUploadArtifactsDir expandableArgs += fmt.Sprintf(` --mount "%s:%s:rw"`, stagingDir, stagingDir) awfHelpersLog.Print("Added read-write mount for upload_artifact staging directory") } @@ -644,7 +644,7 @@ func BuildAWFArgs(config AWFCommandConfig) []string { if isGitHubCLIModeEnabled(config.WorkflowData) { if awfSupportsCliProxy(firewallConfig) { awfArgs = append(awfArgs, "--difc-proxy-host", "host.docker.internal:18443") - awfArgs = append(awfArgs, "--difc-proxy-ca-cert", "/tmp/gh-aw/difc-proxy-tls/ca.crt") + awfArgs = append(awfArgs, "--difc-proxy-ca-cert", constants.TmpDIFCProxyTLSCACert) awfHelpersLog.Print("Added --difc-proxy-host and --difc-proxy-ca-cert for CLI proxy sidecar") } else { awfHelpersLog.Printf("Skipping CLI proxy flags: AWF version %q is older than minimum %s", getAWFImageTag(firewallConfig), constants.AWFCliProxyMinVersion) diff --git a/pkg/workflow/claude_engine.go b/pkg/workflow/claude_engine.go index d57e44ed0ab..9ce12dbfb0b 100644 --- a/pkg/workflow/claude_engine.go +++ b/pkg/workflow/claude_engine.go @@ -357,7 +357,7 @@ func (e *ClaudeEngine) GetExecutionSteps(workflowData *WorkflowData, logFile str // "Fast mode unavailable: Fast mode is not available in the Agent SDK", // which crashes the agent mid-session on every API call. "CLAUDE_CODE_DISABLE_FAST_MODE": "1", - "GH_AW_PROMPT": "/tmp/gh-aw/aw-prompts/prompt.txt", + "GH_AW_PROMPT": constants.AwPromptsFile, // Tag the step as a GitHub AW agentic execution for discoverability by agents "GITHUB_AW": "true", // Override GITHUB_STEP_SUMMARY with a path that exists inside the sandbox. @@ -384,7 +384,7 @@ func (e *ClaudeEngine) GetExecutionSteps(workflowData *WorkflowData, logFile str // Add GH_AW_MCP_CONFIG for MCP server configuration only if there are MCP servers if HasMCPServers(workflowData) { - env["GH_AW_MCP_CONFIG"] = "${{ runner.temp }}/gh-aw/mcp-config/mcp-servers.json" + env["GH_AW_MCP_CONFIG"] = constants.McpServersJsonPathExpr } // In sandbox (AWF) mode, set git identity environment variables so the first git commit diff --git a/pkg/workflow/claude_mcp.go b/pkg/workflow/claude_mcp.go index 459a6cabf94..fdfe0aec45e 100644 --- a/pkg/workflow/claude_mcp.go +++ b/pkg/workflow/claude_mcp.go @@ -3,6 +3,7 @@ package workflow import ( "strings" + "github.com/github/gh-aw/pkg/constants" "github.com/github/gh-aw/pkg/logger" ) @@ -13,5 +14,5 @@ func (e *ClaudeEngine) RenderMCPConfig(yaml *strings.Builder, tools map[string]a claudeMCPLog.Printf("Rendering MCP config for Claude: tool_count=%d, mcp_tool_count=%d", len(tools), len(mcpTools)) // Claude uses JSON format without Copilot-specific fields and multi-line args - return renderDefaultJSONMCPConfig(yaml, tools, mcpTools, workflowData, "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json") + return renderDefaultJSONMCPConfig(yaml, tools, mcpTools, workflowData, constants.ShellMcpServersJsonPath) } diff --git a/pkg/workflow/codex_engine.go b/pkg/workflow/codex_engine.go index 9577bc6d6ad..ef61df59c33 100644 --- a/pkg/workflow/codex_engine.go +++ b/pkg/workflow/codex_engine.go @@ -132,7 +132,7 @@ func (e *CodexEngine) GetInstallationSteps(workflowData *WorkflowData) []GitHubA func (e *CodexEngine) GetDeclaredOutputFiles() []string { // Return the Codex log directory for artifact collection. return []string{ - "/tmp/gh-aw/mcp-config/logs/", + constants.TmpMcpConfigLogsDir, } } @@ -384,14 +384,14 @@ mkdir -p "$CODEX_HOME/logs" // we create this file before the agent starts and append it to the real // $GITHUB_STEP_SUMMARY after secret redaction. "GITHUB_STEP_SUMMARY": AgentStepSummaryPath, - "GH_AW_PROMPT": "/tmp/gh-aw/aw-prompts/prompt.txt", + "GH_AW_PROMPT": constants.AwPromptsFile, // Tag the step as a GitHub AW agentic execution for discoverability by agents "GITHUB_AW": "true", "RUNNER_TEMP": "${{ runner.temp }}", - "GH_AW_MCP_CONFIG": "${{ runner.temp }}/gh-aw/mcp-config/config.toml", + "GH_AW_MCP_CONFIG": constants.CodexMcpConfigTomlPath, // Keep Codex runtime state in /tmp/gh-aw because ${RUNNER_TEMP}/gh-aw is // mounted read-only inside the AWF chroot sandbox. - "CODEX_HOME": "/tmp/gh-aw/mcp-config", + "CODEX_HOME": constants.TmpMcpConfigDir, // Enable verbose RUST_LOG only in debug mode (runner.debug == 1); default to warn to avoid noisy output. "RUST_LOG": "${{ runner.debug == 1 && 'trace,hyper_util=info,mio=info,reqwest=info,os_info=info,codex_otel=warn,codex_core=debug,ocodex_exec=debug' || 'warn' }}", "GH_AW_GITHUB_TOKEN": effectiveGitHubToken, diff --git a/pkg/workflow/codex_mcp.go b/pkg/workflow/codex_mcp.go index 0a66ddaa112..81b82924f1c 100644 --- a/pkg/workflow/codex_mcp.go +++ b/pkg/workflow/codex_mcp.go @@ -117,7 +117,7 @@ func (e *CodexEngine) RenderMCPConfig(yaml *strings.Builder, tools map[string]an tools: tools, mcpTools: mcpTools, workflowData: workflowData, - configPath: "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json", + configPath: constants.ShellMcpServersJsonPath, renderCustom: func(yaml *strings.Builder, toolName string, toolConfig map[string]any, isLast bool) error { return e.renderCodexJSONMCPConfigWithContext(yaml, toolName, toolConfig, isLast, workflowData) }, diff --git a/pkg/workflow/compiler_difc_proxy.go b/pkg/workflow/compiler_difc_proxy.go index 87effc082fd..77912f9d1c3 100644 --- a/pkg/workflow/compiler_difc_proxy.go +++ b/pkg/workflow/compiler_difc_proxy.go @@ -316,7 +316,7 @@ func proxyEnvVars() map[string]string { "GH_REPO": "${{ github.repository }}", "GITHUB_API_URL": "https://localhost:18443/api/v3", "GITHUB_GRAPHQL_URL": "https://localhost:18443/api/graphql", - "NODE_EXTRA_CA_CERTS": "/tmp/gh-aw/proxy-logs/proxy-tls/ca.crt", + "NODE_EXTRA_CA_CERTS": constants.TmpProxyTLSCACert, } } @@ -558,7 +558,7 @@ func difcProxyLogPaths(data *WorkflowData) []string { // Exclude proxy-tls/ to avoid uploading TLS material (mcp-logs/ is already // collected as part of standard MCP logging). return []string{ - "/tmp/gh-aw/proxy-logs/", - "!/tmp/gh-aw/proxy-logs/proxy-tls/", + constants.TmpProxyLogsDir, + "!" + constants.TmpProxyTLSDir, } } diff --git a/pkg/workflow/compiler_main_job.go b/pkg/workflow/compiler_main_job.go index 633467fa76d..458f2129260 100644 --- a/pkg/workflow/compiler_main_job.go +++ b/pkg/workflow/compiler_main_job.go @@ -301,7 +301,7 @@ func (c *Compiler) buildMainJob(data *WorkflowData, activationJobCreated bool) ( // 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" + env["GH_AW_MCP_LOG_DIR"] = constants.TmpMcpLogsSafeOutputsDir // Note: GH_AW_SAFE_OUTPUTS, GH_AW_SAFE_OUTPUTS_CONFIG_PATH, and // GH_AW_SAFE_OUTPUTS_TOOLS_PATH are set via a run step (see generateSetRuntimePathsStep) diff --git a/pkg/workflow/compiler_safe_outputs_job.go b/pkg/workflow/compiler_safe_outputs_job.go index 3937406e749..32a9b477154 100644 --- a/pkg/workflow/compiler_safe_outputs_job.go +++ b/pkg/workflow/compiler_safe_outputs_job.go @@ -118,7 +118,7 @@ func (c *Compiler) buildSafeOutputsSetupAndDownloadSteps(data *WorkflowData, age consolidatedSafeOutputsJobLog.Print("Adding patch artifact download for create-pull-request or push-to-pull-request-branch") patchDownloadSteps := buildArtifactDownloadSteps(ArtifactDownloadConfig{ ArtifactName: agentArtifactPrefix + constants.AgentArtifactName, - DownloadPath: "/tmp/gh-aw/", + DownloadPath: constants.TmpGhAwDirSlash, SetupEnvStep: false, // No environment variable needed, the script checks the file directly StepName: "Download patch artifact", }, c.getActionPin) @@ -478,7 +478,7 @@ func (c *Compiler) buildSafeOutputsJobFromParts( if usesPatchesAndCheckouts(data.SafeOutputs) { patchDownloadSteps := buildArtifactDownloadSteps(ArtifactDownloadConfig{ ArtifactName: agentArtifactPrefix + constants.AgentArtifactName, - DownloadPath: "/tmp/gh-aw/", + DownloadPath: constants.TmpGhAwDirSlash, SetupEnvStep: false, StepName: "Download patch artifact", }, c.getActionPin) diff --git a/pkg/workflow/compiler_yaml.go b/pkg/workflow/compiler_yaml.go index 56961b56c71..5825534710d 100644 --- a/pkg/workflow/compiler_yaml.go +++ b/pkg/workflow/compiler_yaml.go @@ -672,7 +672,7 @@ func (c *Compiler) generatePrompt(yaml *strings.Builder, data *WorkflowData, pre // Extract everything from ".github/" onwards (inclusive) // +1 to skip the leading slash, so we get ".github/workflows/..." not "/.github/workflows/..." workflowFilePath = normalizedPath[githubIndex+1:] - } else if strings.HasPrefix(normalizedPath, ".github/") { + } else if strings.HasPrefix(normalizedPath, constants.GithubDir) { // Relative path already starting with ".github/" — use as-is. // This can happen when the compiler is invoked with a relative markdown path // (e.g. ".github/workflows/test.md") rather than an absolute one. @@ -1077,7 +1077,7 @@ func resolveWorkspaceRoot(markdownPath string) string { // Absolute or non-root-relative path: strip everything from "/.github/" onward. return filepath.FromSlash(before) } - if strings.HasPrefix(normalized, ".github/") { + if strings.HasPrefix(normalized, constants.GithubDir) { // Path already starts at the workspace root. return "." } diff --git a/pkg/workflow/compiler_yaml_main_job.go b/pkg/workflow/compiler_yaml_main_job.go index 6289cf5e50f..554f2b9fcda 100644 --- a/pkg/workflow/compiler_yaml_main_job.go +++ b/pkg/workflow/compiler_yaml_main_job.go @@ -462,9 +462,9 @@ func (c *Compiler) generateEngineInstallAndPreAgentSteps(yaml *strings.Builder, func (c *Compiler) generateAgentRunSteps(yaml *strings.Builder, data *WorkflowData, engine CodingAgentEngine, needsGitConfig bool) ([]string, string, error) { // Collect artifact paths for unified upload at the end var artifactPaths []string - artifactPaths = append(artifactPaths, "/tmp/gh-aw/aw-prompts/prompt.txt") + artifactPaths = append(artifactPaths, constants.AwPromptsFile) - logFileFull := "/tmp/gh-aw/agent-stdio.log" + logFileFull := constants.AgentStdioLogPath // Clean credentials before executing the agentic engine. // This removes git credentials from .git/config and, when known credential-leaking @@ -581,21 +581,21 @@ func (c *Compiler) collectArtifactPaths(data *WorkflowData, engine CodingAgentEn paths = append(paths, getEngineArtifactPaths(engine)...) // Collect MCP logs. - paths = append(paths, "/tmp/gh-aw/mcp-logs/") + paths = append(paths, constants.TmpMcpLogsDir) // Collect DIFC proxy logs (proxy-tls certs + container stderr) when proxy was injected paths = append(paths, difcProxyLogPaths(data)...) // Collect MCPScripts logs path if mcp-scripts is enabled if IsMCPScriptsEnabled(data.MCPScripts) { - paths = append(paths, "/tmp/gh-aw/mcp-scripts/logs/") + paths = append(paths, constants.TmpMcpScriptsLogsDir) } // Include the aggregated agent_usage.json in the agent artifact so third-party // tools can consume structured token data without parsing the step summary. // Requires AWF v0.25.8+ if isFirewallEnabled(data) { - paths = append(paths, "/tmp/gh-aw/"+constants.TokenUsageFilename) + paths = append(paths, constants.TmpGhAwDirSlash+constants.TokenUsageFilename) } // Collect agent stdio logs path for unified upload @@ -608,29 +608,29 @@ func (c *Compiler) collectArtifactPaths(data *WorkflowData, engine CodingAgentEn // Collect agent-generated files path for unified upload // This directory is used by workflows that instruct the agent to write files // (e.g., smoke-claude status summaries) - paths = append(paths, "/tmp/gh-aw/agent/") + paths = append(paths, constants.TmpGhAwAgentDir) // Collect GitHub API rate-limit log for observability. // Written by github_rate_limit_logger.cjs during REST API calls. - paths = append(paths, "/tmp/gh-aw/"+constants.GithubRateLimitsFilename) + paths = append(paths, constants.TmpGhAwDirSlash+constants.GithubRateLimitsFilename) // Collect OTLP span mirror — enables post-hoc trace debugging without a live collector. // Written by send_otlp_span.cjs; each line is a full OTLP/HTTP JSON traces payload. // Only included when OTLP is configured for this workflow. if isOTLPEnabled(data) { - paths = append(paths, "/tmp/gh-aw/"+constants.OtelJsonlFilename) - paths = append(paths, "/tmp/gh-aw/"+constants.OtlpExportErrorsFilename) + paths = append(paths, constants.TmpGhAwDirSlash+constants.OtelJsonlFilename) + paths = append(paths, constants.TmpGhAwDirSlash+constants.OtlpExportErrorsFilename) } // Collect safe outputs and agent output paths for the unified artifact. // These were previously uploaded as separate safe-output and agent-output artifacts. if data.SafeOutputs != nil { // Raw safe-output NDJSON (copied to /tmp/gh-aw/ by generateOutputCollectionStep) - paths = append(paths, "/tmp/gh-aw/"+constants.SafeOutputsFilename) + paths = append(paths, constants.TmpGhAwDirSlash+constants.SafeOutputsFilename) // Processed agent output JSON produced by collect_ndjson_output.cjs - paths = append(paths, "/tmp/gh-aw/"+constants.AgentOutputFilename) + paths = append(paths, constants.TmpGhAwDirSlash+constants.AgentOutputFilename) if data.SafeOutputs.CommentMemory != nil { - paths = append(paths, "/tmp/gh-aw/comment-memory/") + paths = append(paths, constants.TmpCommentMemoryDir) } } @@ -644,13 +644,13 @@ func (c *Compiler) collectArtifactPaths(data *WorkflowData, engine CodingAgentEn // safe-output handler is staged and doesn't need checkout itself) threatDetectionNeedsPatches := IsDetectionJobEnabled(data.SafeOutputs) if usesPatchesAndCheckouts(data.SafeOutputs) || threatDetectionNeedsPatches { - paths = append(paths, "/tmp/gh-aw/aw-*.patch") + paths = append(paths, constants.TmpAwPatchGlob) // Bundle files are generated when patch-format: bundle is configured. // Both formats use the same download path in the safe_outputs job, so // include the bundle glob unconditionally alongside the patch glob. // The artifact upload step already sets if-no-files-found: ignore, so // this is safe even when no bundle files exist. - paths = append(paths, "/tmp/gh-aw/aw-*.bundle") + paths = append(paths, constants.TmpAwBundleGlob) } // Include firewall audit/observability logs in the unified agent artifact diff --git a/pkg/workflow/copilot_engine.go b/pkg/workflow/copilot_engine.go index 8ce6ccdbe55..724451f2390 100644 --- a/pkg/workflow/copilot_engine.go +++ b/pkg/workflow/copilot_engine.go @@ -137,7 +137,7 @@ func (e *CopilotEngine) GetAgentManifestFiles() []string { // The .github/ directory contains copilot-instructions.md, path-specific instruction // files, and copilot-setup-steps.yml — any of which can alter agent behaviour. func (e *CopilotEngine) GetAgentManifestPathPrefixes() []string { - return []string{".github/"} + return []string{constants.GithubDir} } // GetHarnessScriptName returns the filename of the JavaScript harness script that wraps diff --git a/pkg/workflow/copilot_engine_execution.go b/pkg/workflow/copilot_engine_execution.go index 3a2f68b26d7..c6eabddfd15 100644 --- a/pkg/workflow/copilot_engine_execution.go +++ b/pkg/workflow/copilot_engine_execution.go @@ -142,7 +142,7 @@ func (e *CopilotEngine) GetExecutionSteps(workflowData *WorkflowData, logFile st isBYOKMode := engineEnvHasKey(workflowData, constants.CopilotProviderBaseURL) if sandboxEnabled { // Simplified args for sandbox mode (AWF) - copilotArgs = []string{"--add-dir", "/tmp/gh-aw/", "--log-level", "all", "--log-dir", logsFolder} + copilotArgs = []string{"--add-dir", constants.TmpGhAwDirSlash, "--log-level", "all", "--log-dir", logsFolder} // Note: --add-dir "${GITHUB_WORKSPACE}" is appended raw after shellJoinArgs below // to allow shell variable expansion (cannot go through shellEscapeArg). @@ -151,7 +151,7 @@ func (e *CopilotEngine) GetExecutionSteps(workflowData *WorkflowData, logFile st copilotExecLog.Print("Using firewall mode with simplified arguments") } else { // Original args for non-sandbox mode - copilotArgs = []string{"--add-dir", "/tmp/", "--add-dir", "/tmp/gh-aw/", "--add-dir", "/tmp/gh-aw/agent/", "--log-level", "all", "--log-dir", logsFolder} + copilotArgs = []string{"--add-dir", "/tmp/", "--add-dir", constants.TmpGhAwDirSlash, "--add-dir", constants.TmpGhAwAgentDir, "--log-level", "all", "--log-dir", logsFolder} copilotExecLog.Print("Using standard mode with full arguments") } @@ -262,7 +262,7 @@ func (e *CopilotEngine) GetExecutionSteps(workflowData *WorkflowData, logFile st } else if sandboxEnabled { // AWF - use the installed binary directly // The binary is mounted into the AWF container from /usr/local/bin/copilot - commandName = "/usr/local/bin/copilot" + commandName = constants.CopilotBinaryPath } else { // Non-sandbox mode: use standard copilot command commandName = "copilot" @@ -578,7 +578,7 @@ touch %s } // Always add GH_AW_PROMPT for agentic workflows - env["GH_AW_PROMPT"] = "/tmp/gh-aw/aw-prompts/prompt.txt" + env["GH_AW_PROMPT"] = constants.AwPromptsFile // Tag the step as a GitHub AW agentic execution for discoverability by agents env["GITHUB_AW"] = "true" diff --git a/pkg/workflow/copilot_logs.go b/pkg/workflow/copilot_logs.go index 5d57b489f27..7d1cd84571c 100644 --- a/pkg/workflow/copilot_logs.go +++ b/pkg/workflow/copilot_logs.go @@ -4,6 +4,7 @@ import ( "encoding/json" "strings" + "github.com/github/gh-aw/pkg/constants" "github.com/github/gh-aw/pkg/logger" ) @@ -462,5 +463,5 @@ func (e *CopilotEngine) GetErrorDetectionScriptId() string { // GetLogFileForParsing returns the log directory for Copilot CLI logs // Copilot writes detailed debug logs to /tmp/gh-aw/sandbox/agent/logs/ func (e *CopilotEngine) GetLogFileForParsing() string { - return "/tmp/gh-aw/sandbox/agent/logs/" + return constants.TmpSandboxAgentLogsDir } diff --git a/pkg/workflow/crush_engine.go b/pkg/workflow/crush_engine.go index 495a54cbe1d..f2c8ed004f8 100644 --- a/pkg/workflow/crush_engine.go +++ b/pkg/workflow/crush_engine.go @@ -203,7 +203,7 @@ func (e *CrushEngine) GetExecutionSteps(workflowData *WorkflowData, logFile stri } env := map[string]string{ - "GH_AW_PROMPT": "/tmp/gh-aw/aw-prompts/prompt.txt", + "GH_AW_PROMPT": constants.AwPromptsFile, "GITHUB_WORKSPACE": "${{ github.workspace }}", "RUNNER_TEMP": "${{ runner.temp }}", "NO_PROXY": "localhost,127.0.0.1", diff --git a/pkg/workflow/crush_mcp.go b/pkg/workflow/crush_mcp.go index dcb15cc8d80..54a705a850f 100644 --- a/pkg/workflow/crush_mcp.go +++ b/pkg/workflow/crush_mcp.go @@ -3,6 +3,7 @@ package workflow import ( "strings" + "github.com/github/gh-aw/pkg/constants" "github.com/github/gh-aw/pkg/logger" ) @@ -13,5 +14,5 @@ func (e *CrushEngine) RenderMCPConfig(sb *strings.Builder, tools map[string]any, crushMCPLog.Printf("Rendering MCP config for Crush: tool_count=%d, mcp_tool_count=%d", len(tools), len(mcpTools)) // Crush uses JSON format without Copilot-specific fields and multi-line args - return renderDefaultJSONMCPConfig(sb, tools, mcpTools, workflowData, "/tmp/gh-aw/mcp-config/mcp-servers.json") + return renderDefaultJSONMCPConfig(sb, tools, mcpTools, workflowData, constants.TmpMcpServersJsonPath) } diff --git a/pkg/workflow/engine_output.go b/pkg/workflow/engine_output.go index af99f10cbda..27bfe7d9fd9 100644 --- a/pkg/workflow/engine_output.go +++ b/pkg/workflow/engine_output.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/github/gh-aw/pkg/constants" "github.com/github/gh-aw/pkg/logger" ) @@ -33,7 +34,7 @@ func generateCleanupStep(outputFiles []string) (string, bool) { // Filter to get only workspace files (exclude /tmp/gh-aw/ files) var workspaceFiles []string for _, file := range outputFiles { - if !strings.HasPrefix(file, "/tmp/gh-aw/") { + if !strings.HasPrefix(file, constants.TmpGhAwDirSlash) { workspaceFiles = append(workspaceFiles, file) } } diff --git a/pkg/workflow/frontmatter_extraction_metadata.go b/pkg/workflow/frontmatter_extraction_metadata.go index 2fe7a6726aa..87309069916 100644 --- a/pkg/workflow/frontmatter_extraction_metadata.go +++ b/pkg/workflow/frontmatter_extraction_metadata.go @@ -7,6 +7,7 @@ import ( "strconv" "strings" + "github.com/github/gh-aw/pkg/constants" "github.com/github/gh-aw/pkg/logger" "github.com/github/gh-aw/pkg/typeutil" ) @@ -189,7 +190,7 @@ func buildLocalWorkflowSourceURL(markdownPath string) string { if idx != -1 { // Skip the leading slash to get ".github/workflows/...". relPath = normalised[idx+1:] - } else if strings.HasPrefix(normalised, ".github/") { + } else if strings.HasPrefix(normalised, constants.GithubDir) { // Already a relative path starting with ".github/". relPath = normalised } else { diff --git a/pkg/workflow/gemini_engine.go b/pkg/workflow/gemini_engine.go index 2ab7e249b8a..f1f6430f191 100644 --- a/pkg/workflow/gemini_engine.go +++ b/pkg/workflow/gemini_engine.go @@ -107,7 +107,7 @@ func (e *GeminiEngine) GetInstallationSteps(workflowData *WorkflowData) []GitHub // ancestor under /tmp/gh-aw/ and the actions/upload-artifact LCA calculation stays correct. func (e *GeminiEngine) GetDeclaredOutputFiles() []string { return []string{ - "/tmp/gh-aw/gemini-client-error-*.json", + constants.TmpGeminiClientErrorGlob, } } @@ -249,7 +249,7 @@ touch %s // Build environment variables env := map[string]string{ "GEMINI_API_KEY": "${{ secrets.GEMINI_API_KEY }}", - "GH_AW_PROMPT": "/tmp/gh-aw/aw-prompts/prompt.txt", + "GH_AW_PROMPT": constants.AwPromptsFile, // Tag the step as a GitHub AW agentic execution for discoverability by agents "GITHUB_AW": "true", "GITHUB_WORKSPACE": "${{ github.workspace }}", diff --git a/pkg/workflow/gemini_logs.go b/pkg/workflow/gemini_logs.go index 636b9e9d99b..fb8a626d058 100644 --- a/pkg/workflow/gemini_logs.go +++ b/pkg/workflow/gemini_logs.go @@ -4,6 +4,7 @@ import ( "encoding/json" "strings" + "github.com/github/gh-aw/pkg/constants" "github.com/github/gh-aw/pkg/logger" ) @@ -96,7 +97,7 @@ func (e *GeminiEngine) GetLogParserScriptId() string { // GetLogFileForParsing returns the log file path for parsing func (e *GeminiEngine) GetLogFileForParsing() string { - return "/tmp/gh-aw/agent-stdio.log" + return constants.AgentStdioLogPath } // GetDefaultDetectionModel returns the default model for threat detection diff --git a/pkg/workflow/gemini_mcp.go b/pkg/workflow/gemini_mcp.go index 444c51e78ff..a51ea8e9d77 100644 --- a/pkg/workflow/gemini_mcp.go +++ b/pkg/workflow/gemini_mcp.go @@ -3,6 +3,7 @@ package workflow import ( "strings" + "github.com/github/gh-aw/pkg/constants" "github.com/github/gh-aw/pkg/logger" ) @@ -13,5 +14,5 @@ func (e *GeminiEngine) RenderMCPConfig(yaml *strings.Builder, tools map[string]a geminiMCPLog.Printf("Rendering MCP config for Gemini: tool_count=%d, mcp_tool_count=%d", len(tools), len(mcpTools)) // Gemini uses JSON format without Copilot-specific fields and multi-line args - return renderDefaultJSONMCPConfig(yaml, tools, mcpTools, workflowData, "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json") + return renderDefaultJSONMCPConfig(yaml, tools, mcpTools, workflowData, constants.ShellMcpServersJsonPath) } diff --git a/pkg/workflow/mcp_config_playwright_renderer.go b/pkg/workflow/mcp_config_playwright_renderer.go index b68d5189303..763fb721926 100644 --- a/pkg/workflow/mcp_config_playwright_renderer.go +++ b/pkg/workflow/mcp_config_playwright_renderer.go @@ -61,6 +61,7 @@ import ( "encoding/json" "strings" + "github.com/github/gh-aw/pkg/constants" "github.com/github/gh-aw/pkg/logger" ) @@ -137,7 +138,7 @@ func renderPlaywrightMCPConfigWithOptions(yaml *strings.Builder, playwrightConfi // creates a network namespace for renderer processes that cannot reach localhost. // This is required for screenshot workflows that serve docs on localhost. // Note: as of @playwright/mcp v0.0.26+, --no-sandbox is a direct top-level flag. - entrypointArgs := []string{"--output-dir", "/tmp/gh-aw/mcp-logs/playwright", "--no-sandbox"} + entrypointArgs := []string{"--output-dir", constants.TmpMcpLogsPlaywrightDir, "--no-sandbox"} if len(customArgs) > 0 { entrypointArgs = append(entrypointArgs, customArgs...) } diff --git a/pkg/workflow/mcp_environment.go b/pkg/workflow/mcp_environment.go index f30b91b50ab..c286405a5e1 100644 --- a/pkg/workflow/mcp_environment.go +++ b/pkg/workflow/mcp_environment.go @@ -202,7 +202,7 @@ func collectMCPEnvironmentVariables(tools map[string]any, mcpTools []string, wor // the converted MCP config can be copied into the writable Codex home directory. // This matches the value set on the agent step in codex_engine.go. if workflowData != nil && workflowData.AI == string(constants.CodexEngine) { - envVars["CODEX_HOME"] = "/tmp/gh-aw/mcp-config" + envVars["CODEX_HOME"] = constants.TmpMcpConfigDir } return envVars diff --git a/pkg/workflow/mcp_renderer_builtin.go b/pkg/workflow/mcp_renderer_builtin.go index dda926504f2..263c0b6129f 100644 --- a/pkg/workflow/mcp_renderer_builtin.go +++ b/pkg/workflow/mcp_renderer_builtin.go @@ -191,7 +191,7 @@ func (r *MCPConfigRendererUnified) renderAgenticWorkflowsTOML(yaml *strings.Buil mounts = []string{constants.DefaultWorkspaceMount, constants.DefaultTmpGhAwMount} } else { // Release mode: Use minimal Alpine image with mounted binaries - entrypoint = "${RUNNER_TEMP}/gh-aw/gh-aw" + entrypoint = GhAwBinaryPath entrypointArgs = []string{"mcp-server", "--validate-actor"} // Mount gh-aw binary, gh CLI binary, workspace, and temp directory mounts = []string{constants.DefaultGhAwMount, constants.DefaultGhBinaryMount, constants.DefaultWorkspaceMount, constants.DefaultTmpGhAwMount} @@ -352,7 +352,7 @@ func renderAgenticWorkflowsMCPConfigWithOptions(yaml *strings.Builder, isLast bo // Release mode: Use minimal Alpine image with mounted binaries // The gh-aw binary is mounted from ${RUNNER_TEMP}/gh-aw and executed directly // Pass --validate-actor flag to enable role-based access control - entrypoint = "${RUNNER_TEMP}/gh-aw/gh-aw" + entrypoint = GhAwBinaryPath entrypointArgs = []string{"mcp-server", "--validate-actor"} // Mount gh-aw binary, gh CLI binary, workspace, and temp directory mounts = []string{constants.DefaultGhAwMount, constants.DefaultGhBinaryMount, constants.DefaultWorkspaceMount, constants.DefaultTmpGhAwMount} diff --git a/pkg/workflow/opencode_mcp.go b/pkg/workflow/opencode_mcp.go index 169c9669635..820d55145d5 100644 --- a/pkg/workflow/opencode_mcp.go +++ b/pkg/workflow/opencode_mcp.go @@ -3,6 +3,7 @@ package workflow import ( "strings" + "github.com/github/gh-aw/pkg/constants" "github.com/github/gh-aw/pkg/logger" ) @@ -12,5 +13,5 @@ var openCodeMCPLog = logger.New("workflow:opencode_mcp") func (e *OpenCodeEngine) RenderMCPConfig(sb *strings.Builder, tools map[string]any, mcpTools []string, workflowData *WorkflowData) error { openCodeMCPLog.Printf("Rendering MCP config for OpenCode: tool_count=%d, mcp_tool_count=%d", len(tools), len(mcpTools)) - return renderDefaultJSONMCPConfig(sb, tools, mcpTools, workflowData, "/tmp/gh-aw/mcp-config/mcp-servers.json") + return renderDefaultJSONMCPConfig(sb, tools, mcpTools, workflowData, constants.TmpMcpServersJsonPath) } diff --git a/pkg/workflow/pi_engine.go b/pkg/workflow/pi_engine.go index 8f60ff096af..09be4d69f7e 100644 --- a/pkg/workflow/pi_engine.go +++ b/pkg/workflow/pi_engine.go @@ -379,7 +379,7 @@ touch %s // vars; routing is instead handled through models.json (firewall case) or by Pi's // native provider (no-firewall case). env := map[string]string{ - "GH_AW_PROMPT": "/tmp/gh-aw/aw-prompts/prompt.txt", + "GH_AW_PROMPT": constants.AwPromptsFile, "GITHUB_AW": "true", "GITHUB_WORKSPACE": "${{ github.workspace }}", "GITHUB_STEP_SUMMARY": AgentStepSummaryPath, @@ -404,7 +404,7 @@ touch %s // When the models.json gateway approach is used, tell Pi where to find it. if piModelsJSONSetup != "" { - env["PI_CODING_AGENT_DIR"] = "/tmp/gh-aw/pi-agent-dir" + env["PI_CODING_AGENT_DIR"] = constants.TmpPiAgentDir piLog.Printf("Pi: setting PI_CODING_AGENT_DIR for models.json gateway config") } diff --git a/pkg/workflow/repo_memory.go b/pkg/workflow/repo_memory.go index abc07017425..c353f69c07e 100644 --- a/pkg/workflow/repo_memory.go +++ b/pkg/workflow/repo_memory.go @@ -504,7 +504,7 @@ func generateRepoMemoryArtifactUpload(builder *strings.Builder, data *WorkflowDa for _, memory := range data.RepoMemoryConfig.Memories { // Determine the memory directory - memoryDir := "/tmp/gh-aw/repo-memory/" + memory.ID + memoryDir := constants.TmpRepoMemoryDir + memory.ID // Sanitize memory ID for artifact naming (remove hyphens, lowercase) sanitizedID := SanitizeWorkflowIDForCacheKey(memory.ID) @@ -563,7 +563,7 @@ func generateRepoMemorySteps(builder *strings.Builder, data *WorkflowData) { } // Determine the memory directory - memoryDir := "/tmp/gh-aw/repo-memory/" + memory.ID + memoryDir := constants.TmpRepoMemoryDir + memory.ID // Step 1: Clone the repo-memory branch if memory.Wiki { @@ -694,7 +694,7 @@ func (c *Compiler) buildPushRepoMemoryJob(data *WorkflowData, threatDetectionEna targetRepo = targetRepo + ".wiki" } - artifactDir := "/tmp/gh-aw/repo-memory/" + memory.ID + artifactDir := constants.TmpRepoMemoryDir + memory.ID // Build file glob filter string fileGlobFilter := "" diff --git a/pkg/workflow/runtime_import_validation.go b/pkg/workflow/runtime_import_validation.go index c7480877622..77c09985504 100644 --- a/pkg/workflow/runtime_import_validation.go +++ b/pkg/workflow/runtime_import_validation.go @@ -23,6 +23,7 @@ import ( "regexp" "strings" + "github.com/github/gh-aw/pkg/constants" "github.com/github/gh-aw/pkg/parser" ) @@ -104,8 +105,8 @@ func validateRuntimeImportFiles(markdownContent string, workspaceDir string) ([] for _, filePath := range paths { // Normalize the path to be relative to .github folder normalizedPath := filePath - if strings.HasPrefix(normalizedPath, ".github/") { - normalizedPath = normalizedPath[8:] // Remove ".github/" + if strings.HasPrefix(normalizedPath, constants.GithubDir) { + normalizedPath = normalizedPath[len(constants.GithubDir):] // Remove ".github/" } else if strings.HasPrefix(normalizedPath, ".github\\") { normalizedPath = normalizedPath[8:] // Remove ".github\" (Windows) } diff --git a/pkg/workflow/safe_jobs.go b/pkg/workflow/safe_jobs.go index f4549111ced..7a2c89d1928 100644 --- a/pkg/workflow/safe_jobs.go +++ b/pkg/workflow/safe_jobs.go @@ -241,7 +241,7 @@ func (c *Compiler) buildSafeJobs(data *WorkflowData, threatDetectionEnabled bool agentArtifactPrefix := artifactPrefixExprForAgentDownstreamJob(data) downloadSteps := buildArtifactDownloadSteps(ArtifactDownloadConfig{ ArtifactName: agentArtifactPrefix + constants.AgentArtifactName, - DownloadPath: "${{ runner.temp }}/gh-aw/safe-jobs/", + DownloadPath: SafeJobsDownloadDirExpr, SetupEnvStep: false, // We'll handle env vars separately to add job-specific ones StepName: "Download agent output artifact", }, c.getActionPin) @@ -255,7 +255,7 @@ func (c *Compiler) buildSafeJobs(data *WorkflowData, threatDetectionEnabled bool // GH_AW_AGENT_OUTPUT uses the runner.temp Actions expression so the path is // resolved by the runner without requiring a $GITHUB_OUTPUT write. setupEnvVars := map[string]string{ - "GH_AW_AGENT_OUTPUT": "${{ runner.temp }}/gh-aw/safe-jobs/" + constants.AgentOutputFilename, + "GH_AW_AGENT_OUTPUT": SafeJobsDownloadDirExpr + constants.AgentOutputFilename, } // All job-specific env vars (literal or expression-based) are injected with // their original values. Nothing goes through $GITHUB_OUTPUT. diff --git a/pkg/workflow/safe_outputs_config_generation.go b/pkg/workflow/safe_outputs_config_generation.go index fb559eed388..f8a4cbf9797 100644 --- a/pkg/workflow/safe_outputs_config_generation.go +++ b/pkg/workflow/safe_outputs_config_generation.go @@ -6,6 +6,7 @@ import ( "sort" "strings" + "github.com/github/gh-aw/pkg/constants" "github.com/github/gh-aw/pkg/sliceutil" "github.com/github/gh-aw/pkg/stringutil" ) @@ -181,7 +182,7 @@ func generateSafeOutputsConfig(data *WorkflowData) (string, error) { for _, memory := range data.RepoMemoryConfig.Memories { memories = append(memories, map[string]any{ "id": memory.ID, - "dir": "/tmp/gh-aw/repo-memory/" + memory.ID, + "dir": constants.TmpRepoMemoryDir + memory.ID, "max_file_size": memory.MaxFileSize, "max_patch_size": memory.MaxPatchSize, "max_file_count": memory.MaxFileCount, diff --git a/pkg/workflow/safe_outputs_steps.go b/pkg/workflow/safe_outputs_steps.go index 57b8abad0c3..a27c157ad09 100644 --- a/pkg/workflow/safe_outputs_steps.go +++ b/pkg/workflow/safe_outputs_steps.go @@ -265,7 +265,7 @@ func buildAgentOutputDownloadSteps(prefix string, pinAction func(string) string) return buildArtifactDownloadSteps(ArtifactDownloadConfig{ ArtifactName: prefix + constants.AgentArtifactName, // Unified agent artifact (prefixed in workflow_call) ArtifactFilename: constants.AgentOutputFilename, // Filename inside the artifact directory - DownloadPath: "/tmp/gh-aw/", + DownloadPath: constants.TmpGhAwDirSlash, SetupEnvStep: true, EnvVarName: "GH_AW_AGENT_OUTPUT", StepName: "Download agent output artifact", diff --git a/pkg/workflow/setup_action_paths.go b/pkg/workflow/setup_action_paths.go index 620404ae299..8e329f1c016 100644 --- a/pkg/workflow/setup_action_paths.go +++ b/pkg/workflow/setup_action_paths.go @@ -26,3 +26,11 @@ const GhAwBinaryPath = constants.GhAwRootDirShell + "/gh-aw" // SafeJobsDownloadDir is the directory for safe job files on the runner const SafeJobsDownloadDir = constants.GhAwRootDirShell + "/safe-jobs/" + +// SafeJobsDownloadDirExpr is SafeJobsDownloadDir in Actions expression form. +// Use in YAML `with:` fields and `env:` values that need GitHub Actions expression resolution. +const SafeJobsDownloadDirExpr = constants.GhAwRootDir + "/safe-jobs/" + +// SafeOutputsUploadArtifactsDir is the upload-artifacts staging directory in shell env var form. +// Used as a read-write container mount for safe_outputs upload_artifact configurations. +const SafeOutputsUploadArtifactsDir = SafeOutputsDirShell + "/upload-artifacts" diff --git a/pkg/workflow/step_order_validation.go b/pkg/workflow/step_order_validation.go index d47c36774a5..9c3069194b6 100644 --- a/pkg/workflow/step_order_validation.go +++ b/pkg/workflow/step_order_validation.go @@ -6,6 +6,8 @@ import ( "path/filepath" "slices" "strings" + + "github.com/github/gh-aw/pkg/constants" ) var stepOrderLog = newValidationLogger("step_order") @@ -188,7 +190,7 @@ func isPathScannedBySecretRedaction(path string) bool { // Accept both literal paths and environment variable references. // Engines that produce output outside /tmp/gh-aw/ must move their files into /tmp/gh-aw/ // via GetPreBundleSteps before the unified artifact upload (see gemini_engine.go). - if !strings.HasPrefix(path, "/tmp/gh-aw/") && !strings.HasPrefix(path, "${RUNNER_TEMP}/gh-aw/") && !strings.HasPrefix(path, "${{ runner.temp }}/gh-aw/") { + if !strings.HasPrefix(path, constants.TmpGhAwDirSlash) && !strings.HasPrefix(path, constants.GhAwRootDirShellSlash) && !strings.HasPrefix(path, constants.GhAwRootDirSlash) { // Check if it's an environment variable that might resolve to /tmp/gh-aw/ or ${RUNNER_TEMP}/gh-aw/ // For now, we'll allow ${{ env.* }} patterns through as we can't resolve them at compile time // Assume environment variables that might contain /tmp/gh-aw or ${RUNNER_TEMP}/gh-aw paths are safe diff --git a/pkg/workflow/threat_detection.go b/pkg/workflow/threat_detection.go index fff53a14d83..5723a71d723 100644 --- a/pkg/workflow/threat_detection.go +++ b/pkg/workflow/threat_detection.go @@ -744,7 +744,7 @@ func (c *Compiler) buildDetectionEngineExecutionStep(data *WorkflowData) []strin } } - logFile := "/tmp/gh-aw/threat-detection/detection.log" + logFile := constants.ThreatDetectionLogPath executionSteps := engine.GetExecutionSteps(threatDetectionData, logFile) for _, step := range executionSteps { for i, line := range step {