@@ -107,34 +107,46 @@ pub const Executor = struct {
107107 var attempt : u32 = 0 ;
108108 while (attempt < self .cfg .max_retries ) : (attempt += 1 ) {
109109 if (attempt > 0 ) {
110- self .log .statusFmt ("[Cycle {d}] Evaluating ( retry {d}/{d})... " , .{ cycle , attempt + 1 , self .cfg .max_retries });
110+ self .log .statusFmt ("[Cycle {d}] Evaluation retry {d}/{d}" , .{ cycle , attempt + 1 , self .cfg .max_retries });
111111 }
112112
113113 const result = self .runOpencode (self .cfg .planning_model , title , prompt , false );
114114 if (result ) | output | {
115115 // Check for COMPLETE or NEEDS_WORK in output
116116 if (std .mem .indexOf (u8 , output , "COMPLETE" ) != null ) {
117+ self .log .logFmt ("[Cycle {d}] Evaluation result: COMPLETE" , .{cycle });
117118 self .allocator .free (output );
118119 return "COMPLETE" ;
119120 } else if (std .mem .indexOf (u8 , output , "NEEDS_WORK" ) != null ) {
121+ self .log .logFmt ("[Cycle {d}] Evaluation result: NEEDS_WORK" , .{cycle });
120122 self .allocator .free (output );
121123 return "NEEDS_WORK" ;
124+ } else {
125+ self .log .logErrorFmt ("[Cycle {d}] Evaluation returned unexpected response" , .{cycle });
126+ self .log .logError (" Expected: 'COMPLETE' or 'NEEDS_WORK'" );
127+ self .log .logError (" Hint: The evaluation model may need more specific instructions" );
122128 }
123129 self .allocator .free (output );
124- } else | _ | {
125- // Error running opencode
130+ } else | err | {
131+ self . log . logErrorFmt ( "[Cycle {d}] Evaluation attempt {d} failed: {s}" , .{ cycle , attempt + 1 , @errorName ( err ) });
126132 }
127133
128134 // Backoff before retry
129135 if (attempt + 1 < self .cfg .max_retries ) {
130136 const sleep_time = self .cfg .backoff_base * std .math .pow (u32 , 2 , attempt );
131- self .log .statusFmt ("[Cycle {d}] Retrying evaluation in {d}s..." , .{ cycle , sleep_time });
137+ self .log .statusFmt ("[Cycle {d}] Waiting {d}s before retry ..." , .{ cycle , sleep_time });
132138 std .Thread .sleep (@as (u64 , sleep_time ) * std .time .ns_per_s );
133139 }
134140 }
135141
136142 // Default to NEEDS_WORK if we couldn't determine
137- self .log .logErrorFmt ("Failed to get evaluation result after {d} attempts (cycle {d}), defaulting to NEEDS_WORK" , .{ self .cfg .max_retries , cycle });
143+ self .log .logError ("" );
144+ self .log .logErrorFmt ("[Cycle {d}] Failed to get evaluation result after {d} attempts" , .{ cycle , self .cfg .max_retries });
145+ self .log .logError (" Defaulting to NEEDS_WORK to continue safely" );
146+ self .log .logError (" Possible causes:" );
147+ self .log .logError (" - Model API unavailable or rate limited" );
148+ self .log .logError (" - Evaluation prompt not producing expected output" );
149+ self .log .logError (" - Network connectivity issues" );
138150 return "NEEDS_WORK" ;
139151 }
140152
@@ -152,21 +164,35 @@ pub const Executor = struct {
152164
153165 while (attempt < self .cfg .max_retries ) : (attempt += 1 ) {
154166 if (attempt > 0 ) {
155- self .log .logFmt ("Attempt {d}/{d}" , .{ attempt + 1 , self .cfg .max_retries });
167+ self .log .logFmt ("Retry attempt {d}/{d}" , .{ attempt + 1 , self .cfg .max_retries });
156168 }
157169
158170 const result = self .runOpencode (model , title , prompt , continue_session );
159171 if (result ) | output | {
160172 self .allocator .free (output );
161173 return .success ;
162174 } else | err | {
163- self .log .logErrorFmt ("opencode failed (attempt {d}/{d}): {s} with model '{s}'" , .{ attempt + 1 , self .cfg .max_retries , @errorName (err ), model });
175+ self .log .logErrorFmt ("OpenCode execution failed (attempt {d}/{d})" , .{ attempt + 1 , self .cfg .max_retries });
176+ self .log .logErrorFmt (" Model: {s}" , .{model });
177+ self .log .logErrorFmt (" Error: {s}" , .{@errorName (err )});
178+
179+ if (attempt + 1 == self .cfg .max_retries ) {
180+ // Last attempt, provide detailed troubleshooting
181+ self .log .logError ("" );
182+ self .log .logError ("All retry attempts exhausted. Troubleshooting tips:" );
183+ self .log .logError (" 1. Verify 'opencode' CLI is installed: which opencode" );
184+ self .log .logError (" 2. Check if opencode works directly: opencode --version" );
185+ self .log .logError (" 3. Verify model is available: opencode models list" );
186+ self .log .logError (" 4. Check API credentials are configured properly" );
187+ self .log .logError (" 5. Review network connectivity and API rate limits" );
188+ self .log .logErrorFmt (" 6. Try increasing OPENCODER_MAX_RETRIES (current: {d})" , .{self .cfg .max_retries });
189+ }
164190 }
165191
166192 // Backoff before retry
167193 if (attempt + 1 < self .cfg .max_retries ) {
168194 const sleep_time = self .cfg .backoff_base * std .math .pow (u32 , 2 , attempt );
169- self .log .logFmt ("Retrying in {d}s..." , .{sleep_time });
195+ self .log .logFmt ("Waiting {d}s before retry ..." , .{sleep_time });
170196 std .Thread .sleep (@as (u64 , sleep_time ) * std .time .ns_per_s );
171197 }
172198 }
@@ -228,16 +254,34 @@ pub const Executor = struct {
228254 if (code == 0 ) {
229255 return stdout_list .toOwnedSlice (self .allocator );
230256 }
231- self .log .logErrorFmt ("opencode exited with code {d} (model: {s}, title: {s})" , .{ code , model , title });
257+ self .log .logErrorFmt ("OpenCode process exited with non-zero status" , .{});
258+ self .log .logErrorFmt (" Exit code: {d}" , .{code });
259+ self .log .logErrorFmt (" Model: {s}" , .{model });
260+ self .log .logErrorFmt (" Title: {s}" , .{title });
261+
262+ // Provide context based on exit code
263+ if (code == 1 ) {
264+ self .log .logError (" Common causes: Invalid arguments, API authentication failure" );
265+ } else if (code == 2 ) {
266+ self .log .logError (" Common causes: Model not found, invalid model specification" );
267+ } else if (code >= 126 and code <= 127 ) {
268+ self .log .logError (" Common causes: opencode CLI not found or not executable" );
269+ self .log .logError (" Hint: Verify installation with 'which opencode'" );
270+ }
232271 },
233272 .Signal = > | sig | {
234- self .log .logErrorFmt ("opencode terminated by signal {d} (model: {s}, title: {s})" , .{ sig , model , title });
273+ self .log .logErrorFmt ("OpenCode process terminated by signal {d}" , .{sig });
274+ self .log .logErrorFmt (" Model: {s}" , .{model });
275+ self .log .logError (" This usually indicates the process was killed externally" );
276+ self .log .logError (" Hint: Check system resources (memory, CPU) and logs" );
235277 },
236278 .Stopped = > | sig | {
237- self .log .logErrorFmt ("opencode stopped by signal {d} (model: {s}, title: {s})" , .{ sig , model , title });
279+ self .log .logErrorFmt ("OpenCode process stopped by signal {d}" , .{sig });
280+ self .log .logErrorFmt (" Model: {s}" , .{model });
238281 },
239282 .Unknown = > | status | {
240- self .log .logErrorFmt ("opencode terminated with unknown status {d} (model: {s}, title: {s})" , .{ status , model , title });
283+ self .log .logErrorFmt ("OpenCode process terminated with unknown status {d}" , .{status });
284+ self .log .logErrorFmt (" Model: {s}" , .{model });
241285 },
242286 }
243287
@@ -258,6 +302,7 @@ fn createTestLogger(allocator: Allocator) !*Logger {
258302
259303 try std .fs .cwd ().makePath (temp_dir );
260304
305+ const main_log_path = try std .fs .path .join (allocator , &.{ temp_dir , "main.log" });
261306 const cycle_log_dir = try std .fs .path .join (allocator , &.{ temp_dir , "cycles" });
262307 const alerts_file = try std .fs .path .join (allocator , &.{ temp_dir , "alerts.log" });
263308
@@ -266,6 +311,7 @@ fn createTestLogger(allocator: Allocator) !*Logger {
266311 const logger_ptr = try allocator .create (Logger );
267312 logger_ptr .* = Logger {
268313 .main_log = null ,
314+ .main_log_path = main_log_path ,
269315 .cycle_log_dir = cycle_log_dir ,
270316 .alerts_file = alerts_file ,
271317 .cycle = 0 ,
@@ -281,6 +327,7 @@ fn destroyTestLogger(logger_ptr: *Logger, allocator: Allocator) void {
281327 const temp_base = std .fs .path .dirname (logger_ptr .cycle_log_dir ) orelse "/tmp" ;
282328 std .fs .cwd ().deleteTree (temp_base ) catch {};
283329
330+ allocator .free (logger_ptr .main_log_path );
284331 allocator .free (logger_ptr .cycle_log_dir );
285332 allocator .free (logger_ptr .alerts_file );
286333 allocator .destroy (logger_ptr );
0 commit comments