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 {