@@ -181,27 +181,39 @@ func (e *CommandExecutor) Execute(ctx context.Context, step PlanStep) (PlanObser
181181 observation .Details = runErr .Error ()
182182 }
183183
184- // If the command failed, persist a detailed failure report for inspection.
185- if runErr != nil {
186- _ = writeFailureLog (step , stdout , stderr , runErr )
187- }
188-
189184 duration := time .Since (start )
190- e .metrics .RecordCommandExecution (step .ID , duration , runErr == nil )
185+
186+ // If the command failed, persist a detailed failure report for inspection.
191187 if runErr != nil {
188+ if err := writeFailureLog (step , stdout , stderr , runErr ); err != nil {
189+ // Log warning but don't fail execution - failure logging is best-effort
190+ e .logger .Warn (ctx , "Failed to write failure log" ,
191+ Field ("step_id" , step .ID ),
192+ Field ("error" , err .Error ()),
193+ )
194+ }
195+ e .metrics .RecordCommandExecution (step .ID , duration , false )
192196 e .logger .Error (ctx , "Command execution failed" , runErr ,
193197 Field ("step_id" , step .ID ),
194198 Field ("shell" , step .Command .Shell ),
195199 Field ("duration_ms" , duration .Milliseconds ()),
196200 )
197- } else {
198- e .logger .Debug (ctx , "Command execution completed" ,
199- Field ("step_id" , step .ID ),
200- Field ("duration_ms" , duration .Milliseconds ()),
201- )
201+ // Return error with step context
202+ if exitErr == nil {
203+ return observation , fmt .Errorf ("command[%s]: execution failed: %w" , step .ID , runErr )
204+ }
205+ // Exit errors include exit code in the wrapped error
206+ return observation , fmt .Errorf ("command[%s]: exited with code %d: %w" , step .ID , * observation .ExitCode , runErr )
202207 }
203208
204- return observation , runErr
209+ e .metrics .RecordCommandExecution (step .ID , duration , true )
210+ e .logger .Debug (ctx , "Command execution completed" ,
211+ Field ("step_id" , step .ID ),
212+ Field ("duration_ms" , duration .Milliseconds ()),
213+ )
214+
215+ // Success - no error to return
216+ return observation , nil
205217}
206218
207219// writeFailureLog persists a diagnostic file under .goagent/ whenever a command
@@ -266,26 +278,41 @@ func writeFailureLog(step PlanStep, fullStdout, fullStderr []byte, runErr error)
266278 _ , _ = b .Write ([]byte ("\n " ))
267279 }
268280
269- return os .WriteFile (path , b .Bytes (), 0o644 )
281+ if err := os .WriteFile (path , b .Bytes (), 0o644 ); err != nil {
282+ return fmt .Errorf ("writeFailureLog: failed to write file %q: %w" , path , err )
283+ }
284+ return nil
270285}
271286
272287func (e * CommandExecutor ) executeInternal (ctx context.Context , step PlanStep ) (PlanObservationPayload , error ) {
273288 invocation , err := parseInternalInvocation (step )
274289 if err != nil {
275- return PlanObservationPayload {}, fmt .Errorf ("command: %w" , err )
290+ e .logger .Error (ctx , "Failed to parse internal command invocation" , err ,
291+ Field ("step_id" , step .ID ),
292+ Field ("command_run" , step .Command .Run ),
293+ )
294+ return PlanObservationPayload {}, fmt .Errorf ("command[%s]: parse internal invocation: %w" , step .ID , err )
276295 }
277296
278297 handler , ok := e .internal [invocation .Name ]
279298 if ! ok {
280- return PlanObservationPayload {}, fmt .Errorf ("command: unknown internal command %q" , invocation .Name )
299+ e .logger .Error (ctx , "Unknown internal command" , nil ,
300+ Field ("step_id" , step .ID ),
301+ Field ("command_name" , invocation .Name ),
302+ )
303+ return PlanObservationPayload {}, fmt .Errorf ("command[%s]: unknown internal command %q" , step .ID , invocation .Name )
281304 }
282305
283306 payload , execErr := handler (ctx , invocation )
284307 if execErr != nil {
308+ e .logger .Error (ctx , "Internal command execution failed" , execErr ,
309+ Field ("step_id" , step .ID ),
310+ Field ("command_name" , invocation .Name ),
311+ )
285312 if payload .Details == "" {
286313 payload .Details = execErr .Error ()
287314 }
288- return payload , execErr
315+ return payload , fmt . Errorf ( "command[%s]: internal command %q failed: %w" , step . ID , invocation . Name , execErr )
289316 }
290317 if payload .ExitCode == nil {
291318 zero := 0
@@ -298,7 +325,7 @@ func parseInternalInvocation(step PlanStep) (InternalCommandRequest, error) {
298325 run := strings .TrimSpace (step .Command .Run )
299326 tokens , err := tokenizeInternalCommand (run )
300327 if err != nil {
301- return InternalCommandRequest {}, err
328+ return InternalCommandRequest {}, fmt . Errorf ( "parse internal command %q: %w" , run , err )
302329 }
303330 if len (tokens ) == 0 {
304331 return InternalCommandRequest {}, errors .New ("internal command: missing command name" )
0 commit comments