feat: increase timeout to 30s (#43)
Seth Troisi committed
Jun 4, 2026 at 10:50 UTC
889d09f82c87bc7eb26300a2237ca1b097434a3a
6 files changed
+28
-17
docs/02_execution_and_interactive.md
+3
-1
@@ -3,6 +3,8 @@ log:
3
2026-05-07: Fixed `colab console` piped-stdin handling. Previously a piped invocation (e.g. `echo 'cmd' | colab console -s s`) sent the command and then hung indefinitely because the previous EOF handler emitted a bare `\x04` (Ctrl-D), which the remote `tmux`-wrapped bash treats as a literal character rather than a session terminator. The new handler sends `exit\n` (which bash actually exits on) and then closes the websocket from the client side after a short grace period (`PIPED_EOF_GRACE_SECONDS = 0.5s`) so any tail output (bash `logout`, tmux `[exited]`) makes it back to the user. TTY mode is unchanged: real-terminal EOF is left to the remote shell. Verified live: `echo 'echo HELLO' | colab console -s s` now exits in ~1.2s instead of hanging.
4
5
2026-05-07: Fixed `print_kitty` (used by `colab exec --output-image` and any image-producing exec) to no-op when `sys.stdout.isatty()` is false. The Kitty Graphics Protocol escape sequence is meaningless when stdout is a file or pipe and was visually corrupting captured output (a multi-KB base64 PNG blob would land in log files, grep targets, or showboat captures). Image bytes are still saved to disk via `handle_image`'s file-write path; only the inline-render attempt is suppressed.
6
+
7
+2026-06-04: Bumped the default `--timeout` for `colab exec` from 10s to 30s (and the matching `colab run` default) so brief silent tasks are less likely to hit a premature `TimeoutError`. Explicit `--timeout` overrides are unaffected.
8
---
9
10
# Design: Execution and Interactive Interaction (`repl`, `exec`, `console`)
@@ -26,7 +28,7 @@ Execution involves sending Python code (or shell commands) to the Jupyter kernel
28
- If file path is local: Read content, send as code.
29
- If file path is remote: Execute `!python <path>`.
30
- **Multi-Modal Output**: Handle `display_data` messages (e.g., `image/png`, `text/html`). For the CLI, we'll save images to temporary files and print their paths, or if the terminal supports it (e.g., iTerm2), inline them.
29
-- **Timeout Configuration**: Exposes a `--timeout` flag (default 10s) to allow long-running silent tasks (like model compilation or data downloading) to execute without being prematurely killed.
31
+- **Timeout Configuration**: Exposes a `--timeout` flag (default 30s) to allow long-running silent tasks (like model compilation or data downloading) to execute without being prematurely killed.
32
33
### 3. Console (`colab console`)
34
- **Implementation**: Connects directly to the backend terminal endpoint (`/colab/tty`) via WebSockets using `websocket-client`.
docs/05_run_command.md
+2
-1
@@ -2,6 +2,7 @@
2
log:
3
2026-05-12: Initial design and implementation of `colab run <script.py> [args...]`. Combines `colab new` + `colab exec` + `colab stop` into a single fire-and-forget invocation so a Python file can use `#!/usr/bin/env -S colab run` as a shebang line and execute on a freshly-allocated Colab VM. Adds `--keep` (skip auto-stop), `--gpu` / `--tpu` (passthrough to session creation), `-s/--session` (name the ephemeral session), and propagates the script's exit status (non-zero on any uncaught exception in the kernel). The script's `sys.argv` is re-set inside the kernel to mirror native `python script.py arg1 arg2` semantics, and `__name__` is set to `"__main__"`.
4
2026-05-12: Native CPython exit-code semantics for `sys.exit()` / `raise SystemExit(...)` from the script body. The Colab kernel reports a `SystemExit` as `output_type=='error'`, which under the previous logic would have (a) printed the IPython traceback (`An exception has occurred, use %tb...`) and (b) flagged the run as a failure regardless of the integer exit code. Now: `sys.exit()` / `sys.exit(0)` exit 0 silently; `sys.exit(N)` exits N; `sys.exit('msg')` exits 1 (matching CPython). The IPython "To exit: use 'exit', 'quit', or Ctrl-D." UserWarning is filtered via the prelude. Encoded after running `examples/gpu_hello.py` end-to-end and seeing the noisy `SystemExit: 0` traceback at the end of an otherwise-successful GPU run.
5
+2026-06-04: Bumped the default value of the `--timeout` flag from 10.0s to 30.0s so short-but-silent tasks aren't prematurely killed out of the box. Mirrors the same change for `colab exec`.
6
---
7
8
# Design: `colab run` — Shebang-Compatible One-Shot Execution
@@ -29,7 +30,7 @@ colab run [OPTIONS] SCRIPT [SCRIPT_ARGS]...
30
| `--gpu` | str | None | Same set as `colab new --gpu` (T4, L4, G4, H100, A100). |
31
| `--tpu` | str | None | Same set as `colab new --tpu` (v5e1, v6e1). |
32
| `--keep` | bool | False | Do **not** stop the session after the script finishes. |
32
-| `--timeout` | float | 10.0 | Timeout in seconds for code execution to prevent hanging on silent tasks. |
33
+| `--timeout` | float | 30.0 | Timeout in seconds for code execution to prevent hanging on silent tasks. |
34
35
### Shebang usage
36
With `--keep` and `--gpu` baked into the shebang line, an entire one-file workload becomes:
src/colab_cli/commands/execution.py
+5
-4
@@ -109,8 +109,9 @@ def exec_command(
109
Optional[str], typer.Option("--output-image", help="Path to save plot")
110
] = None,
111
timeout: Annotated[
112
- Optional[float], typer.Option("--timeout", help="Timeout in seconds for code execution")
113
- ] = 10.0,
112
+ Optional[float],
113
+ typer.Option("--timeout", help="Timeout in seconds for code execution"),
114
+ ] = 30.0,
115
):
116
"""Execute code in a session"""
117
from colab_cli.common import state
@@ -208,9 +209,9 @@ def exec_command(
209
state.store.add(s)
210
211
outputs = runtime.execute_code(
211
- code,
212
+ code,
213
output_hook=lambda o: display_output(o, output_image),
213
- timeout=timeout
214
+ timeout=timeout,
215
)
216
if "cell" in block:
217
save_output(outputs, block["cell"])
src/colab_cli/commands/run.py
+4
-5
@@ -234,8 +234,9 @@ def run_command(
234
),
235
] = False,
236
timeout: Annotated[
237
- Optional[float], typer.Option("--timeout", help="Timeout in seconds for code execution")
238
- ] = 10.0,
237
+ Optional[float],
238
+ typer.Option("--timeout", help="Timeout in seconds for code execution"),
239
+ ] = 30.0,
240
):
241
"""Run a Python script on a fresh Colab VM, then release the VM
242
@@ -390,9 +391,7 @@ def run_command(
391
392
try:
393
outputs = runtime.execute_code(
393
- payload,
394
- output_hook=_make_run_output_hook(),
395
- timeout=timeout
394
+ payload, output_hook=_make_run_output_hook(), timeout=timeout
395
)
396
except Exception:
397
# Genuine transport-level failure. Cleanup still happens via the
tests/test_exec.py
+12
-4
@@ -58,7 +58,9 @@ def test_cli_exec_file(mock_store, mock_runtime_class, mock_common_state, tmp_pa
58
mock_runtime.execute_code.assert_any_call(
59
"import os; os.makedirs('/content', exist_ok=True); os.chdir('/content')"
60
)
61
- mock_runtime.execute_code.assert_any_call("print('hello')", output_hook=ANY, timeout=10.0)
61
+ mock_runtime.execute_code.assert_any_call(
62
+ "print('hello')", output_hook=ANY, timeout=30.0
63
+ )
64
65
66
def test_cli_exec_stdin(mock_store, mock_runtime_class, mock_common_state):
@@ -80,7 +82,9 @@ def test_cli_exec_stdin(mock_store, mock_runtime_class, mock_common_state):
82
assert mock_session.last_execution[1] is None
83
assert mock_session.last_execution[2] is not None
84
mock_store.add.assert_called_with(mock_session)
83
- mock_runtime.execute_code.assert_any_call("print(42)", output_hook=ANY, timeout=10.0)
85
+ mock_runtime.execute_code.assert_any_call(
86
+ "print(42)", output_hook=ANY, timeout=30.0
87
+ )
88
89
90
def test_cli_exec_not_found(mock_common_state):
@@ -194,6 +198,10 @@ def test_cli_exec_timeout(mock_store, mock_runtime_class, mock_common_state, tmp
198
script = tmp_path / "script.py"
199
script.write_text("print('hello')")
200
197
- result = runner.invoke(app, ["exec", "-s", "s1", "-f", str(script), "--timeout", "3600"])
201
+ result = runner.invoke(
202
+ app, ["exec", "-s", "s1", "-f", str(script), "--timeout", "3600"]
203
+ )
204
assert result.exit_code == 0
199
- mock_runtime.execute_code.assert_any_call("print('hello')", output_hook=ANY, timeout=3600.0)
205
+ mock_runtime.execute_code.assert_any_call(
206
+ "print('hello')", output_hook=ANY, timeout=3600.0
207
+ )
tests/test_ipynb_exec.py
+2
-2
@@ -98,10 +98,10 @@ class TestIpynbExec(unittest.TestCase):
98
"os.chdir", mock_runtime.execute_code.call_args_list[0].args[0]
99
)
100
mock_runtime.execute_code.assert_any_call(
101
- "print('cell 1')", output_hook=ANY, timeout=10.0
101
+ "print('cell 1')", output_hook=ANY, timeout=30.0
102
)
103
mock_runtime.execute_code.assert_any_call(
104
- "print('cell 2')", output_hook=ANY, timeout=10.0
104
+ "print('cell 2')", output_hook=ANY, timeout=30.0
105
)
106
107
@patch("colab_cli.commands.execution.ColabRuntime")