A Read-Only Terminal Link for a Training Run You Check From Your Phone

A Read-Only Terminal Link for a Training Run You Check From Your Phone

Hugging Face shipped TRL v1.13.0 on September 10 with a guide that trains one 1,048,576-token sequence per step on a single 8×H100 node. Qwen3-8B, 380 seconds a step, 56.2 GB per GPU, twelve steps. That is a run you start, walk away from, and want to glance at from the couch or the train. A read-only terminal link is the least-privilege way to do that: the process stays on the training box, nobody gets a shell, and the phone shows the same tqdm bar the launching terminal shows. This post walks through the exact command with shell.online, what the viewer sees, and what the link does and does not grant.

The command, and what it prints

The guide launches its example with accelerate. Prefix that launch with the CLI from shell.online and add the read-only flag before the command. The double dash ends option parsing so accelerate's own flags are never mistaken for ours:

shell --read-only -- accelerate launch \
    --config_file examples/sft_qwen3_8b_1m_context/context_parallel_8gpu.yaml \
    examples/sft_qwen3_8b_1m_context/sft_qwen3_8b_1m_context.py

The wrapped command runs in the background inside a local PTY. The URL prints only after the relay connection exists and the task has survived a short startup handshake, so a launch that dies on a bad config path prints its real exit status and no dead link. In an interactive terminal you also get a QR code whose fragment carries the salted URL and the password together, so a phone opens it with one scan. Piped and JSON output stay machine-clean.

The launching terminal is now free. Use shell list to see the process uptime and the relay's independent online, reconnecting, expired, or unknown state; unknown means the check was inconclusive, not that the link died. To take over locally, shell attach with the session ID puts you back in the PTY without disconnecting any browser, and Ctrl-X then D detaches again. The CLI reference lists every option and the exit codes.

The guide notes that the first step lands after ten minutes or so of loading and tokenizing. A viewer who opens the link during that window sees the accelerate startup output and then the progress bar updating in place, because the process is writing to a real PTY and tqdm behaves exactly as it does locally.

What a read-only terminal link shows on a phone

Viewers never own PTY sizing. A session with only desktop browsers attached runs on a 120×36 grid. The moment a phone or portrait viewer connects, the relay switches everyone to an 80-column grid and each browser fits that grid to its own screen; the last portrait viewer leaving restores 120×36. Read-only phones count for this too. The mobile behavior page describes the 80×24 compatibility grid; as of v0.11.2 the CLI advertises support for a taller 80×40 grid in its host handshake header, the relay picks 80×40 when the host supports it, and older CLIs still get 80×24. The negotiation lives in pull request #100, which also added two-finger pinch zoom on the terminal alone, from 50% to 150%, without disturbing anyone else's view.

For a training run, 80 columns matters. The progress line in the guide is short enough to survive the switch unwrapped:

{'loss': 4.311, 'grad_norm': 29.25, 'num_tokens': 1049000.0, 'epoch': 0.25}
  8%|▊ | 1/12 [06:20<1:09:41, 380.10s/it]

Single-finger gestures scroll terminal history. If output outruns the network or the phone's rendering, stale display work is dropped and sends one authoritative screen snapshot from a bounded local ring buffer, so a returning viewer sees the current screen rather than a replay of every intermediate redraw. The reliability page covers the bounded queues and write timeouts that keep a high-output process from ever blocking on Cloudflare.

Read-only is enforced by the relay, not the browser

With the read-only flag, ordinary browser input and the confirmed Ctrl-D EOF are rejected by the Worker. Opening DevTools or hand-crafting WebSocket frames cannot turn the link into an interactive session, and the access mode cannot be weakened after creation. That is the property you want when the link is going to sit in a group chat for an hour: the worst a holder can do is watch.

Watching is still access. The URL and the eight-character password together are a bearer credential. Anyone holding both can read everything the terminal prints, which for a training run means dataset names, checkpoint paths, loss values, and any secret a careless script echoes. Treat the pair like a token, and rotate by starting a new share.

What the relay sees is the other half of the trust model. Terminal payloads are encrypted on the training box with AES-256-GCM before they enter Cloudflare and decrypted in the browser. The printed URL carries only a random salt in its fragment, which never reaches the server, and the CLI and browser derive the same key locally with PBKDF2-HMAC-SHA256 at 600,000 iterations, the key derivation function specified in RFC 8018. Cloudflare still observes connection IPs, timing, encrypted frame sizes, opcodes, access mode, the command label, and lifecycle events, and it does not persist terminal contents. The end-to-end encryption page is explicit that E2EE cannot hide traffic shape or stop the relay from dropping or delaying a valid frame.

Eight generated base64url characters give 48 bits of entropy, which is fine for a link that lives as long as one task. For a multi-day run, or one whose logs you would not want indexed, set SHELL_ONLINE_E2EE_PASSWORD to a longer unique value before launching and send it separately from the URL. Derivation still happens on the machine. The security model page lays out the full boundary, including the point that an interactive share types with the wrapped process's operating-system permissions, which is exactly why this workflow uses the read-only mode.

Network loss, deadlines, and the end of the run

The link can disappear. The process should not. The CLI and browser reconnect with bounded backoff, a temporary network failure never stops the local task, and the same ordinary share link remains recoverable for 12 hours after its host disconnects. A phone that loses signal in a tunnel comes back to the current screen, because the relay keeps the session and the CLI keeps the process.

Lifetime is bounded by the task. When accelerate exits, the share closes and its server-side state is deleted; there is nothing to clean up. If you want the link to expire before the run does, add an earlier deadline. Relative units and local times both work:

shell --read-only --auto-close 2h -- accelerate launch ...
shell --read-only --auto-close "tomorrow 09:00" -- accelerate launch ...

An auto-close value can only shorten the lifetime, never extend it past task exit, and a missing or invalid value returns status 2 instead of becoming part of the command. Since v0.11.0 long deadlines are honored rather than silently clipped to the relay's rolling 12-hour lease, which matters for exactly this kind of overnight job.

Two more cases come up with training runs specifically. First, the guide's run reaches 63.6 GB on an 80 GB card and still fails without the expandable-segments allocator setting. When a run dies like that during the handshake, the CLI reports the status and prints no URL, so a monitoring link never points at a corpse. Second, when you resume from a checkpoint after a crash, the persistent mode keeps the same URL and password across relaunches:

shell --read-only --persistent ~/.shell-train-state -- accelerate launch ...

The state file stores an owner-only host identity, password, and E2EE material. Whoever already has the link opens the resumed run without a new credential exchange, and shell kill stops the current process without deleting the identity file. The source repository documents this under platforms and in the CLI help, alongside the Docker image that combines the same mechanism with an automatic restart policy.

None of this needs an account. Linking a machine with shell login is optional and publishes only the share URL, command name, host name, and timing to the web app, never terminal contents or the password. For a solo training run watched from a phone, the plain command above is the whole setup. The broader idea, a browser link to any terminal process, is covered in the introduction to shell.online.

Watch the run, not the shell

One command wraps a training launch in a read-only, end-to-end encrypted link that opens on any phone and closes itself when the job ends.

Try shell.online
About this article

Published by the Pilot Protocol team. Product claims are scoped to the availability labels and technical references linked in the article; deployment behavior can vary by version and environment.

How we publish · Suggest a correction · Technical references