@@ -192,7 +192,7 @@ def _call_shell_app(
192192 out_stream : TextIO = sys .stdout ,
193193 err_stream : TextIO = sys .stderr ,
194194 exit_on_error : bool = True ,
195- ) -> None :
195+ ) -> bool :
196196 self ._load_shell_app ()
197197
198198 fd , filepath = tempfile .mkstemp (suffix = self .tmp_suffix , text = True )
@@ -224,6 +224,7 @@ def _call_shell_app(
224224 e .exit_code or 1 ,
225225 exit_on_error = exit_on_error ,
226226 )
227+ return False
227228 except KeyboardInterrupt :
228229 os .close (fd )
229230 pid = p .pid
@@ -233,10 +234,11 @@ def _call_shell_app(
233234 MakimError .SH_KEYBOARD_INTERRUPT ,
234235 )
235236 os .close (fd )
237+ return True
236238
237239 def _call_shell_remote (
238240 self , cmd : str , host_config : dict [str , Any ], exit_on_error : bool = True
239- ) -> None :
241+ ) -> bool :
240242 try :
241243 # Render the host configuration values
242244 env , _ = self ._load_scoped_data ('task' )
@@ -266,25 +268,30 @@ def _call_shell_remote(
266268 MakimError .SSH_EXECUTION_ERROR ,
267269 exit_on_error = exit_on_error ,
268270 )
271+ return False
269272
270273 ssh .close ()
274+ return True
271275 except paramiko .AuthenticationException :
272276 MakimLogs .raise_error (
273277 f'Authentication failed for host { host_config ["host" ]} ' ,
274278 MakimError .SSH_AUTHENTICATION_FAILED ,
275279 )
280+ return False
276281 except paramiko .SSHException as ssh_exception :
277282 MakimLogs .raise_error (
278283 f'SSH error: { ssh_exception !s} ' ,
279284 MakimError .SSH_CONNECTION_ERROR ,
280285 exit_on_error = exit_on_error ,
281286 )
287+ return False
282288 except Exception as e :
283289 MakimLogs .raise_error (
284290 f'Unexpected error during remote execution: { e !s} ' ,
285291 MakimError .SSH_EXECUTION_ERROR ,
286292 exit_on_error = exit_on_error ,
287293 )
294+ return False
288295
289296 def _render_host_config (
290297 self , host_config : dict [str , Any ], env : dict [str , str ]
@@ -914,7 +921,7 @@ def _execute_hook(
914921
915922 def _run_command (
916923 self , args : dict [str , Any ], exit_on_error : bool = True
917- ) -> None :
924+ ) -> bool :
918925 cmd = self .task_data .get ('run' , '' ).strip ()
919926 remote_host = self .task_data .get ('remote' )
920927
@@ -968,7 +975,7 @@ def _run_command(
968975
969976 width , _ = get_terminal_size ()
970977
971- def process_matrix_combination (matrix_vars : dict [str , Any ]) -> None :
978+ def process_matrix_combination (matrix_vars : dict [str , Any ]) -> bool :
972979 # Update environment variables
973980 for k , v in env .items ():
974981 os .environ [k ] = v
@@ -1009,27 +1016,33 @@ def process_matrix_combination(matrix_vars: dict[str, Any]) -> None:
10091016 """ ,
10101017 MakimError .REMOTE_HOST_NOT_FOUND ,
10111018 )
1012- self ._call_shell_remote (
1019+ return self ._call_shell_remote (
10131020 current_cmd ,
10141021 cast (dict [str , Any ], host_config ),
10151022 exit_on_error = exit_on_error ,
10161023 )
10171024 else :
10181025 out_stream , err_stream = self ._get_output_stream ()
1019- self ._call_shell_app (
1026+ return self ._call_shell_app (
10201027 current_cmd ,
10211028 out_stream ,
10221029 err_stream ,
10231030 exit_on_error = exit_on_error ,
10241031 )
1032+ return True
10251033
10261034 # Run command for each matrix combination
1035+ all_success = True
10271036 for matrix_vars in matrix_combinations or [{}]:
1028- process_matrix_combination (matrix_vars )
1037+ if not process_matrix_combination (matrix_vars ):
1038+ all_success = False
1039+ if exit_on_error :
1040+ break
10291041
10301042 # move back the environment variable to the previous values
10311043 os .environ .clear ()
10321044 os .environ .update (self .env_scoped )
1045+ return all_success
10331046
10341047 def _get_output_stream (self ) -> tuple [TextIO , TextIO ]:
10351048 """Set up logging streams based on task log configuration."""
@@ -1088,8 +1101,7 @@ async def _run_with_retry(self, args: dict[str, Any]) -> bool:
10881101
10891102 if not retry_config or retry_count == 1 :
10901103 try :
1091- self ._run_command (args , exit_on_error = False )
1092- return True
1104+ return self ._run_command (args , exit_on_error = False )
10931105 except Exception :
10941106 return False
10951107
@@ -1099,20 +1111,23 @@ async def _run_with_retry(self, args: dict[str, Any]) -> bool:
10991111 MakimLogs .print_info (
11001112 f'[Retry] Attempt { attempt } /{ retry_count } '
11011113 )
1102- self ._run_command (args , exit_on_error = False )
1103- return True
1114+ if self ._run_command (args , exit_on_error = False ):
1115+ return True
11041116 except Exception :
1105- if attempt == retry_count :
1106- MakimLogs .print_warning (
1107- f'[Retry] All { retry_count } retries failed.'
1108- )
1109- return False
1110- attempt += 1
1117+ # Exception caught, will retry or fail based on retry_count
1118+ pass # nosec B110
1119+
1120+ if attempt == retry_count :
11111121 MakimLogs .print_warning (
1112- f'[Retry] Attempt { attempt } /{ retry_count } .'
1113- f' Retrying in { delay } seconds...' ,
1122+ f'[Retry] All { retry_count } retries failed.'
11141123 )
1115- await asyncio .sleep (delay )
1124+ return False
1125+ attempt += 1
1126+ MakimLogs .print_warning (
1127+ f'[Retry] Attempt { attempt } /{ retry_count } .'
1128+ f' Retrying in { delay } seconds...' ,
1129+ )
1130+ await asyncio .sleep (delay )
11161131 return False
11171132
11181133 # public methods
@@ -1134,7 +1149,20 @@ def load(
11341149 self .env = self ._load_dotenv (self .global_data )
11351150
11361151 def run (self , args : dict [str , Any ]) -> bool :
1137- """Run makim task code."""
1152+ """Run makim task code.
1153+
1154+ Returns
1155+ -------
1156+ bool
1157+ True if the task completed successfully or if ignore-errors is
1158+ enabled, False otherwise.
1159+
1160+ Note
1161+ ----
1162+ API Change: This method now returns bool instead of None. This change
1163+ enables proper failure hook execution and ignore-errors functionality.
1164+ External callers should be updated to handle the boolean return value.
1165+ """
11381166 self .args = args
11391167
11401168 # setup
@@ -1170,7 +1198,7 @@ def run(self, args: dict[str, Any]) -> bool:
11701198 if retry :
11711199 success = asyncio .run (self ._run_with_retry (args ))
11721200 else :
1173- self ._run_command (
1201+ success = self ._run_command (
11741202 args , exit_on_error = not (failure_hook or ignore_errors )
11751203 )
11761204 except Exception :
@@ -1179,7 +1207,9 @@ def run(self, args: dict[str, Any]) -> bool:
11791207 if not success and failure_hook :
11801208 self ._run_hooks (args , 'failure' )
11811209
1182- if success or ignore_errors :
1210+ # Run post-run hooks if task succeeded, or no failure hook exists, or
1211+ # ignoring errors
1212+ if success or not failure_hook or ignore_errors :
11831213 self ._run_hooks (args , 'post-run' )
11841214
11851215 return success or ignore_errors
0 commit comments