Skip to content

Fix: handle OSError from os.get_terminal_size() in CLI table rendering for non-TTY environments #2599

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

vuyelwadr
Copy link
Contributor

Summary

This PR fixes a bug in the dstack CLI where running commands like dstack ps in a non-interactive environment (subprocess, PTY, or script) would crash with:

Traceback (most recent call last):
File "/Users/x/.local/bin/dstack", line 10, in
sys.exit(main())
File "/Users/x/.local/share/uv/tools/dstack/lib/python3.10/site-packages/dstack/_internal/cli/main.py", line 87, in main
args.func(args)
File "/Users/x/.local/share/uv/tools/dstack/lib/python3.10/site-packages/dstack/_internal/cli/commands/ps.py", line 44, in _command
console.print(run_utils.get_runs_table(runs, verbose=args.verbose))
File "/Users/x/.local/share/uv/tools/dstack/lib/python3.10/site-packages/dstack/_internal/cli/utils/run.py", line 152, in get_runs_table
table = Table(box=None, expand=os.get_terminal_size()[0] <= 110)
OSError: [Errno 25] Inappropriate ioctl for device

The error was caused by unguarded calls to os.get_terminal_size() in the CLI’s table rendering code. This patch wraps those calls in a try/except block and uses a default width (120) if the call fails, ensuring the CLI works in both TTY and non-TTY environments.

Details

  • All uses of os.get_terminal_size() are now wrapped in try/except.
  • If an OSError is raised, a default width of 120 is used.
  • This prevents crashes when running dstack in automation, CI, or as a subprocess.

Steps to Reproduce

  1. Run dstack ps in a Python subprocess or PTY (not a real terminal).
  2. Observe the crash with OSError.
  3. With this patch, the command works and outputs as expected.

Why this is needed

  • Many users want to automate or monitor dstack runs from scripts or other tools.
  • The CLI should not crash in non-interactive environments.

Related Issue


Thank you for reviewing!

@@ -95,7 +95,11 @@ def th(s: str) -> str:
props.add_row(th("Inactivity duration"), inactivity_duration)
props.add_row(th("Reservation"), run_spec.configuration.reservation or "-")

offers = Table(box=None, expand=os.get_terminal_size()[0] <= 110)
try:
width = os.get_terminal_size()[0]
Copy link
Contributor

Choose a reason for hiding this comment

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

IMO, it's better to use something like this:

import shutil
table = Table(box=None, expand=shutil.get_terminal_size(fallback=(120, 40)).columns <= 110)

@peterschmidt85 peterschmidt85 merged commit e420791 into dstackai:master May 4, 2025
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants