Repository: ClickHouse/ClickHouse
File: ci/praktika/utils.py (Python)

Code before:
                        for err in retry_errors:
                            if any(err in err_line for err_line in err_output):
                                print(
                                    f"Retryable error occurred: [{err}], [{retry+1}/{retries}]"
                                )
                                should_retry = True
                                break
                        if not should_retry:
                            print(
                                f"No retryable errors found, stopping retry attempts"
                            )
                            break
                    if should_retry and retry < retries - 1:
                        delay = min(2 ** (retry + 1), 60)
                        print(f"Retrying in {delay}s...")
                        time.sleep(delay)
            except Exception as e:
                if verbose:
                    if retries == 1:
                        print(
                            f"ERROR: command failed, exit code: {proc.returncode}"
                        )
                    else:
                        print(
                            f"Retry {retry+1}/{retries}: command failed, exit code: {proc.returncode}"
                        )
                if proc:
                    proc.kill()
                if strict and retry == retries - 1:
                    raise e
            if retry_sleep:
                time.sleep(retry_sleep)

        if strict and (not proc or proc.returncode != 0):
            err = "\n   ".join(err_output).strip()
            raise RuntimeError(
                f"command failed, exit code {proc.returncode},\nstderr:\n>>>\n{err}\n<<<"
            )

        return proc.returncode if proc else 1  # Return 1 if the process never started

    @classmethod
    def run_async(
        cls,
        command,
        stdin_str=None,
        verbose=False,
        suppress_output=False,
        **kwargs,
    ):
        if verbose:

Review comment (suggestion):
In the exception handler, the code attempts to access `proc.returncode` but if the exception occurred during process creation or early in execution, `proc` might be None or not have a returncode. This could cause an AttributeError when trying to print the error message. Consider checking if proc exists and has a returncode before accessing it, or use a fallback message.

Code after:
                    )

                if not retry_errors:
                    continue  # No retry errors specified, just retry on any failure

                if not any(
                    err in err_line for err_line in err_output for err in retry_errors
                ):
                    if verbose:
                        print(f"No retryable errors found, stopping retries")
                    break

                if verbose:
                    matched = next(
                        (
                            err
                            for err in retry_errors
                            if any(err in line for line in err_output)
                        ),
                        "",
                    )
                    print(
                        f"Retryable error [{matched}] found, retry {retry+1}/{retries}"
                    )
            except Exception as e:
                if verbose:
                    if retries == 1:
                        print(f"ERROR: exception {e}")
                    else:
                        print(f"Retry {retry+1}/{retries}: exception {e}")
                        if retry == retries - 1:
                            print(f"ERROR: Final attempt failed, no more retries left.")
                if proc:
                    proc.kill()
                if retry == retries - 1:
                    if strict:
                        raise
                    else:
                        return 1  # Return non-zero for failure

        if verbose:
            print(
                f"ERROR: command failed after {retry+1}/{retries} attempt(s), exit code: {proc.returncode}"
            )

        if strict:
            err = "\n   ".join(err_output).strip()
            raise RuntimeError(
                f"command failed, exit code {proc.returncode},\nstderr:\n>>>\n{err}\n<<<"
            )