A Terminal Sharing Link That Expires at a Set Time: What shell.online --auto-close Actually Stops

A Terminal Sharing Link That Expires at a Set Time: What shell.online --auto-close Actually Stops

On October 1, two coding tools shipped new ways to make an agent stop and ask. GitHub put computer use for Copilot CLI and the Copilot app into public preview, with the line "Copilot asks for approval before controlling an app". The same day Anthropic published Claude Code mods, and one of the example mods, Blast Radius, holds rm -rf, git reset --hard, git clean, force pushes and database migrations until someone presses Proceed or Cancel. Both are good controls, and both assume that a person is around to answer. This post covers the control for the hours when nobody is: a terminal sharing link that expires at a set time, and why in shell.online that expiry stops the process too, not only the link.

An approval prompt needs someone to answer it

An approval gate sits inside the agent. It fires when the agent reaches for something risky, and the session waits. If you started the agent at 6 p.m. and went home, that wait can last all night, or (if you turned the prompts off to let it run) there is no wait at all and the agent keeps working until it decides it is done.

A deadline sits outside the agent. It does not care what the agent is about to do. It says the process exists until a given time and then it does not. The two controls answer different questions, and an unattended session usually wants both: prompts for the actions you would want to see, and a hard stop for the case where nobody comes back to look.

shell.online gives any terminal process a browser link. You run a command through it, it prints a URL, a password and a QR code, and anyone holding the URL and password can open the live terminal from a phone or a desktop browser. The process and its PTY stay on your machine. The --auto-close flag is how you attach a deadline to that session when you start it.

A terminal sharing link that expires: the commands

Every share already closes when its wrapped process exits. --auto-close adds an earlier deadline on top of that. It goes before the wrapped command, like every start flag:

shell --auto-close 2h claude
shell --auto-close 1h30m -- npm run dev
shell --auto-close 18:30 codex
shell --auto-close tomorrow 09:00 -- python train.py

The command reference lists the grammar. Relative values take ms, s, m, h, d, w, mo and y, and units combine (1h30m, or 2d 3h). Absolute values accept RFC 3339, local ISO-like dates, today and tomorrow with an optional time, or a bare HH:MM, which means its next occurrence. Bare today means the end of today. Bare tomorrow means midnight at the start of tomorrow.

Some edge cases are handled on purpose and are worth knowing before you script around them:

  • A missing or invalid value returns exit status 2. It never gets treated as the command to run, so a typo in the deadline does not start a process with no deadline at all.
  • A date in the past is reported as having passed, not as a grammar error.
  • --auto-close never and --auto-close false are rejected with "sessions always close when their task exits". A deadline can shorten a session. It cannot keep one alive after its task ends.
  • If the deadline elapses before the process starts, the CLI refuses to start it.
  • Long deadlines are honored as written. Before v0.11.0 they were silently cut down to the relay's rolling 12-hour lease, which the changelog for that release records as a fix.

What actually happens at the deadline

This is the part that is easy to get wrong if you assume the flag only expires a URL. It is read from the CLI source in the shell.online repository, in cmd/shell/main.go and cmd/shell/session_unix.go.

The CLI wraps the process in a Go context created with context.WithDeadline. When the deadline passes, that context is cancelled, and the code that waits on the process treats it exactly like a stop request. On Unix it sends SIGTERM to the process group (a negative PID, which per kill(2) signals every process in the group whose ID is the absolute value), waits two seconds, and then sends SIGKILL if the process is still there. On Windows the process is killed directly. After that the CLI sends the final state to connected viewers and reports the exit code.

So in practical terms:

  • The agent, build or dev server is stopped. Whatever it had not written to disk is gone. If your program needs to clean up, two seconds after SIGTERM is all it gets.
  • The signal goes to the process group, so children that stay in that group get it too. That matters for agents that spawn test runners or servers of their own. A child that moved itself into a new group or session is outside it.
  • The link stops working because the session is over, and anyone watching sees it end.

If what you want is a link that dies while the work carries on, this flag is the wrong tool. Use shell password rotate <ID>, which the security guide describes as disconnecting old viewers, changing the password and salt, and revoking existing MCP grants while the process keeps running. To stop a session early by hand, there is shell kill -- <ID>, with shell list to find the ID.

The URL and the password together are a bearer credential. Anyone holding both can view the terminal and, unless the session is read-only, type into it with the operating-system permissions of the wrapped process. A deadline limits how long that is true. It does not limit what can be done in the meantime.

Pairing a deadline with a read-only link

Access mode and deadline are both chosen when the session is created, and they combine:

shell --read-only --auto-close 18:30 claude

This is the shape for handing a reviewer or a manager a view of an agent's afternoon. The security guide states that read-only input "is rejected by both the relay and the CLI", so a modified browser cannot turn it into an interactive session, and the deadline means the view, and the run, are over by 18:30 whether or not anyone remembers to clean up. One consequence of choosing at creation time: a read-only session cannot later be switched to interactive. If the owner wants to type, that takes a new share.

Read-only terminal access does not cancel file access. If you also pass --files or --files-root, the selected directory is exposed to whoever holds the link, deadline or not.

When an agent starts the session itself

An agent or a script that launches its own share can read the deadline back. With --json, the new-session event goes to stderr as one JSON object. For a session with a deadline, the source sets auto_close to "deadline" and adds closes_at as an RFC 3339 timestamp. Without one, auto_close is "task".

shell --json --read-only --auto-close 45m -- pytest -x

The same event includes e2ee_password for encrypted sessions, so treat that stderr stream as a secret: it should go to the operator, not into a CI log or a chat channel. The agent guide covers the separate path for giving another agent scoped MCP access, which has its own grants and expiry.

What a deadline does not cover

A few limits, stated plainly:

  • It is not a schedule. Nothing restarts the process after the deadline, and the flag cannot start one later.
  • It does not undo anything. Commands the agent ran before the deadline stay run, and anything a viewer copied stays copied.
  • It is enforced on your machine. The shell process that wraps the program is what sends the signals, so the deadline is only as good as that host being up. The README already says to keep the host awake and online, since a sleeping host cannot be watched either.
  • It does not change what the relay can see. With default end-to-end encryption the relay forwards authenticated ciphertext plus connection metadata, which per the security guide includes timing, encrypted sizes, labels, access mode, lifecycle and grid dimensions. With --no-e2ee, terminal contents are visible to the relay whether or not there is a deadline.
  • With --persistent, a later launch can reuse the saved URL and password. The deadline ends that run of the process and does not delete the state file, so the file still needs protecting.

For the overall model of what a share is and is not, our introduction to shell.online on this blog is the starting point, and the docs go through install, phones and teammates.

Picking a value

A deadline set too short kills work you wanted. One set too long is a link that stays live after you stopped caring. A reasonable rule is to set it to the time you would want to be told the run is still going: the end of the working day for an agent session you check from your phone, the morning stand-up for an overnight training job, an hour for a demo. The process can always finish sooner, and the share closes the moment it does.

Give an unattended session an end time

Install the CLI, then start your agent with shell --auto-close 2h claude. Add --read-only when the people you send it to only need to watch.

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