findAgentLogFile searches for agent logs within the logDir. It uses engine.GetLogFileForParsing() to determine which log file to use: - If GetLogFileForParsing() returns a non-empty value that doesn't point to agent-stdio.log, look for files in the "agent_output" artifact directory (before flattenin
(logDir string, engine workflow.CodingAgentEngine)
| 124 | // |
| 125 | // Returns the first path found and a boolean indicating success. |
| 126 | func findAgentLogFile(logDir string, engine workflow.CodingAgentEngine) (string, bool) { |
| 127 | // Use GetLogFileForParsing to determine which log file to use |
| 128 | logFileForParsing := engine.GetLogFileForParsing() |
| 129 | |
| 130 | // If the engine specifies a log file that isn't the default agent-stdio.log, |
| 131 | // look in the agent_output artifact directory or flattened location |
| 132 | if logFileForParsing != "" && logFileForParsing != defaultAgentStdioLogPath { |
| 133 | // Check for agent_output directory (artifact, before flattening) |
| 134 | agentOutputDir := filepath.Join(logDir, "agent_output") |
| 135 | if fileutil.DirExists(agentOutputDir) { |
| 136 | // Find the first file in this directory |
| 137 | var foundFile string |
| 138 | if walkErr := filepath.Walk(agentOutputDir, func(path string, info os.FileInfo, err error) error { |
| 139 | if err != nil { |
| 140 | logsUtilsLog.Printf("walk error at %s: %v", path, err) |
| 141 | return nil |
| 142 | } |
| 143 | if info == nil { |
| 144 | return nil |
| 145 | } |
| 146 | if !info.IsDir() && foundFile == "" { |
| 147 | foundFile = path |
| 148 | return errWalkStop |
| 149 | } |
| 150 | return nil |
| 151 | }); walkErr != nil && !errors.Is(walkErr, errWalkStop) { |
| 152 | fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("filesystem error walking %s: %v", agentOutputDir, walkErr))) |
| 153 | } |
| 154 | if foundFile != "" { |
| 155 | return foundFile, true |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | // Check for flattened location (after flattening) |
| 160 | // The engine's log path is absolute (e.g., /tmp/gh-aw/sandbox/agent/logs/ for a |
| 161 | // directory, or /tmp/gh-aw/pi-streaming.jsonl for a direct file). |
| 162 | // After flattening, it's at logDir/<relative-path>. |
| 163 | // Strip /tmp/gh-aw/ prefix to get the relative path |
| 164 | const tmpGhAwPrefix = "/tmp/gh-aw/" |
| 165 | if after, ok := strings.CutPrefix(logFileForParsing, tmpGhAwPrefix); ok { |
| 166 | relPath := after |
| 167 | flattenedPath := filepath.Join(logDir, relPath) |
| 168 | logsUtilsLog.Printf("Checking flattened location for logs: %s", flattenedPath) |
| 169 | // Case 1: the engine log path points to a specific file (e.g. pi-streaming.jsonl) |
| 170 | if fileutil.FileExists(flattenedPath) { |
| 171 | logsUtilsLog.Printf("Found engine log file at flattened path: %s", flattenedPath) |
| 172 | return flattenedPath, true |
| 173 | } |
| 174 | // Case 2: the engine log path points to a directory — walk it for known log formats |
| 175 | if fileutil.DirExists(flattenedPath) { |
| 176 | // Prefer events.jsonl (structured Copilot session format) over debug .log files. |
| 177 | // Walk the full tree: stop immediately when events.jsonl is found (preferred), |
| 178 | // but keep walking after a .log match in case events.jsonl appears later. |
| 179 | var foundEventsJsonl, foundLogFile string |
| 180 | if walkErr := filepath.Walk(flattenedPath, func(path string, info os.FileInfo, err error) error { |
| 181 | if err != nil { |
| 182 | logsUtilsLog.Printf("walk error at %s: %v", path, err) |
| 183 | return nil |