Skip to content

ares-install: stop reporting a successful install as a failure - #20

Merged
mariotaku merged 3 commits into
mainfrom
fix/install-cleanup-reporting
Aug 28, 2026
Merged

ares-install: stop reporting a successful install as a failure#20
mariotaku merged 3 commits into
mainfrom
fix/install-cleanup-reporting

Conversation

@mariotaku

Copy link
Copy Markdown
Member

Three fixes to how ares-install reports what happened. The first is a correctness bug: on some sets it reports a successful install as a failure.

1. A successful install reported as a failure

The install request goes out over an exec channel, and the device starts work on it before it answers. A connection that dies in that window leaves request_exec returning Socket error: disconnected — while the device carries on and installs the package.

On a set that drops SSH connections around install time this is not a corner case. It reproduced four runs out of four on a UP7560: every one printed

Failed to install: Fatal: Socket error: disconnected

and every one succeeded on the device, confirmed against appinstalld's NL_APP_INSTALLED and the Installed-Time in opkg's status file. Caught in the act with a changed binary, so checksums could tell the builds apart:

staged                      b05d92da609916f5cd99369fb3d86587
ares-install                -> "Failed to install: Fatal: Socket error: disconnected"
device, immediately after   a83303ff8c8c805cbfdf91afa79cfdd5   <- old app
device, ten seconds later   b05d92da609916f5cd99369fb3d86587   <- new app

The device was still writing the package while ares-install had already given up. Anyone checking the moment it exits sees the old app and is told the install failed — wrong twice over, and it sends people hunting for a device fault that isn't there.

A dead connection is not evidence of a failed install. It now says what is actually known:

lost the connection to up7560 while the install was running. The device may have installed
the package anyway - `ares-install -d up7560 --listfull` prints the version that is on it.
`--list` alone only names the app, which says nothing about which build landed

--listfull because it serialises version; plain --list prints ids only, so it answers "is this app on the device", not "did this build land" — a distinction that cost real debugging time. The Failed to install prefix is dropped for this case, since it contradicts the one thing the message has to say. Exit status stays non-zero: an unknown outcome is not a success.

2. A silent install stream reported as success

dev/install is subscribed to, and the loop takes the first message saying "installed" or naming a failure. If the stream ends having said neither, .next() returns None — and that was mapped to Ok(String::new()), so ares-install printed Installed package ! and exited 0 for an install whose outcome it never learned. That is now an error.

3. Cleanup noise in front of the real error

Deleting the uploaded ipk happens on the way out, before the install result is propagated, so its failure always landed ahead of the error that explains the run:

Failed to delete /media/developer/temp/ares_install_fca561c25f.ipk: Ssh(Fatal("Inappropriate ioctl for device"))
Failed to install: Fatal: Socket error: disconnected

The ENOTTY is meaningless — a stale errno libssh reports through a session that is already gone — and it reads like a permission or path problem on the device. Cleanup now checks the session first and, when it is gone, names what was left behind instead:

Lost the connection to up7560, so /media/developer/temp/ares_install_d224c1e3f6.ipk is still on the device.

Errors in this file print with Display rather than Debug, so the remaining two say what went wrong instead of dumping a variant.

Verification

Fixes 1 and 3 were built and run against a UP7560 on the exact case that produced the false failure six times: output accurate, leftover ipk named, cleanup noise gone, exit status non-zero, and the package confirmed on the device by checksum. Fix 2 is not reachable on demand and was not exercised on hardware.

Workspace builds clean; 79 tests pass; clippy warnings for the crate drop from 36 to 34.

Not addressed here: why the socket dies. That was chased a long way — dropbear 2022.83 with no channel cap, listener pid and start time unchanged across installs, an idle reverse tunnel surviving untouched, no OOM kill, and a clean dmesg over a window verified to cover the event. It is narrowed to something on the device deliberately ending that process, and it is a device-side question that does not block this.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Gde2tcn5SbUHKhesuTNB7a

Mariotaku and others added 3 commits August 27, 2026 13:57
A run whose session drops mid-install printed the cleanup failure first
and the reason second:

    Failed to delete /media/developer/temp/ares_install_fca561c25f.ipk:
        Ssh(Fatal("Inappropriate ioctl for device"))
    Failed to install: Fatal: Socket error: disconnected

The delete is attempted on the way out, before the install result is
propagated, so its failure always lands ahead of the error that explains
the run. Worse, the ENOTTY is meaningless - it is a stale errno libssh
reports through a session that is already gone, and it reads like a
permission or path problem on the device.

Check the session before cleaning up. When it is gone, say what was left
behind - the upload had already succeeded, so the package really is still
sitting in /media/developer/temp - and let the real error through on its
own. "Deleting uploaded package..." moves inside the same branch, so it is
no longer announced for a delete that cannot happen.

Verified that libssh reports a vanished peer as `is_connected() == false`
against a throwaway sshd: killing the child holding the session flips it,
which is the condition this relies on.

Errors here are printed with Display rather than Debug now, so the
remaining two say what went wrong instead of dumping a variant.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gde2tcn5SbUHKhesuTNB7a
`dev/install` is subscribed to, and the loop takes the first message that
says "installed" or that names a failure. If the stream ends having said
neither, `.next()` returns None - and that was mapped to `Ok(String::new())`,
so ares-install printed "Installed package !" and exited 0 for an install
whose outcome it never learned.

A subscription can end that way whenever the channel closes early: the
device's session going down, luna-send exiting, appinstalld stopping mid
report. Claiming success there puts a package on the device that may not
be on it.

Report it instead. The device having stopped talking is not evidence
either way, and the message says so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gde2tcn5SbUHKhesuTNB7a
The install request goes out over an exec channel, and the device starts
work on it before it answers. A connection that dies in that window leaves
`request_exec` returning "Socket error: disconnected" - while the device
carries on and installs the package.

On a set that drops SSH connections around install time this is not a
corner case. A peer session hit it four runs out of four: every one
printed "Failed to install: Fatal: Socket error: disconnected", and every
one succeeded on the device, confirmed against appinstalld's
NL_APP_INSTALLED and the Installed-Time in opkg's status file. Anyone
reading that output reinstalls, or goes hunting for a device fault that is
not there.

A dead connection is not evidence of a failed install. Say what is
actually known - the connection went down mid-install, the package may
well be on the device, and `ares-install --list` will settle it - and skip
the "Failed to install" prefix, which contradicts the only thing the
message has to say.

The exit status stays non-zero: an unknown outcome is not a success
either.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gde2tcn5SbUHKhesuTNB7a
@mariotaku
mariotaku merged commit 14ef1f9 into main Aug 28, 2026
4 checks passed
@mariotaku
mariotaku deleted the fix/install-cleanup-reporting branch August 28, 2026 00:08
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