The box updates itself
A box installed with curl -fsSL https://fezu.dev/install | sh checks for a
new signed release every hour and installs it at once, even while a thread is
working: the restart stops the running turns, and they pick up again after it.
It waits only for a command you left running in the shell panel, for a day at
most, except on the dev channel (see release-channels).
The shell panel keeps running through the update. It goes
back to the old version if the new one does not come up and stay up.
A new release comes out at most once a day, on days with changes, together with the matching version of the app at app.fezu.dev. A release that fixes a serious bug can be marked urgent: boxes then install it at their next hourly check even while a command runs in the shell panel.
Why you would use it
You want new versions on the box without logging in to it, and without an update cutting off a command you left running in the shell panel.
How to use it
There is nothing to turn on. The installer enables v-code-update.timer.
- To see when the next check runs:
systemctl --user list-timers v-code-update.timer. - To check now:
~/.local/share/v-code/current/install.sh update. It runs the same updater the timer does, with the same rules. - To read what it did:
journalctl --user -u v-code-update. - To install the newest release now, even while a command runs in the shell panel, run
~/.local/share/v-code/bin/v-code-update --update --forcefrom a terminal (ssh, or a desktop terminal). It prints what it does as it goes. The restart stops the turns that are running; they pick up again after it. From a tab, use/update(update-from-a-tab).
A box installed from a git clone has no timer. Update it with update-the-install.
On a Mac (install-on-macos) the timer is the launch
agent dev.v-code.update, which runs the same updater every hour, starting an
hour after it is loaded. To check now: the same install.sh update. What it
did is in ~/Library/Logs/v-code/update.log. Where these pages say systemd,
the Mac asks launchd: launchctl print gui/<uid>/dev.v-code.server for whether
VCode runs, its runs count in place of NRestarts, and
launchctl kickstart -k to restart onto the new version.
What you see
Nothing in the app. The updater writes to the journal, each line starting with
v-code::
| Line | Meaning |
|---|---|
up to date: <installed> (the newest release is <v>) |
Nothing newer. |
a thread is busy; a restart resumes the turns it cuts |
A thread has work. It does not hold the update back; the turns the restart stops resume. |
a thread is busy; <v> waits for the next run |
A thread has work, and this run is install.sh update from a thread, whose restart would stop it. The hourly run installs it. |
a thread is busy; <v> is urgent, so it installs now |
The release is marked urgent. It installs anyway, and the restart stops the running turns. |
a command runs in the shell panel (<command>); <v> is urgent, so it installs now |
The same for the shell panel. |
a thread is busy; <v> is on the dev channel, so it installs now |
The box is on the dev channel. It installs anyway, and the turns it stops resume. The shell panel line has the same ending. |
a thread is busy; --force installs <v> now |
You ran it with --force. It installs anyway. The shell panel line has the same ending. |
<v> is urgent, but this run is inside v-code.service, whose restart would stop it |
An urgent release found by install.sh update run from a thread. It waits like any release; the hourly run installs it. |
<v> is on the dev channel, but this run is inside v-code.service, whose restart would stop it |
The same for a dev box. The next run of the timer installs it. |
a command runs in the shell panel (<command>); <v> waits for the next run |
A pane in the shell panel runs something other than a shell. It tries again in an hour. |
a command has run in the shell panel (<command>) for 86400 s or more; updating anyway |
The shell panel has been busy for a day. The update goes ahead and the restart ends that command. |
VCode on 127.0.0.1:<port> did not answer; <v> waits for the next run |
The unit is active but did not answer. |
VCode did not answer and systemctl --user did not either, so this run cannot tell whether VCode is running; <v> waits for the next run |
Neither VCode nor the user manager answered. The run counts the box as busy. |
VCode is not running, so no turn can be cut off |
systemd says the unit is not running. It goes ahead. |
updating <old> to <new> |
Downloading, then switching. |
updated to <new> |
The new version answered and stayed up. |
<old> is still the version running: <new> was never started; switched back to <old>; <new> is tried again next run |
The restart never reached the new version. It is not rejected. |
<new> did not come up within 30 s, and VCode was down before the switch; switched back to <old>; <new> is tried again next run |
The box was already down, so the release is not blamed. It is not rejected. |
<v> was rolled back before; waiting for a newer release |
That version failed here once and is never tried again. |
another install or update is running; trying again next run |
Another run holds the lock. |
A rollback exits 1, so systemctl --user status v-code-update shows it failed:
<new> did not report itself running within 30 s; rolled back to <old>, and <new> will not be tried again,
or <new> came up but did not stay up; … when it answered once and then did
not answer 5 s later, reported another version, or systemd restarted it in between.
A switch back that does not reject the release exits 1 too.
Options and settings
| Option | Default | What it changes |
|---|---|---|
OnBootSec |
2min |
First check after boot. A timer started later than that fires at once, so a fresh install checks right away |
OnUnitActiveSec |
1h |
Time between checks |
RandomizedDelaySec |
5min |
Spreads boxes out so they do not all fetch in the same minute |
TimeoutStartSec |
30min |
A hung download is stopped, so later runs are not held back |
Limits and known gaps
- The updater needs
AUTH_TOKENin~/.config/v-code/envto ask VCode whether it is busy, and reads the port fromVCODE_PORTthere. Without the token the run stops:no AUTH_TOKEN in <env file>, so VCode cannot be asked whether it is busy. Without the file it stops too:no env file at ~/.config/v-code/env: run ~/.local/share/v-code/current/install.sh install. - A box installed before the rename to VCode keeps its old updater, which
reads
latest.json. That pointer stays at the last release before the rename, so the box stays on it until you run the install command once (install-on-linux). That install moves the box to the new names and puts this updater in its place.install.sh install --from-updatenever does the move: on a box that still has an old name it stops withthis box still has names from before the rename to VCode: run curl -fsSL https://fezu.dev/install | sh first. - A box with a command always running in the shell panel updates once it has
run for a day, or sooner when the release is urgent or you run it with
--force. - Urgent and
--forceskip only a busy shell panel. When VCode does not answer, orsystemctl --userdoes not, the update waits as it always does. --forcerun from a VCode thread stops before it fetches anything:this is running inside v-code.service (a VCode thread or its shell panel), which --force restarts part way through. Nothing was changed. Run it in its own unit: systemd-run --user --collect --unit=v-code-update-forced …/v-code-update --update --force; its log: journalctl --user -u v-code-update-forced. On a Mac it says to run it from Terminal. The shell panel on Linux runs outside the unit, so--forceworks from there.- An urgent release reaches a box only through an updater that knows the flag. A box on a release from before 2026.10.01 waits for an idle moment the first time. A box on a release from before 0.1.42 waits for no thread to be working the first time too.
- An urgent release that was rolled back on a box is never tried there again.
- On Linux VCode starts the shell panel's tmux in a systemd scope of its own
(
systemd-run --user --scope), so a restart leaves the panel and whatever runs in it alone, and holds nothing back. A panel whose tmux was started inside the unit is different: a version before 2026.09.28 started it there, and so does a box wheresystemd-runfails, and on a Mac VCode cannot tell. There a pane left invim,less,htopor a server holds updates back until you quit it, or for 24 hours, whichever comes first. After that the update restarts VCode and the command ends with it. A pane at the prompt ofbash,zsh,sh,fish,dash,ksh,tcsh,csh,nu,elvishorxonshholds nothing back. - The route answers 403 on a server with no
AUTH_TOKEN:the updater needs AUTH_TOKEN set on the server. - Run from inside a VCode thread,
install.sh updatefinds that thread busy and waits for the next run. - It never moves to an older or equal version, and never to one in
rejected. - A busy thread does not hold back a rollback either. Only a run inside a
VCode thread waits, since the restart would stop it:
…; <new> will not be tried again, and the rollback to <old> waits until no thread is busy. The hourly run then finishes it. - A download slower than 1 KiB/s for 60 s is given up on, and the run tries again next hour.
- After a first install whose
install.shdid not finish, it does not update:the install of <v> has not finished; run ~/.local/share/v-code/current/install.sh install. Not updating. - The phone's cached app shell still needs its own refresh (update-to-a-new-build).
Related
- install-on-linux: the install that sets this up
- install-on-macos: the same on a Mac, with a launch agent
- update-the-install: updating a box installed from a git clone
- logs-and-troubleshooting: reading the journal
- release-channels: the account's channel; Dev installs every commit
- update-from-a-tab:
/updatestarts it from a tab