Files
cs249r_book/tinytorch/tito/core/runtime.py
Vijay Janapa Reddi f0de9f970e fix(tito): reliably sync CLI progress to the community dashboard (#1849)
The dashboard did not reflect CLI progress for users across Windows and
Ubuntu. Three independent client defects, now fixed:

1. Automatic sync was silently skipped on any non-TTY shell. _trigger_submission
   gated on `not sys.stdin.isatty()`, which is False on Windows Git Bash/MinTTY
   and many IDE terminals even when interactive, so `tito module complete`
   updated local progress.json but never uploaded, with no message. Decouple
   "should we sync" (logged-in and not CI) from "should we prompt" (needs a
   TTY): non-interactive real users now sync without a prompt instead of being
   skipped.

2. A 2xx response with synced_modules null/0 was reported as success using the
   local count, hiding backend failures. sync_progress now returns a SyncResult
   and reports an honest accepted-but-unconfirmed warning when the server does
   not confirm persistence.

3. No standalone resync path and login did not sync, so modules completed before
   logging in never reached the dashboard. Add `tito community sync` and an
   offer to sync existing progress after login.

The sync trigger decision now lives in one shared helper
(auto_sync_after_completion) used by the module, milestone, and login paths,
plus a small core/runtime.py that owns is_ci()/is_interactive(). Adds
tests/cli/test_progress_sync.py (15 tests) covering the non-TTY regression, the
CI/not-logged-in branches, honest 2xx interpretation, and the new command.
2026-06-15 13:57:06 -04:00

66 lines
2.4 KiB
Python

"""
Runtime environment detection for the TinyTorch CLI.
Single source of truth for two *separate* questions that the rest of the CLI
must never conflate:
1. ``is_ci()`` -- are we running inside automation (CI, a pipeline)?
2. ``is_interactive()`` -- can we safely prompt the user and read an answer?
Why these are separate
----------------------
A previous version gated progress *syncing* on ``sys.stdin.isatty()``. That
silently broke sync for real students on Windows Git Bash / MinTTY, where the
shell pipes stdin to ``python.exe`` and ``sys.stdin.isatty()`` returns ``False``
*even in an interactive terminal*. The same happens in some IDE-integrated
terminals on any OS.
The lesson encoded here: "can I prompt?" is NOT the same as "should I do the
network action?". Whether to *prompt* depends on having a usable TTY; whether to
*sync* depends only on being a real (non-CI) user who is logged in. Callers
decide whether to prompt with ``is_interactive()`` and decide whether to act
with ``is_ci()`` -- never the other way around.
"""
import os
import sys
# Environment variables set by common CI / automation providers.
_CI_ENV_VARS = (
"CI", # generic; set by GitHub Actions, GitLab, CircleCI, Travis, ...
"GITHUB_ACTIONS",
"GITLAB_CI",
"JENKINS_URL",
"BUILDKITE",
"TF_BUILD", # Azure Pipelines
"TEAMCITY_VERSION",
)
def is_ci() -> bool:
"""Return ``True`` when running in a CI / automation environment.
Used to suppress *all* interactive behavior and any "helpful" background
network calls (like auto-syncing progress) that have no place in automation.
"""
return any(os.environ.get(var) for var in _CI_ENV_VARS)
def is_interactive() -> bool:
"""Return ``True`` only when it is safe to prompt the user.
Requires a real console on **both** stdin and stdout, and that we are not in
CI. This is intentionally conservative: when it returns ``False`` the caller
should proceed *without* a prompt (e.g. auto-confirm), not skip the action.
See the module docstring for why this must never be used to decide whether
to perform a network sync.
"""
if is_ci():
return False
try:
return bool(sys.stdin.isatty() and sys.stdout.isatty())
except (AttributeError, ValueError):
# Streams can be replaced (e.g. by test harnesses) or closed.
return False