Skip to content

Desktop app lows: old tray on upgrade, restart waiter, cmd remover, uninstall put-back - #7

Merged
itrycodinganditsbad merged 12 commits into
mainfrom
desktop-lows
Oct 19, 2026
Merged

itrycodinganditsbad merged 12 commits into
mainfrom
desktop-lows

Conversation

@itrycodinganditsbad

@itrycodinganditsbad itrycodinganditsbad commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

The low findings left from the re-review of #6.

What changed

  • Old tray on upgrade (L3): craft-cloud --close tells the running window and tray to quit, waits for them to release their locks, and prints which ones were running. It checks both the new folder and the old loose file names.

    • install.sh runs it before copying and starts the new tray at the end.
    • If the install fails, the EXIT trap starts the installed tray again.
  • Restart waiter (L5): where the user has systemd, the waiter runs under systemd-run --user with a fixed unit name. It survives logind's KillUserProcesses, and a second install that gives up finds the first waiter and doesn't add another. Without systemd it still runs under nohup.

  • cmd remover fallback (N1–N3):

    • tasklist writes to a temp file first, so a tasklist that can't run means "wait", not "the setup has ended".
    • find and ping are called by full path.
    • The removal is retried every second for 30 seconds.
  • Uninstall failing after the stop (N5): if the agent's program is still there, its Run values are put back and, if it was running, it is started again. The error message says so.

  • Fix round from this PR's review:

    • Restart waiter, no agent unit (M1): the waiter only waits. It starts the agent outside itself (in a unit of its own, else detached) and ends, so a later install that gives up adds a waiter again.
    • Agent's folder variables (M2): CRAFT_CLOUD_AGENT_DIR and the XDG folder variables that are set reach the units through --setenv.
    • Reused pid (L6): the wait checks the process name as well as the pid, so a pid reused by another program can't hold the waiter.
    • Uninstall, held file (M3): the program folder's files are moved aside (Craft Cloud.removing-<pid>) before anything is deleted. The whole folder moves in one step, or file by file with roll-back when the setup runs from inside it. If a file held open blocks the move, nothing is touched, the Run values are put back and the agent is started again. On Windows a running exe can be moved; what can't be deleted yet goes once the setup has ended.
    • Put-back (L2): it needs all three exes in place, and says when a Run value couldn't be restored.
    • cmd remover (L1, L4): only find decides, so a failing ping no longer counts as "ended". The test now checks that each round waited.
    • Tray after upgrade (L5): the tray is restarted only where the session bus is reachable; elsewhere the script says how to get it back. Ctrl-C, TERM and HUP run the EXIT trap.
  • Second review round:

    • Roll-back: a roll-back that can't move every file back names the ones left aside.
    • Leftovers: the next setup run finishes any Craft Cloud.removing-<pid> folder left behind (its setup no longer running). An install moves back what the folder lacks and then installs over it; an uninstall deletes it.
    • Deleting the moved-aside files: this can no longer fail the uninstall; whatever is left over is cleared by the next run.
    • cmd removers: each has its own temp file.
    • Waiter inside its own unit: if it can't create the agent's unit, it reports that to the journal instead of starting a nohup process the unit would kill.
  • After the real-machine install:

    • ssh … ./install.sh never returned. Under bash (Arch's sh), a background a && b & list keeps the script's streams open while b runs. Every background start now goes through detach: stdin, stdout and stderr on /dev/null, and setsid where it's available. A test runs install.sh under both sh and bash with its output as a pipe.
    • Start at login pointed at another copy. Every install now points both Run values at the installed copy. That covers a tray value left by a dev build that ran once. A value that isn't there stays away.

Testing

  • Linux: cargo xtask ci passes.

  • New install.sh tests:

    • closing the old tray and starting the new one;
    • a second install that gives up adds no second waiter;
    • the nohup fallback without systemd-run.

    Stand-ins for systemd-run and systemctl keep the tests from touching the machine's real systemd.

  • Windows: clippy --all-targets -D warnings and the tests for the desktop, setup and xtask crates pass. The cmd remover now also has a test with a tasklist that can't run: the folder stays.

  • New unit tests cover --close (a tray in the new folder and an old-version window at the old names) and the uninstall that fails after the stop.

Left

  • The cmd remover's temp file stays in TEMP if it gives up after an hour. If TEMP isn't writable, the remover waits the hour and removes nothing.

  • close_running_tells_the_window_and_the_tray_new_and_old takes about 6 s: its fake listeners never release their locks.

  • N4 (needs a decision): clipboard-win ships no licence file, so LICENSES.txt gives it the BSL-1.0 text from error-code, which has the same author and licence. That is a deliberate exception to "no file means plain MIT/Apache or fail". Please accept it or rule otherwise.

  • Without systemd, the nohup waiter still dies with the session under KillUserProcesses. Systems without systemd don't have that setting.

  • A final uninstall message left open for over an hour still leaves the program folder behind (L7).

🤖 Generated with Claude Code

https://claude.ai/code/session_01RC1oqfy9kZznj94RjMojyz

itrycodinganditsbad and others added 12 commits October 19, 2026 10:27
An upgrade left the old tray process running on its old lock (an earlier
version kept it loose in the data folder), so the new window started a
second tray: two icons until logout. The app gets `--close`, which tells
the window and the tray to quit, in the new folder and at the old loose
names, waits for them to let go of their locks and prints which ran.
install.sh runs it before the copy and starts the new tray at the end, or
the installed one from the EXIT trap when the install fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RC1oqfy9kZznj94RjMojyz
The waiter that starts the agent again after a timeout ran under nohup in
the login session, so logind's KillUserProcesses ended it with the
session, and every install that gave up queued another. Where the user
has systemd it is now a unit of its own (systemd-run --user, a fixed
name): it outlives the session, and a second install finds it waiting
and adds none. Elsewhere it stays under nohup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RC1oqfy9kZznj94RjMojyz
The fallback remover (where PowerShell can't run) piped tasklist into
find, so a tasklist that couldn't run read as "the setup has ended" and
the folder was removed too early, then left. tasklist now writes to a file
in the temporary folder first, and a failure means wait and ask again.
find and ping are named by their full paths (cmd looks in its working
folder first), and the removal is retried every second for half a
minute, not once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RC1oqfy9kZznj94RjMojyz
An uninstall that failed after the stop (a shortcut or a file that
couldn't go) left the agent stopped and its start at login removed. While
the agent's program is still there, its Run values are now put back and,
if it ran, it is started again, so the computer keeps syncing until the
uninstall is run again; the error says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RC1oqfy9kZznj94RjMojyz
An uninstall that failed on a held file after deleting some files left a
half-removed folder: the agent couldn't be put back, or was put back over
missing files. The folder's files are now moved aside first, into
"Craft Cloud.removing-<pid>" next to it: the whole folder in one move, or
one file at a time when this setup runs from it, moved back if one can't
move. A file held open fails the move as a whole, so then nothing is
touched and the agent is put back and started again. Only after the move
is anything deleted; what can't go yet (an exe still running) goes once
the setup has ended.

The put-back now needs all three exes in place, says when a Run value
couldn't be restored, and says nothing about start at login when none
was removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RC1oqfy9kZznj94RjMojyz
…wait

In the cmd fallback a ping that failed after find had matched (the setup
still running) fell through to the removal. The wait now ends in ver,
which always succeeds. The test with a tasklist that can't run now checks
that each round waited (a broken line would end at once and pass) and
cleans up its temporary file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RC1oqfy9kZznj94RjMojyz
…riables

- Without a systemd unit for the agent, the waiter ran the agent in the
  foreground, inside its own transient unit: the unit stayed active for
  the agent's whole life, so a later install that gave up took it for a
  waiter and nothing restarted the agent. The waiter now starts the agent
  outside itself (a unit of its own, else detached) and ends.
- A transient unit doesn't get the installer's environment, so the
  restarted agent could use another data folder. CRAFT_CLOUD_AGENT_DIR and
  the XDG folder variables that are set are passed with --setenv, to the
  waiter and to the agent's unit.
- The wait checks the process name as well as the pid, so a pid reused by
  another program doesn't hold the (now exclusive) waiter for a day.
- The comment no longer claims the waiter outlives logout: only while the
  user's systemd runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RC1oqfy9kZznj94RjMojyz
…on Ctrl-C

Run over ssh or without a session bus, the tray closed for the upgrade
couldn't register again and the icon was gone. It is now restarted only
where the session bus is reachable; elsewhere the script says how to get
it back. INT, TERM and HUP end the script through exit, so dash runs the
EXIT trap and the agent and tray are started again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RC1oqfy9kZznj94RjMojyz
…he next run

- Uninstalling from inside the folder moves the files aside one by one;
  when one can't move, those moved go back. A file that can't go back is
  now named in the message instead of being dropped silently.
- A folder an earlier run left aside (<folder>.removing-<pid>, its setup
  no longer running) is finished by the next run: an install first moves
  back whatever the program folder lacks and then installs over it; an
  uninstall deletes it.
- Once the files are moved aside, failing to delete them now and failing
  to schedule the delete for later no longer fails the uninstall: the next
  run removes the leftover.
- Each cmd remover has a temporary file of its own: two may run at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RC1oqfy9kZznj94RjMojyz
Inside the waiter's unit, a nohup fallback for a failed systemd-run would
be killed with the unit as the waiter ends. There it now says in the
journal that the agent couldn't be started again; nohup stays the
fallback only outside systemd.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RC1oqfy9kZznj94RjMojyz
Run over ssh, install.sh finished but never returned: the tray it starts
kept the session's output open. Under bash (Arch's sh), a background
`[ -x … ] && nohup … &` list's subshell keeps the script's streams while
the program runs; dash execs it instead, which hid the bug here. Every
background start now goes through detach: stdin, stdout and stderr on
/dev/null, in a session of its own where setsid is there. The waiter's
own fallback does the same. A test runs install.sh under sh and bash
with its output a pipe and checks the pipe ends while the tray runs on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RC1oqfy9kZznj94RjMojyz
The app writes its tray Run value for whichever copy last ran, so a
development build run once (from a cargo target folder) started at every
login after the install. The setup only moved the agent's value, and only
from the older hand-made install. Now, on every install, an agent value
that starts another copy is taken over by the new agent (read back as
before), and a tray value is pointed at this install's app. A value that
isn't there stays away: start at login was turned off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RC1oqfy9kZznj94RjMojyz
@itrycodinganditsbad

Copy link
Copy Markdown
Owner Author

Tried on the two test machines:

  • Windows, at d61f2e7: the setup took 2.3 s from a WSL pipe, over a running install and a dev-build window. The window was closed, and the agent started again and was in sync. The tray value still pointed at the dev build then, which led to 49e1220.
  • Arch/Hyprland, at 49e1220: ssh … ./install.sh returns in 2.8 s. The old window and tray were closed, the new tray started, and the agent restarted and is in sync. At d61f2e7 the same ssh never returned (fixed in e362101).

Not tried: uninstalling from Apps & features.

@itrycodinganditsbad
itrycodinganditsbad merged commit 26cc7a7 into main Oct 19, 2026
4 checks passed
@itrycodinganditsbad
itrycodinganditsbad deleted the desktop-lows branch October 21, 2026 00:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant