Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
12 changes: 10 additions & 2 deletions src/ai/backend/install/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ def compose(self) -> ComposeResult:
yield Label("Development Setup", classes="mode-title")
with TabbedContent():
with TabPane("Install Log", id="tab-dev-log"):
yield SetupLog(wrap=True, classes="log")
yield SetupLog(
wrap=True,
classes="log",
non_interactive=self._non_interactive,
)
with TabPane("Install Report", id="tab-dev-report"):
yield Label("Installation is not complete.")

Expand Down Expand Up @@ -115,7 +119,11 @@ def compose(self) -> ComposeResult:
yield Label("Package Setup", classes="mode-title")
with TabbedContent():
with TabPane("Install Log", id="tab-pkg-log"):
yield SetupLog(wrap=True, classes="log")
yield SetupLog(
wrap=True,
classes="log",
non_interactive=self._non_interactive,
)
with TabPane("Install Report", id="tab-pkg-report"):
yield Label("Installation is not complete.")

Expand Down
47 changes: 45 additions & 2 deletions src/ai/backend/install/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import asyncio
from pathlib import Path

from rich.console import ConsoleRenderable
from rich.console import Console, ConsoleRenderable
from rich.text import Text
from rich.traceback import Traceback
from textual import on
from textual.app import ComposeResult
from textual.binding import Binding
Expand Down Expand Up @@ -44,9 +45,51 @@ class SetupLog(RichLog):
Binding("enter", "continue", show=False),
]

def __init__(self, *args, **kwargs) -> None:
def __init__(
self,
*args,
non_interactive: bool = False,
**kwargs,
) -> None:
super().__init__(*args, **kwargs)
self._continue = asyncio.Event()
self._non_interactive = non_interactive
self._stdout_console: Console | None = None
if self._non_interactive:
self._stdout_console = Console(force_terminal=True)

def _write_to_stdout(self, content: object) -> None:
"""Write content to stdout via Rich Console."""
if self._stdout_console is None:
return
try:
if isinstance(content, (str, Text, Traceback)):
self._stdout_console.print(content)
elif isinstance(content, Exception):
self._stdout_console.print(f"[red]Error: {content}[/red]")
self._stdout_console.print_exception()
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

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

When handling Exception objects, print_exception() is called without arguments, which prints the currently active exception from sys.exc_info(). However, the Exception object passed to this method may not be the currently active exception. Consider passing the exception object explicitly to print_exception() or using print() with the exception object directly, which would be more consistent with the error message on line 69.

Suggested change
self._stdout_console.print_exception()
tb = getattr(content, "__traceback__", None)
if tb is not None:
self._stdout_console.print(Traceback.from_exception(type(content), content, tb))
else:
# Fallback to the current exception if no traceback is attached
self._stdout_console.print_exception()

Copilot uses AI. Check for mistakes.
else:
self._stdout_console.print(content)
except Exception:
pass
Comment on lines +73 to +74
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

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

The bare except clause silently suppresses all exceptions that might occur during stdout writing. While this might be intentional to prevent the logging from crashing the application, it makes debugging difficult when stdout logging fails. Consider at least logging the exception to stderr or using a more specific exception type, or add a comment explaining why exceptions must be suppressed here.

Copilot uses AI. Check for mistakes.

def write(
self,
content: object,
width: int | None = None,
expand: bool = False,
shrink: bool = True,
scroll_end: bool | None = None,
) -> SetupLog:
"""Write content to the log and optionally to stdout."""
# Always write to the RichLog widget
super().write(content, width=width, expand=expand, shrink=shrink, scroll_end=scroll_end)

# Write to stdout if non-interactive mode
if self._non_interactive:
self._write_to_stdout(content)

return self

async def wait_continue(self) -> None:
"""
Expand Down
Loading