Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 78 additions & 15 deletions src/axolotl/cli/art.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,93 @@
"""Axolotl ASCII logo utils."""

import os
import subprocess # nosec B404
from pathlib import Path

import axolotl
from axolotl.utils.distributed import is_main_process

AXOLOTL_LOGO = """
#@@ #@@ @@# @@#
@@ @@ @@ @@ =@@# @@ #@ =@@#.
@@ #@@@@@@@@@ @@ #@#@= @@ #@ .=@@
#@@@@@@@@@@@@@@@@@ =@# @# ##= ## =####=+ @@ =#####+ =#@@###. @@
@@@@@@@@@@/ +@@/ +@@ #@ =@= #@= @@ =@#+ +#@# @@ =@#+ +#@# #@. @@
@@@@@@@@@@ ##@@ ##@@ =@# @# =@# @# @@ @@ @@ @@ #@ #@ @@
@@@@@@@@@@@@@@@@@@@@ #@=+++#@= =@@# @@ @@ @@ @@ #@ #@ @@
=@#=====@@ =@# @# @@ @@ @@ @@ #@ #@ @@
@@@@@@@@@@@@@@@@ @@@@ #@ #@= #@= +@@ #@# =@# @@. =@# =@# #@. @@
=@# @# #@= #@ =#@@@@#= +#@@= +#@@@@#= .##@@+ @@
@@@@ @@@@@@@@@@@@@@@@
"""
GITHUB_URL = "https://github.com/axolotl-ai-cloud/axolotl"

_AXOLOTL_LOGO_HEAD = "\n".join(
[
" ┌─┐┌─┐ ┌─┐┌─┐",
" │ ││ │ │ ││ │ █████ ██ ██ █████ ██ █████ ███████ ██",
" │ └┘ └────┘ └┘ │ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██",
" │ │ ███████ ███ ██ ██ ██ ██ ██ ███ ██",
" │ ◉ ◉ │ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██",
" └──────────────┘ ██ ██ ██ ██ █████ ███████ █████ ███ ███████",
]
)

_BARS_TOP = " ▄▄▄▄▄▄▄▄▄▄ ▄▄▄"
_BARS_BOTTOM = " ▄▄▄ ▄▄▄▄▄▄▄▄▄▄"

_LETTERS_START_COL = len(" └──────────────┘ ")
_LETTERS_END_COL = len(
" └──────────────┘ ██ ██ ██ ██ █████ ███████ █████ ███ ███████"
)

HAS_PRINTED_LOGO = False


def _get_git_info() -> tuple[str | None, str | None]:
"""Return (short_sha, commit_date) or (None, None) when not a git checkout."""
repo_root = Path(axolotl.__file__).resolve().parents[2]
if not (repo_root / ".git").exists():
return None, None
try:
sha = subprocess.check_output( # nosec B603 B607
["git", "-C", str(repo_root), "rev-parse", "--short", "HEAD"],
stderr=subprocess.DEVNULL,
text=True,
).strip()
date = subprocess.check_output( # nosec B603 B607
["git", "-C", str(repo_root), "log", "-1", "--format=%cs"],
stderr=subprocess.DEVNULL,
text=True,
).strip()
return sha or None, date or None
except (subprocess.CalledProcessError, FileNotFoundError, OSError):
Comment on lines +39 to +51

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add a timeout (and handle TimeoutExpired) to _get_git_info() best-effort git probes

subprocess.check_output() calls in src/axolotl/cli/art.py (lines 40 and 45) have no timeout and _get_git_info() doesn’t catch subprocess.TimeoutExpired, so startup can block if git hangs. Add a short timeout to both calls and treat TimeoutExpired like the other fallbacks.

Suggested fix
     try:
         sha = subprocess.check_output(  # nosec B603 B607
             ["git", "-C", str(repo_root), "rev-parse", "--short", "HEAD"],
             stderr=subprocess.DEVNULL,
             text=True,
+            timeout=1,
         ).strip()
         date = subprocess.check_output(  # nosec B603 B607
             ["git", "-C", str(repo_root), "log", "-1", "--format=%cs"],
             stderr=subprocess.DEVNULL,
             text=True,
+            timeout=1,
         ).strip()
         return sha or None, date or None
-    except (subprocess.CalledProcessError, FileNotFoundError, OSError):
+    except (
+        subprocess.CalledProcessError,
+        subprocess.TimeoutExpired,
+        FileNotFoundError,
+        OSError,
+    ):
         return None, None
🧰 Tools
🪛 Ruff (0.15.13)

[error] 40-40: subprocess call: check for execution of untrusted input

(S603)


[error] 41-41: Starting a process with a partial executable path

(S607)


[error] 45-45: subprocess call: check for execution of untrusted input

(S603)


[error] 46-46: Starting a process with a partial executable path

(S607)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/axolotl/cli/art.py` around lines 39 - 51, The _get_git_info() function
calls subprocess.check_output twice without timeouts and doesn't handle
subprocess.TimeoutExpired; add a short timeout (e.g., timeout=1 or 2 seconds) to
both check_output calls and include subprocess.TimeoutExpired in the except
tuple alongside subprocess.CalledProcessError, FileNotFoundError, and OSError so
that a git hang is treated as a fallback and the function returns (None, None)
like other failures.

return None, None


def _centered_under_letters(text: str, *, bars: str = "") -> str:
"""Pad `text` so it sits centered under the AXOLOTL letters, optionally after bars."""
available = _LETTERS_END_COL - _LETTERS_START_COL
pad = max(0, (available - len(text)) // 2)
text_col = _LETTERS_START_COL + pad
gap = max(1, text_col - len(bars))
return bars + " " * gap + text


def _build_logo() -> str:
sha, date = _get_git_info()
parts = [f"v{axolotl.__version__}"]
if sha:
parts.append(sha)
if date:
parts.append(date)
build = " · ".join(parts)
return "\n".join(
[
"",
_AXOLOTL_LOGO_HEAD,
_BARS_TOP,
_centered_under_letters(GITHUB_URL, bars=_BARS_BOTTOM),
_centered_under_letters(build),
"",
]
)


def print_axolotl_text_art():
"""Prints axolotl ASCII art."""

global HAS_PRINTED_LOGO
if HAS_PRINTED_LOGO:
if HAS_PRINTED_LOGO or os.environ.get("AXOLOTL_BANNER_PRINTED"):
return
if is_main_process():
HAS_PRINTED_LOGO = True
print(AXOLOTL_LOGO)
os.environ["AXOLOTL_BANNER_PRINTED"] = "1"
print(_build_logo())
2 changes: 2 additions & 0 deletions src/axolotl/cli/train.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from transformers.hf_argparser import HfArgumentParser

from axolotl.cli.args import TrainerCliArgs
from axolotl.cli.art import print_axolotl_text_art
from axolotl.cli.checks import check_accelerate_default_config, check_user_token
from axolotl.cli.config import (
gpu_capabilities,
Expand Down Expand Up @@ -65,6 +66,7 @@ def do_cli(config: Union[Path, str] = Path("examples/"), **kwargs):
config: Path to `axolotl` config YAML file.
kwargs: Additional keyword arguments to override config file values.
"""
print_axolotl_text_art()
parsed_cfg = load_cfg(config, **kwargs)
parser = HfArgumentParser(TrainerCliArgs)
parsed_cli_args, _ = parser.parse_args_into_dataclasses(
Expand Down
Loading