Conversation
| try: | ||
| os.chdir(os.path.expanduser(path)) | ||
| except FileNotFoundError: | ||
| click.echo( | ||
| click.style(f"Directory not found: {path}", fg=ClickColor.BRIGHT_RED) | ||
| ) | ||
| except PermissionError: | ||
| click.echo( | ||
| click.style(f"Permission denied: {path}", fg=ClickColor.BRIGHT_RED) | ||
| ) |
There was a problem hiding this comment.
🔴 do_cd missing NotADirectoryError/OSError handler crashes the REPL
When a user runs cd somefile.txt (pointing to a regular file, not a directory), os.chdir() raises NotADirectoryError. This exception is not caught by the except FileNotFoundError or except PermissionError handlers at app/commands/repl.py:140-147, causing it to propagate uncaught through cmd.Cmd.cmdloop() and crash the entire REPL session with a traceback.
Root Cause
NotADirectoryError is a subclass of OSError but NOT a subclass of FileNotFoundError or PermissionError. The do_cd method only catches those two specific exceptions, missing NotADirectoryError and other possible OSError subclasses.
Notably, the _run_gitmastery_command method in the same file correctly catches the broader OSError at app/commands/repl.py:106:
except (FileNotFoundError, PermissionError, OSError) as e:but do_cd does not follow the same pattern.
Impact: Any cd to a non-directory path (e.g., a regular file) or a path triggering other OSError variants (symlink loops, etc.) will crash the REPL, forcing the user to restart their session.
| try: | |
| os.chdir(os.path.expanduser(path)) | |
| except FileNotFoundError: | |
| click.echo( | |
| click.style(f"Directory not found: {path}", fg=ClickColor.BRIGHT_RED) | |
| ) | |
| except PermissionError: | |
| click.echo( | |
| click.style(f"Permission denied: {path}", fg=ClickColor.BRIGHT_RED) | |
| ) | |
| try: | |
| os.chdir(os.path.expanduser(path)) | |
| except FileNotFoundError: | |
| click.echo( | |
| click.style(f"Directory not found: {path}", fg=ClickColor.BRIGHT_RED) | |
| ) | |
| except PermissionError: | |
| click.echo( | |
| click.style(f"Permission denied: {path}", fg=ClickColor.BRIGHT_RED) | |
| ) | |
| except OSError as e: | |
| click.echo( | |
| click.style(f"Cannot change directory: {e}", fg=ClickColor.BRIGHT_RED) | |
| ) |
Was this helpful? React with 👍 or 👎 to provide feedback.
| finally: | ||
| sys.path.remove(tmpdir) | ||
| # Clean up cached modules again after execution | ||
| _clear_exercise_utils_modules() |
There was a problem hiding this comment.
🟡 sys.dont_write_bytecode not reset in finally block, stays True for entire REPL session on failure
If exec() at app/utils/gitmastery.py:160 raises an exception, the finally block at lines 161-164 cleans up sys.path and cached modules, but sys.dont_write_bytecode = False at line 166 is outside the try/finally and is never reached. In the REPL context (where the process persists across commands), this leaves sys.dont_write_bytecode = True for the remainder of the session.
Root Cause
The PR correctly added _clear_exercise_utils_modules() inside the finally block at line 164 to handle REPL persistence, but didn't move sys.dont_write_bytecode = False (line 166) into the same finally block. When exec(py_file, namespace) at line 160 throws, the exception propagates through the with tempfile.TemporaryDirectory() context and skips line 166 entirely.
In the normal CLI flow this was harmless because the process exits after each command. But now with the REPL (app/commands/repl.py), the process persists. A failed verify or download command that triggers this path will leave sys.dont_write_bytecode = True, suppressing .pyc generation for all subsequent operations in the session.
Impact: After a single load_file_as_namespace failure in the REPL, Python bytecode caching is disabled for the rest of the session, degrading performance for all subsequent imports.
| finally: | |
| sys.path.remove(tmpdir) | |
| # Clean up cached modules again after execution | |
| _clear_exercise_utils_modules() | |
| finally: | |
| sys.path.remove(tmpdir) | |
| # Clean up cached modules again after execution | |
| _clear_exercise_utils_modules() | |
| sys.dont_write_bytecode = False |
Was this helpful? React with 👍 or 👎 to provide feedback.
app/commands/repl.py
Outdated
|
|
||
| def do_help(self, arg: str) -> bool: # type: ignore[override] | ||
| """Show help for commands.""" | ||
| if arg: |
There was a problem hiding this comment.
This feature is not made known to users, seems like it can show help message for specific command.
app/commands/repl.py
Outdated
| super().do_help(arg) | ||
| return False | ||
|
|
||
| # Show general help |

RFC: https://docs.google.com/document/d/1Xr-ppB1TLQtQB_tvPvOtSi2_h4xAhqMEEM4JxbVy368
Fixes git-mastery/git-mastery#68